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
/ #