getcpu_cacheシステムコール追加パッチを試してみました(前編: pwclientでパッチ適用まで)

今日(2/24)、LKMLで以下のパッチが流れていました。


「getcpu_cache()システムコールを追加する」パッチで、Linux 4.6に向けたものなので、
もう少し先のパッチです(現在は4.5のrc期間)。


まだ、パッチの中身を完全に理解したわけでは無いのですが、
調べたり、試してみたりしたことをまとめてみます。


今回やったことは、以下の3つです。
1. getcpu_cache()について、主にメール本文やコミットメッセージからだいたい理解
2. パッチを適用し、QEMU上でカーネルを動かしてみる
3. パッチに付属しているサンプルを動かしてみる(詳細は次回の記事)


1. getcpu_cache()について

そもそも、getcpu()というシステムコールがあり、
これは呼び出し元スレッドが動作しているCPUを判別するもので、
SMPやNUMAといったマルチコアのアーキテクチャのためのものです。


getcpu_cache()は、getcpu()にキャッシュ機構を追加して高速化しています。


上記のメールの本文に、
「ARM上での、このアプローチとシステムコールに基づくgetcpuとの比較は44倍の高速化を示した。
x86-64上において、glibcを介したvDSOからのlsl実行と比較して14倍の高速化を示した。」
と書かれていることから、キャッシュが効力を発揮する条件においては、
高速化が期待できるパッチのようです。


なお、man 2 getcpuを見ると、getcpu()自体にtcacheという引数があり、
2.6.24より以前では、この引数がNULL以外の場合はgetcpu()用のキャッシュ機構が動作するように実装されていた
とのことです。
そのため、過去に一度実装されていたものが、形を変えて再実装された様に見えます。

2. パッチを適用し、QEMU上でカーネルを動かしてみる

2.1. パッチの適用

LKML等のML上で流れているパッチは、patchwork.kernel.orgで管理されています。
そして、pwclientというコマンドライン上で動作するPythonスクリプトを公開しており、
これを使用すると、「パッチをダウンロードし、git amで適用」といった操作がコマンド一発で行えます。


手順は以下のとおりです。
なお、LinuxのGitのツリーをcloneする手順は省略します。
私は、Linusの木をcloneし、試してみました。


1. pwclientの準備
以下のページからpwclientをダウンロードし、PATHの通った場所に配置、実行権限を付与


また、pwclient実行の度にMLを指定しなくても良いように、以下のページからLKML用の.pwclientrcをダウンロードし、
ホームディレクトリに配置


2. パッチを適用したいLinuxカーネルのGitツリーにブランチを作成

$ git checkout -b add_getcpu_cache


3. 適用したいパッチのPatchwork上のIDを確認

$ pwclient list getcpu_cache
ID      State        Name
--      -----        ----
7952981 New          [RFC,1/3] getcpu_cache system call: cache CPU number of running thread
7953001 New          [RFC,2/3] getcpu_cache: wire up ARM system call
7952991 New          [RFC,3/3] getcpu_cache: wire up x86 32/64 system call
8135861 New          [RFC,v2,1/3] getcpu_cache system call: cache CPU number of running thread
8135881 New          [RFC,v2,2/3] getcpu_cache: wire up ARM system call
8135871 New          [RFC,v2,3/3] getcpu_cache: wire up x86 32/64 system call
8153941 New          [RFC,v3,1/5] getcpu_cache system call: cache CPU number of running thread
8153901 New          [RFC,v3,2/5] getcpu_cache: ARM resume notifier
8153931 New          [RFC,v3,3/5] getcpu_cache: wire up ARM system call
8153911 New          [RFC,v3,4/5] getcpu_cache: x86 32/64 resume notifier
8153921 New          [RFC,v3,5/5] getcpu_cache: wire up x86 32/64 system call
8154741 New          [RFC,v3,1/5] getcpu_cache system call: cache CPU number of running thread
8164781 New          [RFC,v3,1/5] getcpu_cache system call: cache CPU number of running thread
8397411 New          [v4,1/5] getcpu_cache system call: cache CPU number of running thread
8397451 New          [v4,2/5] getcpu_cache: ARM resume notifier
8397421 New          [v4,3/5] getcpu_cache: wire up ARM system call
8397431 New          [v4,4/5] getcpu_cache: x86 32/64 resume notifier
8397441 New          [v4,5/5] getcpu_cache: wire up x86 32/64 system call
8397691 New          [v4,(updated)] getcpu_cache: wire up ARM system call

上記のコマンドでは、"getcpu_cache"でpatchwork.kernel.org内を検索し、結果をリスト表示しています。


"v4"が一番新しく、
> 8397421 New [v4,3/5] getcpu_cache: wire up ARM system call
については、
> 8397691 New [v4,(updated)] getcpu_cache: wire up ARM system call
の更新版が出ているので、今回は以下の5つのパッチを適用してみます。
> 8397411 New [v4,1/5] getcpu_cache system call: cache CPU number of running thread
> 8397451 New [v4,2/5] getcpu_cache: ARM resume notifier
> 8397691 New [v4,(updated)] getcpu_cache: wire up ARM system call
> 8397431 New [v4,4/5] getcpu_cache: x86 32/64 resume notifier
> 8397441 New [v4,5/5] getcpu_cache: wire up x86 32/64 system call


4. パッチを適用
pwclient git-am ID
で、「パッチをダウンロードし、git am」を一括で行ってくれます。

今回の場合、以下の5つのコマンドを実行しました。

$ pwclient git-am 8397411
$ pwclient git-am 8397451
$ pwclient git-am 8397691
$ pwclient git-am 8397431
$ pwclient git-am 8397441

> 62dda9b getcpu_cache: wire up x86 32/64 system call
がHEADである時点のLinusツリーにおいては、コンフリクト無く適用できました。


なお、pwclientについて詳しくは、
pwclientをオプション無しで実行すると表示されるUsageを見てみてください。

2.2. カーネルのビルド

今回は、x86_64_defconfig のデフォルトコンフィギュレーションでビルドしてみます。
また、今回は、カーネルイメージ(bzImage)と初期RAMディスク(initrd)のみで、簡易的に動作確認してみますので、
上記ではbzImageターゲットを指定しています。(initrdはPC上(Debian Jessie)のものを流用します。)

$ make x86_64_defconfig
$ make -j4 bzImage

ビルドが完了すると、arch/x86/boot/bzImage にカーネルイメージが生成されます。


加えて、以降でサンプルをビルドするときのために、
カーネルのヘッダーファイルを適当なディレクトリに生成しておきます。

$ mkdir ~/test_linux_headers
$ make headers_install INSTALL_HDR_PATH=~/test_linux_headers
2.3. QEMUで起動してみる

以下のコマンドで、先ほどビルドしたカーネルとinitrdを使用してQEMU上で起動してみることができます。
"-kernel"オプションと"-initrd"オプションは、自身の環境に合わせて書き換えてください。

$ qemu-system-x86_64 -kernel ~/git/linux/arch/x86/boot/bzImage -initrd /boot/initrd.img-4.4.0 -append "root=/dev/ram rdinit=/bin/sh"

QEMUのウィンドウが立ち上がり、カーネルのブートログが表示された後、
Enterキーを押下すると、"\ #"というプロンプトが表示され、コマンド実行ができるようになります。


なお、以下の例のように実行することで、QEMUのウィンドウを立ち上げずに、シェル上で実行できます。

QEMUのオプションでcursesの使用を指定する例

qemu-system-x86_64 -kernel ~/git/linux/arch/x86/boot/bzImage -initrd /boot/initrd.img-4.4.0 -curses -append "root=/dev/ram rdinit=/bin/sh"

カーネルのブートパラメータでttyS0をコンソールとする例

$ qemu-system-x86_64 -kernel ~/git/linux/arch/x86/boot/bzImage -initrd /boot/initrd.img-4.4.0 -nographic -append "console=ttyS0 root=/dev/ram rdinit=/bin/sh"

つづく

思ったより記事を書くのに時間がかかってしまったので、
サンプルの実行については、また次回に。


なお、簡単に紹介すると、
実行するサンプルは以下のメールにあるものです。


また、サンプル実行でやったこととしては、以下のとおりです。
1. ビルド
2. initrdを展開、ビルドしたサンプルを追加し、再度initrd生成
3. 生成したinitrdを使用してQEMU上でカーネルブート
4. サンプル動作確認