QEMU i.MX6 (Sabre Lite) で TOPPERS/FMP を動かす

QEMU の開発リポジトリでは i.MX6 のエミュレーションがマージされていて、以外にも(?)サクッと動いたので、その手順を記録しておく。

ゲストはTOPPERS/FMPカーネル

ホスト環境

Ubuntu 14.04

QEMU ビルド
% cd /tmp
% sudo apt-get build-dep qemu
% git clone git://git.qemu.org/qemu.git
% cd qemu
% ./configure --target-list=arm-softmmu
% make -j$(nproc) 
TOPPERS/FMP ビルド

ツールチェインの取得。

% cd /tmp
% wget -O - https://launchpad.net/gcc-arm-embedded/5.0/5-2016-q1-update/+download/gcc-arm-none-eabi-5_3-2016q1-20160330-linux.tar.bz2 | tar xjf -

コンフィグレータのビルド。

% cd /tmp
% sudo apt-get install libboost-all-dev
% wget -O - https://www.toppers.jp/download.cgi/cfg-1.9.5.tar.gz | tar xzf -   
% cd cfg
% make depend && make

FMP のビルド(コアの数は2とする)。

% cd /tmp
% wget -O - https://www.toppers.jp/download.cgi/fmp_imx6_gcc-20150917.tar.gz | tar xzf -
% cd fmp_1.3.0; mkdir obj; cd obj 
% ../configure -T imx6_gcc -g /tmp/cfg/cfg/cfg -P 2
% make GCC_TARGET=/tmp/gcc-arm-none-eabi-5_2-2015q4/bin/arm-none-eabi fmp.bin 
QEMU の起動

コアの数を2で起動。serial は telnet をバックエンドとし接続まち状態とする。

% /tmp/qemu/arm-softmmu/qemu-system-arm \
    -M sabrelite \
    -smp 2 \
    -kernel /tmp/fmp_1.3.0/obj/fmp \
    -nographic \
    -monitor stdio \
    -serial telnet:0.0.0.0:1200,server

別のターミナルから、待機中の serial の telnet に接続すれば FMP が動き出す。

% telnet localhost 1200
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Processor 2 start.
local_inirtn exinf = 2, counter = 1

TOPPERS/FMP Kernel Release 1.3.0 for Freescale i.MX6 (Jun  5 2016, 11:22:08)
Copyright (C) 2000-2003 by Embedded and Real-Time Systems Laboratory
                            Toyohashi Univ. of Technology, JAPAN
Copyright (C) 2004-2015 by Embedded and Real-Time Systems Laboratory
            Graduate School of Information Science, Nagoya Univ., JAPAN

Processor 1 start.
local_inirtn exinf = 1, counter = 2
Server task 2 starts.
System logging task is started on port 1.
Server task 1 starts.
Sample program starts (exinf = 1).
select tskno 0x11
select cycid 1
select almid 1
select processor 1
select class     1
task1_1 is running (001).   |
task1_1 is running (002).   |
task1_1 is running (003).   |
task1_1 is running (004).   |
task1_1 is running (005).   |

ZYNQ に GPIO 回路を接続し Linux 上で割込みを受け付けてみる(後編)

前編で作成した回路を確認するためのソフトウェア環境(Linux)を作成する。 尚、今回はVivado に含まれる hsi (Hardware Software Interface)という CUI ツールを使用し、一環してコマンドラインによる手順をとってみた。

準備

確認した環境は Ubuntu の 64 bit だが、Xilinx の開発ツールは 32 bit 向けのため、予め以下を実施。

% sudo apt-get install ia32-lib

また、開発ツールのパスを通すため、

% source  /opt/Xilinx/Vivado/2015.2/settings64.sh

を行っておく。

更に、開発ツールは gmake を期待するため、以下のように対処する。

% sudo ln -s /usr/bin/make /usr/bin/gmake
ZYBO 用 Linux イメージの取得

今回は、公開されているビルド済みイメージを拝借・流用する。

% git clone https://github.com/ucb-bar/fpga-images-zybo.git

clone したリポジトリから使用するファイルは以下の3点。

fpga-images-zybo/boot_image/u-boot.elf
fpga-images-zybo/uImage
fpga-images-zybo/uramdisk.image.gz
FSBL の作成

ZYNQ 用のブートローダである FSBL (Fast Stage Boot Loader) を作成する。

% mkdir fsbl-build && cd fsbl-build
% hsi

 ****** hsi v2015.2 (64-bit)
   **** SW Build 1266856 on Fri Jun 26 16:35:25 MDT 2015
     ** Copyright 1986-2015 Xilinx, Inc. All Rights Reserved.

hsi% open_hw_design /path/to/gpio-sample/gpio-sample.sdk/zybo_wrapper.hdf
zybo_wrapper
hsi% generate_app -hw zybo_wrapper -os standalone -proc ps7_cortexa9_0 -app zynq_fsbl -compile -sw fsbl -dir fsbl-build
hsi% exit

zybo_wrapper.hdf は前編の最後でエクスポートした H/W 情報のファイルで、/path/to/gpio-sample/ は Vivado で作成したプロジェクトのディレクトリ。

また、生成された FSBL は fsbl-build/executable.elf (ELF フォーマット)で、後ほど使用する。

Device tree の作成

前編でエクスポートした H/W 情報を基に、Device tree を作成する。

% mkdir device-tree && cd device-tree
% git clone git://github.com/Xilinx/device-tree-xlnx.git
% hsi
hsi% open_hw_design /path/to/gpio-sample/gpio-sample.sdk/zybo_wrapper.hdf
hsi% set_repo_path ./device-tree-xlnx
hsi% create_sw_design device-tree -os device_tree -proc ps7_cortexa9_0
device-tree
hsi% generate_target -dir ./
WARNING: ps7_ethernet_0: No reset found
WARNING: ps7_i2c_0: No reset found
WARNING: ps7_usb_0: No reset found
hsi% exit
% dtc -I dts -O dtb -o devicetree.dtb ./system.dts

今回は不要だが、必要に応じて、dtc をかける前に system.dts を編集する(bootargs 等)。 尚、dtc は Ubuntu パッケージのものでOK。

boot.bin の作成

boot.bin は FSBL、FPGA の bit ファイル、ユーザプログラムの3つをパックしたバイナリファイル。ZYNQ は起動デバイスから boot.bin をロードしてジャンプする。

まず、boot.bin の構成を定義するファイルを以下の内容で作成する。

the_ROM_image:
{
    [bootloader]/path/to/fsbl-build/executable.elf
    /path/to/gpio-sample/gpio-sample.sdk/zybo_wrapper.bit
    /path/to/fpga-images-zybo/boot_image/u-boot.elf
}

1行目に最初に作成した FSBL、2行目に前編で生成した FPGA 用の bit ファイル、3行目には u-boot (ELFフォーマット)を指定。

上記ファイルを boot.bif として保存し、以下のようにして boot.bin の作成。

% bootgen -image boot.bif -w on -o i boot.bin
microSD カードにコピーして起動

作成した boot.bin 及び devicetree.dtb と、流用する uImage 及び ramdisk-image を microSD カード(FATフォーマット)にコピーする。

% cp /path/to/boot.bin SDCARD
% cp /path/to/devicetree.dtb SDCARD
% cp /path/to/fpga-images-zybo/uImage SDCARD
% cp /path/to/fpga-images-zybo/uramdisk.image.gz SDCARD

microSD カードを ZYBO に挿入して、JP5(起動方法を選択するジャンパ)を SD にショートし、電源を投入する。 ホストから /dev/ttyUSB* を 115200bps で開き、root (パスワード: root) でログイン可能。 (当然ながら電源投入後でないと ttyUSB が見えないので注意)

GPIO の設定

起動した Linux カーネルには、既にドライバを含む GPIO サブシステムが備わっており、sysfs 経由で設定と操作が行える。

LED の端子一覧と、Linux カーネルが管理するGPIO 番号の対応は以下のとおり。

端子 GPIO 番号
LD0(M14) 898
LD1(M15) 899
LD2(G14) 900
LD3(D18) 901

上記を基に、設定を行う。

for i in `seq 898 901`; do
echo $i > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$i/direction
done

正しく設定できたか確認するため、LED を全て点灯してみる。(0 で消灯)

for i in `seq 898 901`; do
echo 1 > /sys/class/gpio/gpio$i/value
done

プッシュボタンは以下のとおり。

端子 GPIO 番号
BTN0(R18) 902
BTN1(P16) 903
BTN2(V16) 904
BTN3(Y16) 905

プッシュボタンの設定。割り込み設定も行う。

for i in `seq 902 905`; do
echo $i > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio$i/direction
echo rising > /sys/class/gpio/gpio$i/edge
done

正しく設定できていれば、ボタンを押している間に以下の値が1になる。

for i in `seq 902 905`; do
cat /sys/class/gpio/gpio$i/value
done
割り込みの確認

最終目的である、追加した GPIO の割り込みを Linux から確認する。

sysfs 経由の GPIO インターフェイスは、poll によって割り込みを確認することができる。 BTN0 の割り込みを待ち受ける以下のプログラムを作成した。(エラー処理などは省略)

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>

int main(void)
{
        struct pollfd sw;
        char buf[1];

        sw.fd = open("/sys/class/gpio/gpio902/value", O_RDONLY);
        sw.events = POLLPRI;

        while (1) {
                poll(&sw, 1, -1);
                lseek(sw.fd, 0, SEEK_SET);
                read(sw.fd, buf, 1);
                printf("pushed!\n");
        }
}

コンパイル:

% arm-xilinx-linux-gnueabi-gcc gpio-int.c -o gpio-int

これを ZYBO 上で実行すると、BTN0 を押したタイミングで pushd! が無事に表示された。

おしまい

Xilinx の開発ツールと、ZYNQ の仕組みがだんだんわかってきた。次は、Verilog で 何らかの IP を新たに作成して動かしてみたい。

ZYNQ に GPIO 回路を接続し Linux 上で割込みを受け付けてみる(前編)

最近、ZYNQ 搭載の評価ボードである ZYBO を購入し、遊んでいる。

http://www.digilentinc.com/Products/Detail.cfm?Prod=ZYBO

FPGA の評価ボードに初めて触れるため色々と試行錯誤があるが、ZYNQと、その開発ツールである Vivado に関する情報がWEB 上に多いので、独学でなんとかなっている。

まだ HDL をガリガリ書くまでに至っていないが、手始めとして、外部GPIOに接続されたプッシュボタンを ZYNQ で割り込みとして拾えるようにし、Linux カーネルから認識できたので、その手順を残す。 また、プッシュボタンの他に LED もつながっているため、ついでに使えるようにしてみた。

環境

Host: Ubuntu 14.04 64bit

Vivado: WebPack 2015.2

準備

予め、Digilentinc サイトから回路データの作成に必要となるファイルをダウンロードしておく。

http://digilentinc.com/Data/Products/ZYBO/ZYBO_Master_xdc.zip

http://digilentinc.com/Data/Products/ZYBO/ZYBO_def.zip

プロジェクトの新規作成

Vivado を立ち上げて、プロジェクトの新規作成を開始する。 f:id:myokota:20150812114556p:plain

RTL プロジェクトを選択。 f:id:myokota:20150812115347p:plain

次の"Add Sources" と "Add Existing IP" ウィザードではは何も追加せずそのまま Next で進み、その後の "Add Constraint" では、下記のように予めダウンロードしていた ZYBO_Master.xdc を指定する。また、下部の"Copy ..." にチェックを入れて、プロジェクトに xdc ファイルを取り込むようにする(後で編集するため)。 f:id:myokota:20150812115625p:plain

Part の選択で ZYBO に乗っている Zynq を指定する。下記の設定でフィルタした一覧から、"xc7z010..." を選択。(現バージョンでは、Select: Boards で ZYBO が出てこない) f:id:myokota:20150812115653p:plain

これでプロジェクト新規作成ウィザードが終了し、Vivado のメインウィンドウに移る。

回路の作成

さっそく回路の作成を行うため、"Create Block Design" を実行する(実行後、Design 名の入力を求められるので、zybo とか適当に設定する)。 f:id:myokota:20150812121011p:plain

まず、ZYNQ を追加する。Diagram ウィンドウで "Add IP" を実行。 f:id:myokota:20150812121246p:plain

追加できる IP 一覧が表示されるため、Search ボックスに zynq と入力して、"ZYNQ7 Processing System" を選択する。

f:id:myokota:20150812121300p:plain

さらに、ZYNQ と同じ要領で、外部 GPIO である "AXI GPIO" という IP を、プッシュボタンとLED用に2つ追加する("gpio" で検索すれば出てくる)。

これで手動による IP の追加が終わったので、"Diagram" ウィンドウ上部に表示されている "Run Block Automation"と"Run Connection Autmation" を実行する。

f:id:myokota:20150812121347p:plain

Run Block Autmation は手動で追加した IP を扱うために必要な他の IP を自動で追加してくれる(リセット回路など)。今回は何も変更せずそのまま OK ボタンで完了。

Run Connectoin Automation は追加した IP の配線を自動で行ってくれる。 今回は以下のように "All Automation" にチェックを入れて OK ボタンを押す。

f:id:myokota:20150812121417p:plain

この時点の回路図は以下のようになる。Diagram ウィンドウ左下の"Regenerate Layout"(リピートマークのようなボタン)を実行すると、レイアウトを整理してくれる。 f:id:myokota:20150812121904p:plain

IP のカスタマイズ (割り込みの設定)

引き続き、各 IP のカスタマイズを行っていく。まず、ZYNQ。Diagram ウィンドウ上の ZYNQ をダブルクリックして以下のウィンドウを開く。 f:id:myokota:20150812121523p:plain 上部の"Import XPS Setting" を押し、予め Digilentinc のサイトから ダウンロードしておいた、ZYBO_def.xml を設定する。これによって、ZYBO 向けの基本的な設定がインポートされる。

そして、今回の実験用のカスタマイズである外部 GPIO 割り込みの設定を行う。 左部の"Interrupts"から割り込み設定を開き、"PL-PS Interrupts" の "IRQ_F2P[15:0]" にチェックを入れる。 f:id:myokota:20150812121637p:plain

ZYNQ に対する設定は以上で終了。

次に、外部 GPIO のカスタマイズ。まず、プッシュボタン用の設定を行う。 Diagramウィンドウから、2つある外部 GPIO のどちらかをダブルクリックして以下の設定を行う。 f:id:myokota:20150812122044p:plain

デフォルト設定から、

  • "All Inputs" をチェック
  • "GPIO Width" を 4 に設定
  • "Enable Interrupts" をチェック

上記を行い、OK で完了。もう一方のLED用のGPIOのカスタマイズは、

  • "All Outputs" をチェック
  • "GPIO Width" を 4 に設定

とする。

そして、ZYNQ とプッシュボタン用GPIOの割り込み信号を結線する。Diagram ウィンドウにて、プッシュボタン用GPIO上の"ip2intc_irpt" と、ZYNQ上の "IRQ_F2P" の Drag&Drop で接続する。(接続可能な端子をマウスオーバーするとマウスポインタが鉛筆マークになる)

f:id:myokota:20150812122100p:plain

最後に GPIO 端子の設定を行う。まず、端子の名称を変更。 Diagram ウィンドウ上で GPIO 端子(デフォルトでは gpio_rtl ...)をダブルクリックすると "External Interface Property" というウィンドウが現れるため、そこの Name から変更する。 f:id:myokota:20150812122119p:plain 今回は、

  • プッシュボタン: btn_4bits
  • LED: led_4bits

とした。端子名称変更は必須ではないが、わかりやすさのために行った。 次いで、Block Design の Souces タブから、"Constraints"-> "constrs_1" -> "ZYBO_Master.xdc" をダブルクリックで開く。ファイルの内容は全てコメントになっており、必要な設定のみコメントを外して設定を有効にする。 f:id:myokota:20150812123131p:plain 今回はプッシュボタンとLEDなので、29行目付近から61行目付近の set_property から始まる行を有効にする。 ("##Buttons" と "##LEDs" というコメントがある範囲)

また、get_ports に、入出力の端子名を指定する。 今回であれば、

  • プッシュボタン: btn_4bits_tri_i
  • LED: led_4bits_tri_o

となる。 xxx_tri_i は入力用の端子名、xxx_tri_o は出力用の端子名(Block Design の Design タブで確認可能)

これで回路の作成は終わったので、HDL を生成する。 f:id:myokota:20150812123224p:plain

そしていよいよ、FPGA にロードするための回路データ(bit ファイル)を生成する。Vivado メインウィンドウ左下の "Generate Bitstream" を押すだけ。

f:id:myokota:20150812123455p:plain

生成完了までに数分かかる("Window" -> "Design Runs" ウィンドウで進捗状況がわかる)。

Bitstream の生成が完了すると、次の工程を促されるが、今回は必要ないため、Cancel で終了。

f:id:myokota:20150812123525p:plain

仕上げに、"File" -> "Export" -> "Export Hardware" を実行して、H/W 情報を SDK 用にエクスポート(後編で使用)。

f:id:myokota:20150812123542p:plain

"Include bitstream" にチェックを入れてOKで完了。

後編へ

長かったけど、これで ZYNQ からプッシュボタンとLEDを扱うための回路データを生成できた。後編では、ソフトウェアを準備し、動作確認を行ってみる。

Linux カーネル の /dev/random について

UNIX 系 OS では、疑似乱数を生成するためのデバイスとして、 /dev/random が提供されている。 /dev/random から生成される疑似乱数の元ネタは予測不可能な値である必要がある。 ハードウェアによる疑似乱数生成器が存在しない場合、ソフトウェアで何とかしないといけない。

Linux カーネルでは、疑似乱数の元ネタを各種デバイスドライバで生成される値を使用する。 それら生成された値を、/dev/random ドライバで管理するエントロピー・プールに溜め込み、 ユーザ空間から要求があった際に、エントロピー・プールから疑似乱数を生成し、提供される。

疑似乱数の元ネタを生成しているドライバをリストしてみる。(本当はもっとヒットするが、てきとうに割愛)

% head -n4 Makefile
VERSION = 3
PATCHLEVEL = 19
SUBLEVEL = 0
EXTRAVERSION = -rc5
% grep -n --exclude=drivers/char/random.c add_.\*_randomness **/*.c
block/blk-core.c:2549:      add_disk_randomness(rq->rq_disk);
drivers/char/hw_random/core.c:79:static void add_early_randomness(struct hwrng *rng)
drivers/char/hw_random/core.c:86:       add_device_randomness(bytes, bytes_read);
drivers/char/hw_random/core.c:98:   add_early_randomness(rng);
drivers/char/hw_random/core.c:354:      add_hwgenerator_randomness((void *)rng_fillbuf, rc,
drivers/char/hw_random/core.c:430:      add_early_randomness(rng);
drivers/input/input.c:152:  add_input_randomness(vals->type, vals->code, vals->value);
drivers/rtc/rtc-wm831x.c:112:       add_device_randomness(&reg, sizeof(reg));
drivers/scsi/scsi_lib.c:703:        add_disk_randomness(req->rq_disk);
drivers/usb/core/hub.c:2447:        add_device_randomness(udev->serial, strlen(udev->serial));
drivers/usb/core/hub.c:2449:        add_device_randomness(udev->product, strlen(udev->product));
drivers/usb/core/hub.c:2451:        add_device_randomness(udev->manufacturer,
kernel/irq/handle.c:176:    add_interrupt_randomness(irq, flags);
kernel/time/posix-cpu-timers.c:428: add_device_randomness((const void*) &tsk->se.sum_exec_runtime,
net/core/dev.c:1293:        add_device_randomness(dev->dev_addr, dev->addr_len);
net/core/dev.c:5842:    add_device_randomness(dev->dev_addr, dev->addr_len);
net/core/dev.c:6346:    add_device_randomness(dev->dev_addr, dev->addr_len);
(略)

予め確認したところ、 add_xxx_randomness という関数が /dev/random (drivers/char/random.c)ドライバで提供されているカーネル関数であり、引数で渡された値をエントロピー・プールに蓄えるらしい。その関数を呼び出すデバイスドライバ郡が上記となる。 尚、疑似乱数の元ネタを各デバイスドライバで「生成」すると前述したが、「生成」といっても、デバイスからの入力データをユーザ空間に転送する過程で、その入力データを /dev/random ドライバに横流しするだけのようである。

また、エントロピー・プールが枯渇している場合は、/dev/random からは疑似乱数が取得できない。 Linux カーネルにおける /dev/random の仕様としては、十分なエントロピーがない場合は、/dev/random からの read が block される。 (Non-blocking で read した場合は EAGAIN となる。)

実際にその状態を確認してみる。 取得に先立ち、エントロピーの(貯蔵)状態を確認してみる。

% uname -rv
3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014
% cat /proc/sys/kernel/random/entropy_avail
894

そして、/dev/random から値を取得してみる。

% hexdump -C /dev/random
00000000  74 2d 94 82 8a 22 e5 87  a8 37 36 94 c6 05 a0 9f  |t-..."...76.....|
00000010  2a 26 c4 ec a5 49 a6 80  c8 ca 4f d7 5e 1b e6 fd  |*&...I....O.^...|
00000020  ce 39 a3 ce 7c 84 05 0a  07 1a d6 87 46 63 29 b5  |.9..|.......Fc).|
00000030  c7 62 b5 12 2c d8 b3 6f  43 4f 0e 85 5d 8e bb 49  |.b..,..oCO..]..I|
00000040  e9 98 46 d8 75 2d 3f 21  ec a5 05 9e 9a 53 ab 1b  |..F.u-?!.....S..|
00000050  87 c1 cb a0 86 08 11 ff  41 5a 4f 04 cf cc d7 cb  |........AZO.....|
00000060  e0 c8 53 e3 e4 88 92 1c  0d a9 3e 0c db 1a 4f 5d  |..S.......>...O]|
^C

hexdump コマンドは、EOF まで read し続けるが、0x70 バイト分のデータを表示したところで停止した。これは、エントロピーが不足し、/dev/random からの read が block されているから。(SIGINT して終了させる。) その直後、再度エントロピー状態を確認してみると、以下のような結果となった。

% cat /proc/sys/kernel/random/entropy_avail
23

確かに、読み出し前とは値が少なくなっていた。

Linux カーネルの /dev/random ドライバを確認してみると、/dev/random が read された際に、エントロピーの状態とその閾値を比較して block している箇所は以下のような実装となっていた。

static ssize_t
_random_read(int nonblock, char __user *buf, size_t nbytes)
{
(略)
                 wait_event_interruptible(random_read_wait,
                         ENTROPY_BITS(&input_pool) >=
                         random_read_wakeup_bits);

ENTROPY_BITS(&input_pool) は、procfs の entropy_avail で取得できる値である。対して、閾値となる random_read_wakeup_bits という変数は、以下のように定義されている。

/*
 * The minimum number of bits of entropy before we wake up a read on
 * /dev/random.  Should be enough to do a significant reseed.
 */
static int random_read_wakeup_bits = 64;

従って、/proc/sys/kernel/random/entropy_avail が 64 以上であれば、疑似乱数を取得できることになる。 そんな /dev/random であるため、扱いに注意が必要である。たまたま体験した例では、Wi-Fi の WPA-PSK である。

WPA-PSK では、アクセスポイントとステーション間の 4way-handshake でお互いに疑似乱数が必要となるが、 Linux 用 wpa_supplicant ではその疑似乱数の取得に /dev/random を Non-blocking で使用し、 /dev/random から疑似乱数が取得できない場合、 4way-handshake を中断させてしまい、接続に失敗する。

もしそうなった場合、各種デバイスドライバからエントロピー・プールが貯め込まれるのを待てば解決するのだが、すぐに貯まるものでもない。 ユーザ空間で直接、/dev/random を叩き、エントロピーを貯めこむためのツールもあるようだが、いずれにしても、暗号化のための鍵生成等、無意識に /dev/random は使われていると思うので、特殊な環境においては、エントロピー・プールの状態には注意が必要かもしれない。

/dev/random がダメなら /dev/urandom を使えばいいじゃない、という意見もあるが、それはまた別の話。

SystemTap メモ

今日、初めて SystemTap を触ってみて感動したためメモを残す。

  • SystemTap の概要は下記のページで掴んだ

  • 手元の Ubuntu 14.04 環境での下準備

    % codename=$(lsb_release -c | awk  '{print [}')
    % sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
    deb http://ddebs.ubuntu.com/ ${codename}      main restricted universe multiverse
    deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
    deb http://ddebs.ubuntu.com/ ${codename}-updates  main restricted universe multiverse
    deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
    EOF
    # apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ECDCAD72428D7C01
    # apt-get update
    # apt-get install linux-image-$(uname -r)-dbgsym
    # apt-get install systemtap
    

  • Hello World で確認

    # stap -e 'probe begin { println("Hello, World!") exit() }'
    Hello, World!

  • 組込み関数について少しメモ

    • stapfuncs(5) を参照 (http://linux.die.net/man/5/stapfuncs)
    • 例1) task_current でカレント・プロセス(current)の task_struct のアドレスを取得し、task_execname でプロセス名を表示
      # sudo stap -e 'probe begin { println(task_execname(task_current())) exit() }'
      stapio
    • 何気に便利) errno_str は引数の値をerrno文字列に変換する
      # stap -e 'probe begin { println(errno_str(1)) exit() }'
      EPERM
  • 対象カーネルのプローブポイントを確認

    # stap -l 'kernel.function("*")' | grep input_event *:
    kernel.function("input_event@/build/buildd/linux-3.13.0/drivers/input/input.c:421")
    kernel.function("input_event_from_user@/build/buildd/linux-3.13.0/drivers/input/input-compat.c:17")
    kernel.function("input_event_size@/build/buildd/linux-3.13.0/drivers/input/input-compat.h:68")
    kernel.function("input_event_size@/build/buildd/linux-3.13.0/drivers/input/misc/../input-compat.h:68")
    kernel.function("input_event_to_user@/build/buildd/linux-3.13.0/drivers/input/input-compat.c:41")
    kernel.function("uinput_events_to_user@/build/buildd/linux-3.13.0/drivers/input/misc/uinput.c:499")
    # sudo stap -l 'kernel.function("input_event")'
    kernel.function("input_event@/build/buildd/linux-3.13.0/drivers/input/input.c:421")

    • コードブロックなしで kernel.function を呼ぶとプローブポイントを確認できる
    • 引数のプローブポイントにワイルドカードを指定することによって全てのプローブポイントを出力する
    • プローブポイントの書式は関数名[@ファイルパス[:行数]]
  • プローブポイントにデバッグログを仕込んでみる

    • プローブポイントは input_event 関数(inputサブシステムのI/F)とし、以下のコードを input_event.stp 等で保存する
      probe begin { println("begin...") }
      probe kernel.function("input_event") {
      printf("%s: 0x%x 0x%x 0x%x\n",
            kernel_string(@cast($dev, "input_dev")->name),
            $type,
            $code,
            $value)
      }
      • カーネルの変数には$変数名で参照可能
      • 構造体メンバへのアクセスには、@cast という関数で一キャストする
        • 第1引数には変数、第2引数には構造体の名称を指定する
    • 以下で実行
      # stap input_event.stp
      ...begin
      
      • マウスやキーボードを適当に操作すると、ログが出力されることを確認(環境は VirtualBox)
        VirtualBox mouse integration: 0x3 0x0 0xaeda
        VirtualBox mouse integration: 0x3 0x1 0x7b51
        VirtualBox mouse integration: 0x0 0x0 0x0
        ...(省略)...
        AT Translated Set 2 keyboard: 0x4 0x3 0x39
        AT Translated Set 2 keyboard: 0x4 0x4 0x39
        AT Translated Set 2 keyboard: 0x1 0x39 0x1
        ...(以下省)...
        
  • 便利な timer

    • 特定のプローブポイントでの駆動ではなく、インターバルタイマによる駆動も可能
    • 例) 10ミリ秒毎にカレント・プロセスの名前を表示
      # stap -e 'probe timer.ms(10) { task = task_execname(task_current()) if ("swapper/0" != task) println(task) }'
      • "swapper/0" は頻度が高いため除外している
      • ms のほか、susnshzjiffies がある
  • Call graph サンプル・スクリプト

    • 本家のサンプル・スクリプトを使用した関数呼び出しフロー
      % wget https://sourceware.org/systemtap/examples/general/para-callgraph.stp
      # stap para-callgraph.stp 'kernel.function("*@fs/*.c")' 'kernel.function("sys_mkdir")' -c "mkdir /tmp/test-dir" -o /tmp/mkdir-call-graph.log
    • -c で指定したコマンド終了時にスクリプトが終了するようにした
    • -o でファイルに保存した標準出力は以下のようになった
       0 mkdir(2346):->user_path_create dfd=0xffffffffffffff9c pathname=0x7fff26f41874 path=0xffff880000083f40 lookup_flags=0x2
      57 mkdir(2346): ->getname_flags filename=0x7fff26f41874 flags=0x0 empty=0x0
      60 mkdir(2346): <-getname_flags return=0xffff88003c976000
      77 mkdir(2346): ->kern_path_create dfd=0xffffffffffffff9c pathname=0xffff88003c976020 path=0xffff880000083f40 lookup_flags=0x0
      81 mkdir(2346):  ->filename_lookup dfd=0xffffffffffffff9c name=0xffff880000083e20 flags=0x10 nd=0xffff880000083e40
      86 mkdir(2346):   ->path_lookupat dfd=0xffffffffffffff9c name=0xffff88003c976020 flags=0x50 nd=0xffff880000083e40
      ...(省略)...
      27 mkdir(2346): ->mntput mnt=0xffff88003dbbaca0
      30 mkdir(2346):  ->mntput_no_expire mnt=0xffff88003dbbac80
      32 mkdir(2346):  <-mntput_no_expire
      33 mkdir(2346): <-mntput
      35 mkdir(2346):<-done_path_create
      
  • その他メモ

参考になったWebページ

LTP (Linux Test Project)

最近、LTP というプロジェクトを知りました。

https://github.com/linux-test-project/ltp

LTP は、Linux カーネルにおけるシステムコール等の各種インターフェイス用テストプログラム/スクリプト郡です。

このプロジェクトを知ったきっかけは、process_vm_readv というシステムコールの使い方を調べている時でした。man ページだけではイマイチ使い方がわからなかったので、より具体的なサンプルプログラムを Web 上で探していたところ、運良く上記 github リポジトリを発見しました。

早速、手元に clone し、grep してみると、テストコードらしきソースファイルを発見出来ました。

% grep --col "process_vm_readv" **/*.[ch]
testcases/kernel/syscalls/cma/process_vm.h:static inline ssize_t test_process_vm_readv(pid_t pid,
testcases/kernel/syscalls/cma/process_vm.h:#if defined(__NR_process_vm_readv)
testcases/kernel/syscalls/cma/process_vm.h:	return syscall(__NR_process_vm_readv, pid, lvec, liovcnt,
testcases/kernel/syscalls/cma/process_vm01.c: * errno tests shared by process_vm_readv, process_vm_writev tests.
testcases/kernel/syscalls/cma/process_vm01.c:static char TCID_readv[] = "process_vm_readv";
...(省略)...


本質的なコード以外にも、テスト実施に付随したコードがあるため一部追いづらいところもありますが、実際に動作するコードなので、参考になります。

尚、ltp のビルド手順は以下のとおりです。

# apt-get intall automake (必要に応じて)
% make autotools
% ./configure
% make

上記で、testcases のソースコードと同じ位置に、テストプログラム本体が出力されます。(具体的な使い方などは、ソースコード上で確認するのが早いです。)

まとめ

Linux プログラミングの際、より具体的なサンプルプログラムがあると、理解がより深まるのですが、往々にして Web 上では断片的な情報が多く、運が悪いと見つからない場合もあり、徒労に終わることがよくありました。

ltp を参照すれば、少なくとも、まとまったサンプルプログラムが リストされているので、有用であると感じました。

尚、システムコール以外の kernel のテストや、テストシナリオなどもあるようなので、あとで追ってみようと思います。

意訳 - Linux ate my RAM!

Linux に触り始めのころに陥りがち話を、以下のサイトが軽快に解説しているので意訳してみた。
Linux ate my RAM!
(尚、連絡先が不明で作者さんに未承諾)


Linux がメモリを食い尽くしてしまう!
落ち着け!メモリは問題ない!


なんでさ?
Linux は未使用のメモリをディスク・キャシュとして使用する。
これは一見、メモリを逼迫するかのようだが、
まったく問題なし!



なんでそんな事すんの?
ディスク・キャッシュはシステムの高速化に寄与するためである。
デメリットは無いのだが、よく混乱を招いている。
アプリケーションからメモリを奪うことは絶対にない!



たくさんのアプリを実行するんだけど?
もしたくさんのメモリが必要な場合、アプリはディスク・キャッシュから取り戻すだけだ。
ディスク・キャッシュは直ちにアプリへ還元される!



もっと swap が必要なんじゃ?
不要。
ディスク・キャッシュは、アプリが、その時点で不要なメモリを使用する。
swap は使わない。
もしアプリがより多くのメモリを必要とする場合、ディスク・キャッシュから取り戻す。
スワッピングは行わない。



どうすれば Linux でディスク・キャッシュを無効にできるの?
無効にすることはできない。
無効にする理由として、「アプリが利用できるメモリが少なくなってしまうから」と考えがちだけど、そんなことはない!
ディスク・キャッシュのおかげでアプリの起動は早くなるし、実行速度も向上する。
だから、無効にする理由なんて無いんだよ!



なんで top / free コマンドの結果で、全メモリが使用中(used)になるの?
単なる言い回しの勘違いによるもの。
君とLinuxは各々、「未使用なメモリを“free”」、「アプリが使用中のメモリを“used” 」と呼ぶ。
では、「“何か”に使われているが、アプリにも有効なメモリ」を何と呼ぶか?

おそらく君は “free” と呼び、Linux は “used” と呼ぶ。

メモリが… 君の呼び方 Linux の呼び方
アプリによって使用中 Used Used
“何か”に使われているが、アプリにも有効 Free Used
未使用 Free Free

上記の“何か”とは top/free コマンドで言う “buffer” と “cached” である。
君とLinux とで認識の違いがあるだけで、メモリが逼迫してるように見えて、実はそうではないんだ。



どうすれば実際にどれくらいの未使用メモリがあるか確認できるの?
アプリが使用可能な未使用メモリがどれくらいあるか確認するには、free -m を実行して、
得られた出力から “-/+ buffers/cache” 行の “free” の列に注目する。

ガバイトでの結果は以下のとおり:

$ free -m
             total       used       free     shared    buffers     cached
Mem:          1504       1491         13          0         91        764
-/+ buffers/cache:        635        869
Swap:         2047          6       2041
$

もしこの数字をどのように見れば判らない場合、メモリは99%フルと捉えてしまうが、実際には42%である。



どうやって裏付けすればいい?
ディスク・キャッシュについての詳細と、より深い知識については、このページを参考のこと。



(おわり。気が向いたら、リンク先のページも意訳してみよう)