getcpu_cacheシステムコール追加パッチを試してみました(後編: サンプルを動かしてみた)
昨日(2/24)の記事の続きです。
2/24に、LKMLで流れていた以下のパッチについて、
昨日(2/24)は、以下の1.と2.まで紹介しました。
1. getcpu_cache()について、主にメール本文やコミットメッセージからだいたい理解
2. パッチを適用し、QEMU上でカーネルを動かしてみる
3. パッチに付属しているサンプルを動かしてみる
今日は、3.についてまとめておきます。
3. サンプルを動かしてみる
3.1. サンプルについて
ひとつ目のパッチのメールに、man 2 getcpu_cacheに記載する内容や、
サンプルプログラムなども記載されています。
サンプルはgetcpu_cache()で現在のCPU番号を取得し、printf()で出力するものです。
サンプル(メール本文での説明では例(EXAMPLE))について、メールでは以下のように説明されています。
以下のコードでは、スレッドのローカル変数に現在のCPU番号を保持させるように getcpu_cache()システムコールを実行している。なお、キャッシュが無効の際は、 sched_getpu(3)を使用する。簡単のために、main()内で完結している。しかし、 マルチスレッドプログラムでは、各プログラムスレッドからgetcpu_cache()を呼び出す 必要がある。
サンプルのコード量は50行程度と短いので、ここで引用して、少し説明してみます。
冒頭のincludeを除くと、コードブロックとしては3つです。
まず1つ目のコードブロックを見てみます。
static inline int getcpu_cache(int cmd, volatile int32_t **cpu_cachep, int flags) { return syscall(__NR_getcpu_cache, cmd, cpu_cachep, flags); }
ここではgetcpu_cacheシステムコールのsyscall()での呼び出しを、
getcpu_cacheという関数でラップしています。
getcpu_cache関数の引数(=getcpu_cacheシステムコールの引数)について、
第1引数"cmd"は操作を設定する引数で、以下の定数のいずれかを設定します。
- GETCPU_CACHE_GET
- cpu_cachepにより指定されたメモリ領域の中で、現在のCPU番号キャッシュのアドレスを取得
- GETCPU_CACHE_SET
- cpu_cachepにより指定されたメモリ領域中のアドレスを使用して、現在のCPU番号キャッシュの設定を試みる
- このアドレスは4バイトアラインでなければならない(自然なアライメント)
また、第2引数"cpu_cachep"は、CPU番号キャッシュのためのメモリ領域のアドレスを指定します。
第3引数"flags"など、getcpu_cacheシステムコールのその他の詳細は、1つ目のパッチのメールに記載されている
man 2 getcpu_cacheの内容を参照してください。
(私自身、上記で説明した箇所以降を、まだ読んでいません。。。)
次に2つ目のコードブロックを見てみます。
/* * __getcpu_cache_tls is recommended as symbol name for the * cpu number cache. Weak attribute is recommended when * declaring this variable in libraries. Applications can * choose to define their own version of this symbol without * the weak attribute and access it directly as a * performance improvement when it matches the address * returned by GETCPU_CACHE_GET. The initial value "-1" * will be read in case the getcpu cache is not available. */ __thread __attribute__((weak)) volatile int32_t __getcpu_cache_tls = -1;
ここでは、CPU番号をキャッシュするための変数を定義しています。
なお、変数名(=シンボル名)は"__getcpu_cache_tls"とすべきで、
アプリケーションからオーバーライドできるよう、
ライブラリで定義する際はweak属性をつけるべき、とのことです。
コメントには以下のように書かれています。
CPU番号キャッシュとして、__getcpu_cache_tlsというシンボル名を推奨する。 また、この変数をライブラリで定義する時、Weak属性を推奨する。 これにより、アプリケーション側でWeak属性無しで変数を定義することで、 アプリケーションは自身でこの変数を定義できる。 またアプリケーションは、性能向上として、このシンボルがGETCPU_CACHE_GETにより 返されるアドレスと一致する時、CPU番号キャッシュ(今回の場合、__getcpu_cache_tls変数)へ 直接アクセスできる。 初期値"-1"は、getcpuキャッシュが無効な場合に読み出される。
最後に、3つ目のコードブロックを見てみます。
int main(int argc, char **argv) { volatile int32_t *cpu_cache = &__getcpu_cache_tls; int32_t cpu; /* Try to register the CPU cache. */ if (getcpu_cache(GETCPU_CACHE_SET, &cpu_cache, 0) < 0) { perror("getcpu_cache set"); fprintf(stderr, "Using sched_getcpu() as fallback.\n"); } cpu = __getcpu_cache_tls; /* Read current CPU number. */ if (cpu < 0) { /* Fallback on sched_getcpu(). */ cpu = sched_getcpu(); } printf("Current CPU number: %d\n", cpu); exit(EXIT_SUCCESS); }
ここが処理の本丸です。
"Try to register the CPU cache."の箇所で、getcpu_cache()を使用して
CPU番号キャッシュ用のアドレスを登録しています。
そして、"Read current CPU number."の箇所で現在のCPU番号を読み出しています。
CPU番号キャッシュ用のアドレスは、main()からアクセス可能なアドレス(単なるグローバル変数)なので、
CPU番号キャッシュを読み出す際に、getcpu_cacheのGETCPU_CACHE_GETは使用せず、
変数から直接読み出しています。
また、__getcpu_cache_tlsから読みだした結果が0より小さい、すなわち初期値"-1"のままだった場合、
getcpu_cacheが動作していないので、従来からあるsched_getcpu()でCPU番号を読み出しています。
3.2. サンプルのビルドとinitrd.imgへの組み込み
ビルドは以下のように行います。
なお、サンプルは"sample_getcpu_cache.c"というファイル名で保存されているものとします。
$ gcc -Wall -Wextra -I../test_linux_headers/include -o sample_getcpu_cache sample_getcpu_cache.c
※ "../test_linux_headers/include"の箇所は、「2.2. カーネルのビルド」で
ヘッダーファイルを生成した場所のincludeディレクトリを指定してください。
そして、initrd.imgへ組み込むために、initrd.imgを展開します。
$ mkdir ~/work # 作業場所を作成し、 $ cd ~/work/ # 移動 $ cp /boot/initrd.img-4.4.0 . # 作業場所へコピー $ gunzip -c initrd.img-4.4.0 | cpio -i # initrd.imgを展開 $ rm initrd.img-4.4.0 # 展開した元のファイルを削除
展開したinitrdへ、先ほどビルドしたサンプルの実行バイナリ(sample_getcpu_cache)をコピーします。
$ cp ../sample_getcpu_cache bin/ # サンプルの実行バイナリをbin/ディレクトリへコピー
最後に、initrd.img-4.4.1というファイル名でイメージを再度生成します。
$ find . | cpio -o -H newc | gzip -c > initrd.img-4.4.1 # initrd.img-4.4.1イメージを作成
3.3. サンプルの実行
サンプルを実行する際には、QEMUでマルチコアのオプションを指定してください。
(でないと、sample_getcpu_cacheでは常にCPU番号"0"が読み出されてしまい、面白くありません。)
例えば、-smpで4コアを指定してみます。
qemu-system-x86_64 -smp 4 -kernel ~/git/linux/arch/x86/boot/bzImage -initrd ~/work/initrd.img-4.4.1 -nographic -append "console=ttyS0 root=/dev/ram rdinit=/bin/sh"
sample_getcpu_cacheを実行してみると、
以下のように、CPU番号がその都度取得できていることがわかります。
/ # sample_getcpu_cache Current CPU number: 2 / # sample_getcpu_cache Current CPU number: 1 / # sample_getcpu_cache Current CPU number: 3 / # sample_getcpu_cache Current CPU number: 1 / # sample_getcpu_cache Current CPU number: 2 / #