QEMU i.MX6 (Sabre Lite) で TOPPERS/FMP を動かす
QEMU の開発リポジトリでは i.MX6 のエミュレーションがマージされていて、以外にも(?)サクッと動いたので、その手順を記録しておく。
ホスト環境
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 を立ち上げて、プロジェクトの新規作成を開始する。
RTL プロジェクトを選択。
次の"Add Sources" と "Add Existing IP" ウィザードではは何も追加せずそのまま Next で進み、その後の "Add Constraint" では、下記のように予めダウンロードしていた ZYBO_Master.xdc を指定する。また、下部の"Copy ..." にチェックを入れて、プロジェクトに xdc ファイルを取り込むようにする(後で編集するため)。
Part の選択で ZYBO に乗っている Zynq を指定する。下記の設定でフィルタした一覧から、"xc7z010..." を選択。(現バージョンでは、Select: Boards で ZYBO が出てこない)
これでプロジェクト新規作成ウィザードが終了し、Vivado のメインウィンドウに移る。
回路の作成
さっそく回路の作成を行うため、"Create Block Design" を実行する(実行後、Design 名の入力を求められるので、zybo とか適当に設定する)。
まず、ZYNQ を追加する。Diagram ウィンドウで "Add IP" を実行。
追加できる IP 一覧が表示されるため、Search ボックスに zynq と入力して、"ZYNQ7 Processing System" を選択する。
さらに、ZYNQ と同じ要領で、外部 GPIO である "AXI GPIO" という IP を、プッシュボタンとLED用に2つ追加する("gpio" で検索すれば出てくる)。
これで手動による IP の追加が終わったので、"Diagram" ウィンドウ上部に表示されている "Run Block Automation"と"Run Connection Autmation" を実行する。
Run Block Autmation は手動で追加した IP を扱うために必要な他の IP を自動で追加してくれる(リセット回路など)。今回は何も変更せずそのまま OK ボタンで完了。
Run Connectoin Automation は追加した IP の配線を自動で行ってくれる。 今回は以下のように "All Automation" にチェックを入れて OK ボタンを押す。
この時点の回路図は以下のようになる。Diagram ウィンドウ左下の"Regenerate Layout"(リピートマークのようなボタン)を実行すると、レイアウトを整理してくれる。
IP のカスタマイズ (割り込みの設定)
引き続き、各 IP のカスタマイズを行っていく。まず、ZYNQ。Diagram ウィンドウ上の ZYNQ をダブルクリックして以下のウィンドウを開く。 上部の"Import XPS Setting" を押し、予め Digilentinc のサイトから ダウンロードしておいた、ZYBO_def.xml を設定する。これによって、ZYBO 向けの基本的な設定がインポートされる。
そして、今回の実験用のカスタマイズである外部 GPIO 割り込みの設定を行う。 左部の"Interrupts"から割り込み設定を開き、"PL-PS Interrupts" の "IRQ_F2P[15:0]" にチェックを入れる。
ZYNQ に対する設定は以上で終了。
次に、外部 GPIO のカスタマイズ。まず、プッシュボタン用の設定を行う。 Diagramウィンドウから、2つある外部 GPIO のどちらかをダブルクリックして以下の設定を行う。
デフォルト設定から、
- "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 で接続する。(接続可能な端子をマウスオーバーするとマウスポインタが鉛筆マークになる)
最後に GPIO 端子の設定を行う。まず、端子の名称を変更。 Diagram ウィンドウ上で GPIO 端子(デフォルトでは gpio_rtl ...)をダブルクリックすると "External Interface Property" というウィンドウが現れるため、そこの Name から変更する。 今回は、
- プッシュボタン: btn_4bits
- LED: led_4bits
とした。端子名称変更は必須ではないが、わかりやすさのために行った。 次いで、Block Design の Souces タブから、"Constraints"-> "constrs_1" -> "ZYBO_Master.xdc" をダブルクリックで開く。ファイルの内容は全てコメントになっており、必要な設定のみコメントを外して設定を有効にする。 今回はプッシュボタンと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 を生成する。
そしていよいよ、FPGA にロードするための回路データ(bit ファイル)を生成する。Vivado メインウィンドウ左下の "Generate Bitstream" を押すだけ。
生成完了までに数分かかる("Window" -> "Design Runs" ウィンドウで進捗状況がわかる)。
Bitstream の生成が完了すると、次の工程を促されるが、今回は必要ないため、Cancel で終了。
仕上げに、"File" -> "Export" -> "Export Hardware" を実行して、H/W 情報を SDK 用にエクスポート(後編で使用)。
"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(®, 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 ...(以下省)...
- マウスやキーボードを適当に操作すると、ログが出力されることを確認(環境は VirtualBox)
- プローブポイントは input_event 関数(inputサブシステムのI/F)とし、以下のコードを
便利な timer
- 特定のプローブポイントでの駆動ではなく、インターバルタイマによる駆動も可能
- 例) 10ミリ秒毎にカレント・プロセスの名前を表示
# stap -e 'probe timer.ms(10) { task = task_execname(task_current()) if ("swapper/0" != task) println(task) }'
- "swapper/0" は頻度が高いため除外している
ms
のほか、s
、us
、ns
、hz
、jiffies
がある
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 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%である。
どうやって裏付けすればいい?
ディスク・キャッシュについての詳細と、より深い知識については、このページを参考のこと。
(おわり。気が向いたら、リンク先のページも意訳してみよう)