執筆者 : 小田 逸郎
※ 「新Linuxカーネル解読室落穂拾い」連載記事一覧はこちら
はじめに
OS徒然草のネタもなくなってきたということで、新しいシリーズを始めることにしました。徒然草の方は、OS一般の話題でしたが、今度は、Linuxに限定した話をしていきます。筆者も昔は、Linuxの障害解析などを実施したりして、Linuxのコードをバリバリ読んでいた時期もあるのですが、最近はとんとご無沙汰で、知らないうちになんだかいろいろな機能が追加されていたりとか、コードの大きな修正が入っていたりとかしているようです。そんな訳で、筆者もあらためてLinuxの勉強をし直そうじゃないかと考えました。もちろん、弊社の若手達が新解読室プロジェクトを始めたことに刺激を受けたというのもあります。
ただし、新しい機能についてどしどし説明しようというのが、本シリーズの主旨ではありません。新解読室の記事は大変参考になって、筆者にも大いに勉強になっていますが、これから、Linuxの解析にチャレンジしようという方にとっては若干敷居が高いのでないかと思います。そこで、本シリーズでは、これから、Linuxのコード解析にチャレンジしようという方に役に立つ情報を出していきたいと考えています。筆者はキャリアが長くなりすぎて、最早、初心者にとって何が分からないかということが分からなくなってしまっていますが、一応、これから初めてLinuxに触れる気持ちになって、ネタを考えて行きたいと思います。新解読室を読んで、これはちょっと説明が必要じゃないかな、と思ったことも取り上げるつもりですので、それにちなんで、「落穂拾い」とタイトルを付けてみました。Linuxコード解析界(?)の裾野が広がればいいなと思っています*1。新解読室の方は、純粋にコードの説明だけですが、本ブログの方は、OS徒然草ほどではないですが、適当に無駄話も交えていくつもりです。
コードの取得
そこまで最初からかい、と思われたかもしれませんが、コードがないことには解析できませんもんね。コードは以下のURLから取得できます。
このURLにアクセスすると、以下のようなページが出ます。
黄色い「Latest Release」と書いている大きなボタンをポチリとすると、コード(tarball)が取得できます。これは、stableリリースの最新版になります。上図の例だと、https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.14.3.tar.xz からダウンロードされますが、上のスクショを取ったときの最新ですので、皆さんがアクセスされたときには更新されていると思います。「linux-6.14.3.tar.xz」のバージョンのところを変えれば、任意のバージョンが取得できます。
リリース種別については、上の「Releases」に解説があります。「pgp」については、上の「Signatures」に解説があります。「patch」、「changelog」は、何が変更されたのか調べるときに使いますが、上級者向けですね。「patch」「inc.patch」で前版から更新することもできますが、全体を取り直した方が手っ取り早いと思います。「browse」で、コードツリーが参照できます。試しに「stable 6.14.3」の「browse」をクリックしてみると、以下のページに飛びます。
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/?h=v6.14.3
「h=」のところを変えれば、任意のバージョンのが見れます。コードツリーは、ブログ等でコードの参照箇所を示すのに使えます。例えば、schedule()、という感じです。
tarballを取得したら、tarコマンドで展開します。
$ tar xf linux-6.14.3.tar.xz
ところで、最近気が付いたのですが、tarで圧縮形式のオプションを指定する必要がなくなったのですね。以前ですと、「xz」のオプションなんだったんだよ、と調べる必要があったのですが、それがなくなって楽になりました。と言うか、もっと前からサポートしてよ、と思いましたけど。ちゃんと中身を見て判断しているみたいですね*2。
オリジナルコードの取得は上記のとおりですが、ディストリビューションで使われているコードを取得したい場合があるかもしれません。それについては各ディストリビューションのサイトで確認してください*3。Linuxのライセンスは、GPLなので、取得はできるはずです。大抵は、オリジナルコードとパッチの形になっていると思います。基本的には、ほとんどオリジナルと変わらないはずですが、インシデント解析では正確に同じコードを見るべきなので、取得が必須となります。
コードの参照には、以下のサイトが便利で、筆者はよく参照しています。
https://elixir.bootlin.com/linux/v6.14.3/source
上記のバージョンはたまたま*4ですが、左のタブで任意のバージョンを選択できます。シンボルをクリックするとクロスリファレンスが出てきたりして便利です。ちなみにLinux以外にもいろいろなOSS(ex. dpdk、xen)のコードが参照できます。左のタブの一番上の「linux」のところをクリックすると分かります。
ドキュメント
Linuxほど世の中で広く使われていて、コントリビュータも多ければ、さぞかし立派なドキュメントがあるだろうと思われるかもしれませんが、まあ、そんなことはありません。とは言え、ドキュメントはありますし、参考になることもあるので、一応、押さえておきましょう。
前記のトップページの下の方に「Other resources」という箱があって、その中に「Documentation」というリンクがあるので、クリックしてみましょう。そうすると、Linuxのドキュメントが参照できます。これは、コードの「Documentation/」ディレクトリの下のrstファイルをhtmlに変換したものです。これは、最新版のものになりますが、特定のバージョンのものを見たい場合は、以下のURLに各バージョンのものがあります。
https://www.kernel.org/doc/html/
ざっと見て貰えば分かりますが、大量の文書があります。とりあえず、暇なときにどんな文書があるのか、ざっと眺めて見るとよいでしょう。大量とは言え、Linuxの機能全体から見たらほんの氷山の一角にしか過ぎません。また、Linuxのドキュメントとしてどんなものが必要か系統立てて設計された訳ではなく、ドキュメントを書きたい人が書いたものを寄せ集めただけです。過度な期待は持たず、知りたい情報があればラッキーと思った方が良いでしょう。また、OSS(に限らず、どんなソフトウェアにも)あるあるですが、コードの変更にドキュメントが追従していないことがあるので、ドキュメントを盲信せず、最終的にはコードに当たりましょう。そうは言っても、一応、Linuxのコードツリー内にあるものなので、巷の2次資料よりは、まずは参照すべき資料になりますし、中には本当に参考になるものもありますからね。本ブログシリーズでもドキュメントがあれば、できるだけポイントするようにします。
ドキュメントのトップページのソースは、Documentation/index.rst です。ここから、リンク関係を追っていけば、全体像が分かります。実際のところ、あまりきちんと体系立てられた構成にはなっていないので、参照しづらいと思います。また、Documentation/ の下には、html化されていないもの(すなわち、前記のリンク関係から漏れているもの)もあります。ですので、知りたい情報があるかは、まず、Documentation/の下をgrep等してみた方が良いかもしれません。ドキュメントは、rst(かtxt)なので、わざわざ、webで見なくても直接見ることもできます。
もうひとつ参考となるURLを紹介しておきます。以下は、コードツリー内のREADMEを集めたものになります。
https://www.kernel.org/doc/readme/
カーネルのビルド
コードを解析する上では、実際に動かしてみてどうなるのか観察したいこともよくあるかと思います。最近は、トレースツールも充実してきていますし、コードのどの分岐を通っているのか、調べたりするのも便利になってきました。そこで、カーネルのビルド方法について、紹介しておきます。なお、x86_64環境でx86_64用のカーネルをビルドすることを前提としています。ご了承ください。
準備
ビルドに必要なパッケージをインストールします。お使いのディストリビューションや環境によって異なると思いますが、筆者(Ubuntu 24.04)の場合は、以下のものをインストールしました*5。大体、以下で足りると思いますが、ビルド中に何か足りなくて失敗した場合は適当に足してください。
$ sudo apt install make gcc flex bison libelf-dev libssl-dev libncurses-dev
基本手順
基本的には以下の手順となります。
- .configの作成
make ...config - ビルド
make -j... - モジュールのインストール
make modules_install - Linux本体のインストール
make install
上記から分かるように、Linuxのビルドには、makeコマンドを使用します。これは、GNU版のmakeで、GNU独自の拡張がいっぱいあって、Linuxでも当然のように使用しています。コードツリーのトップディレクトリにある Makefile を手始めとして、解析すれば、ビルドプロセスの全貌を明らかにできますが、makeの文法を熟知していないと、なかなか困難です。筆者もそれほど知っているわけではないので、何か見る必要性が生じたら、GNU make のドキュメント片手にということになります。GNU make のドキュメントは以下にあります*6。
https://www.gnu.org/software/make/manual/html_node/index.html
MakefileのLinux特有の内容については、以下のドキュメントが参考になります。
https://www.kernel.org/doc/html/latest/kbuild/makefiles.html
以下、トップディレクトリをカレントディレクトリとして、作業しているものとします。「make help」とやれば、ターゲットの一覧が出てきます。
// $ tar xf linux-6.14.3.tar.xz // $ cd linux-6.14.3 $ make help ...
なんか、大量にあります。ざっと、どんなものがあるのか目を通してみると良いでしょう。
.configの作成
Linuxには、大量のコンフィグレーションオプションがあって、それを定義するのが、.configファイルです。.configファイルの作成がまず第一ステップとなります。
「make help」でコンフィグレーション関連を取り出すと、以下のようになります。
$ make help ... Configuration targets: config - Update current config utilising a line-oriented program nconfig - Update current config utilising a ncurses menu based program menuconfig - Update current config utilising a menu based program xconfig - Update current config utilising a Qt based front-end gconfig - Update current config utilising a GTK+ based front-end oldconfig - Update current config utilising a provided .config as base localmodconfig - Update current config disabling modules not loaded except those preserved by LMC_KEEP environment variable localyesconfig - Update current config converting local mods to core except those preserved by LMC_KEEP environment variable defconfig - New config with default from ARCH supplied defconfig savedefconfig - Save current config as ./defconfig (minimal config) allnoconfig - New config where all options are answered with no allyesconfig - New config where all options are accepted with yes allmodconfig - New config selecting modules when possible alldefconfig - New config with all symbols set to default randconfig - New config with random answer to all options yes2modconfig - Change answers from yes to mod if possible mod2yesconfig - Change answers from mod to yes if possible mod2noconfig - Change answers from mod to no if possible listnewconfig - List new options helpnewconfig - List new options and help text olddefconfig - Same as oldconfig but sets new symbols to their default value without prompting tinyconfig - Configure the tiniest possible kernel testconfig - Run Kconfig unit tests (requires python3 and pytest) ...
コンフィグレーション関連だけで、こんなにあるのですね。それにしても、randconifgなんて何用なんでしょうね。
説明文中の「current config」というのは、.configが既にあれば、その.configのことで、なければ、現在動作中のカーネルのもの*7が参照されます。よくやるのは、実行中のカーネルのコンフィグをベースにすることだと思いますが、その際は、oldconfigを使います。
// $ cp /boot/config-...(動作中のもの) .config # 事前にこれをするのと同じ $ make oldconfig
oldconfigは、「current config」をベースに.configを作成しますが、(ビルド対象)コードにないコンフィグは削除され、増えたコンフィグについては、どうするか聞いてきます。Linuxのバージョンが違うと結構差分があるもので、結構大量のプロンプトに答える必要が出てきます。差分については、デフォルトでいいやと思ったら、olddefconfigを使用します。
$ make olddefconfig
これが一番面倒がないです。差分は、前の.configとdiffを取れば分かります。既存の.configから変更があった場合は、前のものが.config.oldにリネームされて残っています。なお、menuconfig でカーソル使用して選択する場合もあると思いますが、これは、olddefconfig相当です。すなわち、menuconfigで何も変更せずにsaveした場合、olddefconfigの結果と同じになります。
ところで、カーネルのビルドには時間が掛かると聞いた方も多いかと思います。大きな理由のひとつは、モジュールの数が多いことです。特にディストリビューションのコンフィグをベースにすると大量になります。そこで、筆者は考えました。自ホストで動かすだけなら、今使用しているモジュールだけビルドすれば良いのではないか、その分だけビルドするように.configを変更するツールでも作るか、と。なんと、同じようなことを考える人はいるもので、もう既に用意されていました。それが、localmodconfigです。(localyesconfigを使えば、モジュールなしで、現ホストで動作するカーネルをビルドできます。)
// $ make olddefconfig # 差分プロンプトをなくす(極力減らす)ために1度実行しておく。 $ make localmodconfig
参考までに筆者の環境(Ubuntu 24.04)では、6000以上あった(いや、そんなにあったんかい!)モジュールが70ほどに激減しました。それに伴い、ビルド時間も大幅減しました(やったね)。
.config は手動で書き換えても構いませんし、(Update系の)ターゲットを何回実行しても構いません。依存関係のあるコンフィグを変えると隠れていたコンフィグが出てくることがあるので、手動で変更した後は、念のため、oldconfigをやっておくと良いでしょう。
ビルドしたカーネルの区別ができるよう、CONFIG_LOCALVERSION パラメータを変更しておくと良いでしょう。(例)
CONFIG_LOCALVERSION="test-01"
(ディストリビューションのコンフィグをベースにした場合、修正が必要なパラメータもあります。Ubuntuについては、補足資料に記述してあります。)
Linuxのコンフィグの全貌は、トップディレクトリの Kconfig から始めて、その包含関係(「source」文)を追っていけば分かります。.configに記述されたパラメータ順もそのように追っていった順になっています。各Kconfigの中身を見ると、大体、定義してある内容は分かるかと思います。ドキュメントとしては以下があります。
https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html
それにしても、コンフィグパラメータの数が多いです。.configの中をざっと数えても1万以上(!)あります。何も知らない人は、つい、Linuxのコンフィグパラメータの一覧と説明が欲しいなどと言ってしまうかもしれませんが、それは軽く口に出してはいけない言葉なのです。
ビルド
.configを作成したら、カーネル本体とモジュールのビルドを行います。makeのターゲットは不要(allターゲット指定と同等)です。
$ make -j 16
「-j」オプションは、コンパイルの並列度を指定します(上記は一例)。コンパイルするファイルは大量なので、CPUを複数使って、並列に実行した方が時間が短くなります。よく、「-j $(nproc)」という例も見かけますが、これは搭載しているCPU数を指定しています。指定しない(-j省略時は、1)のと指定するのでは、大差になりますので、なるべく多く指定しましょう(他の人の迷惑を考えなくてよいのであれば)。
参考までに、モジュール数を絞れば、筆者のオールドマシン(Broadwell 1798MHz)でも、10分ほどで終わりました。少し一服している間に終わる時間ですね。
ビルドをやり直したい場合は、clean または、mrproper ターゲットを実行して、綺麗にします。.config を維持したい場合は、clean、.configから作り直したい場合は、mrproper を実行します。
インストール
modules_install、install ターゲットをこの順で実行します。(sudo必要です。)
$ sudo make modules_install $ sudo make install
modules_installでモジュールのインストールをします。インストールされるのは、/lib/modules/{バージョン+CONFIG_LOCALVERSION}/ (ex. /lib/modules/6.13.7-test-01/)の下になります。/lib/modules/{...}/build にビルド時のディレクトリへのシンボリックリンクが出来ます。後で解析のために参照することがあるので、一度インストールしたら、ビルドディレクトリはそのまま取っておきましょう。
installで、initrdの作成、/bootの下への各種ファイルのインストール、grubの設定変更などが行われます。grubの起動順は、カーネルバージョンの新しい順になるようです。必要に応じて、ブート時のデフォルトの設定を手動で変更します(詳細は割愛します。補足資料にメモあり)。
これで、動作確認できるようになりました。トレースツールなどの使用法とかそのために必要なコンフィグオプション*8については、気が向いたら別途説明したいと思います。
あとがき
今回は、コードの説明をする以前で終わってしまいました。次回から、コードの説明に入ります。ではまた次回。
*1:どんな世界でもそうですが、裾野が広がれば、それだけトップの頂も高くなるものですからね。
*2:ファイル名にサフィックスが付いていなくても問題なく展開できることから推察。
*3:筆者は、Ubuntuを使うことが多いので、Ubuntuに関しては、補足資料の方にメモしてあります。
*4:デフォルトでは、stableの最新が選択されるようです。
*5:libncurses-devは、menuconfigを使う場合のみ必要。
*6:make-docをインストールして、infoコマンドで見ることもできますが、webの方が見やすいと思います。
*7:/bootの下にあるやつです。
*8:Ubuntuのコンフィグをベースにした場合、特に変更なしで使えるようになってます。