この記事はLinux Advent Calendar 2014の25日目ですヽ(=´▽`=)ノ
今回はLinux Kernel Hack入門編ということで入門的なことを書いてみたいと思います。
まず使用する環境ですけど最近出たばっかのFedora 21のWorkstationにしました。まあ、今回の内容的にはディストリビューションは問わないんですが、多くの人が馴染んでいるであろうfedora系というかパッケージマネージャがyumということでこれにしてみました。 自分は普段Arch Linuxなんですけど、こっちだとkernelのmake install時にちょっとしたスクリプトを書く必要があったりするのもあって、fedoraのほうが手軽かなというのもあります。
カーネルコード・リーディング
ブラウザベースでコード・リーディング
読めるソースは大概メインラインのカーネルということになりますが、通常はこれで事足りると思います。自分で何かを設定しなくても良いのでお手軽です。ブラウザで見る場合、free-electrons.comさんのlxrが便利です。
あと、忘れちゃいけないのがgithubですね。githubの場合、タグジャンプはできませんがblameはwebインターフェースでできるので
実装がどうなっているのかを見て行きたい場合はlxr、なんでこういう処理なのか?とか変数の由来等々を知りたい場合はblameで歴史を遡るのが良いかと思います。
エディタでコード・リーディング
エディタはお好みのものを使ってください。エディタで見る場合はctagsなどのツールが便利ですよね。Linuxの場合、makeのターゲットにタグを作るターゲットがあります。make helpの出力の抜粋ですが以下の3個が選べます。
tags/TAGS - Generate tags file for editors cscope - Generate cscope index gtags - Generate GNU GLOBAL index
自分でタグを作る利点はネットワークunreachableな環境でも使える、任意のバージョンのソースに対してタグをつけれるので例えばtipツリーとかメインライン以外のコード・リーディングもしやすくなるというのがありますね。 ソースをgitでcloneしていれば当然blameとかも使い放題です。
システムコールの探しかた
システムコールは基本的にはsys_foobarという感じでsys_が付くのですが、ソースコード上はこうなってません。大概はSYSCALL_DEFINE[0-6]というマクロが使われています。最後の数字は引数の数です。
例えばgetpid()はこうなります。
1084 SYSCALL_DEFINE0(getpid) 1085 { 1086 return task_tgid_vnr(current); 1087 }
なのでlxrでもgrepでも良いのですが、システムコールを探す場合はSYSCALL_DEFINEを使った形、例えばgetpid()なら、SYSCALL_DEFINE0(getpitで探すと見つけやすいです。引数の数はユーザーランド側での引数の数と一緒なのでmanを見ればわかります。あとはsocket関連のシステムコールはsocketcall(2)、ipc関連はipc(2)という風に別のシステムコールにまとめられる場合があるのでその辺りもお忘れなく。
カーネルの機能を知る
だいたい下記のどちらかだと思います。
- 特定の機能について知りたい場合
- 全般的に知りたい場合
前者の場合は目的が明確なのでそれらしい単語をググる等で読むべきコードが大体分かるんじゃないかと。
どこから手を付けたら良いのかわからないというのは後者の場合ですね。とりあえずお勧めしないのはbootから読んでいくパターンです。なんでかというと、boot処理というのは色々なものを初期化していくのでそこでやっていることを知ろうと思ったら、それらについてある程度は知らないと何をやってるのか理解しにくいからですね。ある機能があって、それが初期設定をどうやっているかを調べるのにbootプロセスを見るのは良いんですが、全体を知るには向いていないと思います。
他には、初期化と初期化後のデータを使うのは別のタイミングなのでbootだけみてもその機能はよくわからないというか、使っているところこそが大事ですよね、ってのもあります。これはカーネルじゃなくても一緒だと思います。
で、では全般的に知りたい場合はどうするかってなるとソースを読むよりも本を読んだほうが良いと思います。日本語で読めて全般的に解説しているとなると以下の2冊ですね。
これらの本は全般的に解説しているのでLinuxカーネルが持っているオペレーティングシステムとしての基本的な機能については一通り学べると思います。ただ、発売から結構経っているため今のカーネルとあわない部分というのも当然ありますが。。それでも基本を知るという意味では良いかと思います。
webでの情報源としてはLinux Weekly Netですね。これは最新の状況を知るのに持って来いだと思います。
カーネルビルド
パッケージインストール
Fedora 21のWorkstationを入れるとgitなんかはすでに入っていたりしますが、gccなんかは入っていないのでまずは必要なパッケージをインストールします。この辺を入れておけば追加で依存パッケージも入ります。
[masami@fedora ~]$ sudo yum install ncurses-devel gcc patch ccache
gccとpatchは説明不要だと思いますがncurses-develはカーネルの設定をするときにmake menuconfigで設定したいのでncurses-develを入れます。他のディストリビューションの場合はncursesのヘッダファイルとかがあるパッケージを入れてください。ccacheはビルドしたobjectファイルをキャッシュしておいて次回移行のビルドを速くしたいというところです。
kernel sourceのダウンロード
カーネルのソースコードはgitでcloneする、tar.xzで一式ダウンロード、fedoraのsrc.rpmパッケージを使うという手がありますが、gitでcloneするのが一番手軽じゃないでしょうか。ということで、cloneします。
[masami@fedora ~]$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-kernel
cloneが終わったらブランチ切ってそこでビルドしましょう。カーネルのバージョンは3.18を使ってみたいと思います。
[masami@fedora linux-kernel]$ git checkout -b mykernel v3.18
カーネルのコンフィグ
次にカーネルの設定をしたいと思います。ここでどんな機能を有効にするか、設定値をどうするかってことをしていきますが、今回は今読み込まれているモジュールをベースとしたconfigをしたいと思います。
この場合のターゲットはlocalmodconfigです。
[masami@fedora linux-kernel]$ make localmodconfig
この後色々と機能をどうするか聞かれてきますが全部Enterキー押下で飛ばしてしまいましょう。 これで最小限の設定になっていると思いますが、さらに微調整をしたいので次はmenuconfigを行います。menuconfigの場合、機能menu項目の移動は上下、select、exitなどの下にあるメニューは左右キーで移動できます。決定はEnterキーです。
まずは、General setup -> Local Versionを選択します。
そうしたら適当な名前をつけましょう。OKを押してメニューに戻るとこうなるはずです。
そしたらExitを押してGeneral setupを抜け、もう一度Exitを押します。そうするとsaveするか聞かれるのでyesを選択して完了です。
makeのターゲットについてはLinux Advent Calendar 2014の7日目、satoru_takeuchiさんの「linux kernelのmakeターゲットについてのあれこれ」が参考になります。
make
configが終わったらビルドです。makeのターゲットはbzImageです。makeは並列に行いたいので-Jオプションをつけましょう。 使用可能なcore数が4なので-jに4を渡しています。ccacheを忘れずに。
[masami@fedora linux-kernel]$ nproc 4 [masami@fedora linux-kernel]$ ccache make -j4 bzImage
最後にこんな出力が出てれば無事にmake完了です。
OBJCOPY arch/x86/boot/setup.bin BUILD arch/x86/boot/bzImage Setup is 16124 bytes (padded to 16384 bytes). System is 5545 kB CRC 318250a6 Kernel: arch/x86/boot/bzImage is ready (#1)
bzImageが出来たら次はmoduleをビルドします。
[masami@fedora linux-kernel]$ ccache make -j4 modules
これも何事もなくシェルのプロンプトに戻っていれば完了です。
install
次にカーネルモジュールを/lib/にインストールします。これはroot権限が必要です。
[masami@fedora linux-kernel]$ sudo make modules_install
この後に、 /lib/modulesに今作った3.18mykenelがありますね。ここまできて思い出したんですが、Local Versionを設定するときに先頭の文字は"-"にしといたほうが綺麗に見えると思います。3.18.0-mykernelのほうが見やすいと思うので。
[masami@fedora linux-kernel]$ ls /lib/modules 3.17.4-301.fc21.x86_64 3.18.0mykernel
次に installターゲットで/bootにカーネルの配置、initramfsの作成をします。
[masami@fedora linux-kernel]$ sudo make install
ちなみに、installの実行時に/sbin/installkernelというコマンドが呼ばれ、これが/bootにファイルを置いたりするのですがディストリビューションによってはこのコマンドありません、例えばArch Linux。このような場合、自分で/sbin/installkernelを作ったほうが早いです。この辺の説明は以前のエントリ「φ(.. )メモシテオコウ /sbin/installkernelを適当に作っておく - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ」を参考にしてください。
make installも何事もなくシェルのプロンプトに戻っていれば完了です。
最後にgrubのconfigを更新して再起動します。
[masami@fedora linux-kernel]$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg
ログインしたらカーネルのバージョンを確認していましょう。
[masami@fedora ~]$ uname -a Linux fedora 3.18.0mykernel #1 SMP Fri Dec 19 23:19:01 JST 2014 x86_64 x86_64 x86_64 GNU/Linux
新しいカーネルで起動していますね。これでカーネルのビルドは完了です。
カーネルモジュール
hello world位作るのがお約束な気がしたので・・・
module_init()でモジュールロード時のinit処理、module_exit()でモジュールアンロード時のクリーンアップ処理を登録します。先頭のKBUILD_MODNAMEはprintk()で自分のモジュール名を出したいときに使う技です。pr_infoはprintk(KERN_INFOのシンタックスシュガーです。以下のコードをhelloworld.cとして保存します。
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/kernel.h> MODULE_DESCRIPTION("Hello, World"); MODULE_AUTHOR("masami256"); MODULE_LICENSE("GPL"); static int helloworld_init(void) { pr_info("%s\n", __func__); return 0; } static void helloworld_cleanup(void) { pr_info("%s\n", __func__); } module_init(helloworld_init); module_exit(helloworld_cleanup);
Makefileはこちらです。
KERNDIR := /lib/modules/`uname -r`/build BUILD_DIR := $(shell pwd) VERBOSE = 0 obj-m := helloworld.o smallmod-objs := helloworld.o all: make -C $(KERNDIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules clean: rm -f *.o rm -f *.ko rm -f *.mod.c rm -f *~
これでmakeして作成されたhelloworld.koをinsmod、rmmodしてからjournalctl -r -bでカーネルのログを確認すると以下のようにログが出ているのが確認できます。
-- Logs begin at Fri 2014-12-19 21:36:25 JST, end at Fri 2014-12-19 23:43:47 JST. -- Dec 19 23:43:47 fedora kernel: helloworld: helloworld_cleanup Dec 19 23:43:47 fedora sudo[2471]: masami : TTY=pts/0 ; PWD=/home/masami/helloworld ; USER=root ; COMMAND=/sbin/rmmod helloworld Dec 19 23:43:23 fedora kernel: helloworld: helloworld_init Dec 19 23:43:23 fedora kernel: helloworld: module verification failed: signature and/or required key missing - tainting kernel
カーネルデバッグ
では、最後にデバッグを。まず最初にカーネルのconfigを変えます。make menuconfigでKernel hacking -> Memory debuggingを選択し、Kernel memory leak detectorを有効に、下のMaximum〜を4096にしたら保存してカーネル・モジュールをビルドしてインストールします。
再起動してdmesgでログを確認すると先ほど有効にしたkmemleakが初期化して開始したのがわかります。
[masami@fedora ~]$ dmesg | grep kmemleak [ 7.004780] kmemleak: Kernel memory leak detector initialized [ 7.004790] kmemleak: Automatic memory scanning thread started
次に、先ほど作成したhelloworld.cにバグを埋め込みます。
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> MODULE_DESCRIPTION("Hello, World"); MODULE_AUTHOR("masami256"); MODULE_LICENSE("GPL"); static char *ptr; #define LEAK_DETECT_TEST() do { \ ptr = kmalloc(4, GFP_KERNEL); \ pr_info("ptr is 0x%p\n", ptr); \ } while(0) static int helloworld_init(void) { pr_info("%s\n", __func__); LEAK_DETECT_TEST(); LEAK_DETECT_TEST(); return 0; } static void helloworld_cleanup(void) { pr_info("%s\n", __func__); kfree(ptr); } module_init(helloworld_init); module_exit(helloworld_cleanup);
makeしてinsmod・rmmodするとこうのようにログがでます。
[ 340.021392] helloworld: helloworld_init [ 340.021399] helloworld: ptr is 0xffff8802286da788 [ 340.021403] helloworld: ptr is 0xffff8802286da790 [ 349.762415] helloworld: helloworld_cleanup
kmemleakで即時scanで確認してみます。
[root@fedora helloworld]# echo scan > /sys/kernel/debug/kmemleak
そして、ログを見るとこのようにメモリーリークが検出されます。もし一回やってログが出なかった再度試してみてください。
[ 479.691172] kmemleak: 1 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
指示にしたがって/sys/kernel/debug/kmemleakを見てみます。memory leakしたであろうオブジェクトのアドレスは0xffff8802286da788と出ています。サイズが8なのはkmallocの最低サイズが切り上げられているためです。変数は初期化していないのでhex dumpの中身は今回は無意味ですね。commはプログラム名、pidはcommのpidです。メモリーリークの場合、メモリ確保のタイミングとリークのタイミングが違うのでバックとレースだけだと結構追うの大変ですがヒントとしてはでかいので重要です。
[root@fedora helloworld]# cat /sys/kernel/debug/kmemleak unreferenced object 0xffff8802286da788 (size 8): comm "insmod", pid 1934, jiffies 4295007317 (age 212.238s) hex dump (first 8 bytes): 90 a7 6d 28 02 88 ff ff ..m(.... backtrace: [<ffffffff8173863e>] kmemleak_alloc+0x4e/0xc0 [<ffffffff811f042c>] kmem_cache_alloc_trace+0x13c/0x250 [<ffffffffa033d034>] 0xffffffffa033d034 [<ffffffff81002148>] do_one_initcall+0xd8/0x210 [<ffffffff81119b1d>] load_module+0x209d/0x27c0 [<ffffffff8111a406>] SyS_finit_module+0xa6/0xe0 [<ffffffff81746d69>] system_call_fastpath+0x12/0x17 [<ffffffffffffffff>] 0xffffffffffffffff
とまあ、こんな感じでサクッとまとめてみました。入門の取っ掛かりになれば幸いですm(__)m
宣伝ですw 月一回ペースで秋葉原でテーマをLinuxカーネルに絞ったもくもく会をやってますので、興味のある方はどうぞ。
最後に。これでLinux Advent Calendar 2014も終わりになります。
記事を投稿してくれたknokさん、satoru_takeuchiさん、mzyy94さん、hiraku_wfsさん、furandon_pigさん、tenforwardさん、xoxyuxuさん、akachochinさん、yunon_physさん、shigemk2さん,
そして読んでくれた皆さん、ありがとうございましたm(__)m
これ一冊で完全理解 Linuxカーネル超入門(日経BP Next ICT選書)
- 作者: 日経Linux
- 出版社/メーカー: 日経BP社
- 発売日: 2014/10/16
- メディア: Kindle版
- この商品を含むブログを見る