前回、Plamo-7.1から採用することにしたinitrdの概要を紹介しました。その際に触れたように、initrdはドライバ・モジュールを組み込むことに特化したLinuxシステムで、機能はシンプルなものの、Linuxシステムを運用するためのファイル一式が必要なことを紹介しました。
それらのファイルを過不足なく揃え、適切に配置してinitrdイメージを作成するのがmkinitramfsスクリプトです。今回はこのスクリプトの処理を解説しながらinitrdの作り方を紹介しましょう。
/sbin/mkinitramfs
前回、initrd上のinitスクリプトをつまみ読みしながら処理の流れを概観したように、initrd上には「ルートファイルシステムをマウントするために必要なドライバ・モジュール」と「それらモジュールをカーネルに組み込むためのコマンド(実行ファイル)」、「それら実行ファイルが必要とする共有ライブラリ」などが必要となります。mkinitramfsはこれら必要なファイルを集めてinitrdイメージを作るツールです。
mkinitramfsはシンプルなシェルスクリプトなものの、全文を掲載するには長すぎるので、つまみ読みしながら処理の流れを紹介してみます。このスクリプトは、見だしのようにPlamo-7.1環境では/sbin/mkinitramfsにありますので、全体を見たい方はそちらでご確認ください。また、このコードのオリジナルはBLFSのサイトで参照できます。
必要なコマンドの準備
mkinitramfsでは、最初の30行ほどで内部処理用のcopyコマンドを定義したり、ドライバ・モジュールのディレクトリを確認するなどの初期化処理を行った後、initrd上で使うコマンドを$binfilesと$sbinfilesに指定していきます。
これらのうち、$binfilesに指定したコマンド(の実行ファイル)はinitrdのbinディレクトリに、$sbinfilesに指定した実行ファイルはsbinディレクトリに、それぞれコピーされます。
また、もう少し先でkmodやlvm, dmsetupが追加されるので、initrdに含まれる実行ファイルは総計27個となります。
作業用ディレクトリの作成
次に/tmpディレクトリに作業用の一時ファイル($unsorted)とディレクトリ($WDIR)を用意します。
$unsortedは先に指定した27個の実行ファイルが必要とする共有ライブラリを調べる際に使うファイルで、後述するように各実行ファイルにlddした結果を収めていきます。一方、$WDIRはCPIOイメージを作る際の作業ディレクトリになります。
次に、$WDIRにディレクトリ構造を作ります。initrdはシンプルなものの独立したLinux環境なので、ルートファイルシステムのディレクトリ構造一式が必要となります。
initrd用の/dev/consoleと/dev/nullを作ります。ほとんどのデバイスファイルはudevdが動的に作るものの、udevdのメッセージを表示したり、不要なメッセージを捨てたりするデバイスファイルはあらかじめ用意しておく必要があります。
実行ファイルと共有ライブラリのコピー
まず、udev用の設定ファイルやルールファイルを、動作中の環境から作業用ディレクトリへコピーします。
代入している部分は省略したものの、89行目で使っている$DATADIR/$INITINは/usr/share/mkinitramfs/init.inで、あらかじめ用意してあるこのファイルを、initrd上のinitとしてインストールします。このファイルは前回紹介しました。
ドライバ・モジュールを組み込むためのkmodを$binfilesのリストに追加します。念のため、古いmodule-init-toolsを使っている場合も考慮しています。
次に$binfilesに指定したコマンド(実行ファイル)を作業用ディレクトリ内へコピーします。$binfilesにはコマンド名しか指定していないため、実行ファイルが/bin/にあるのか/usr/bin/にあるのかをチェックして(102行目)、利用する共有ライブラリの情報を$unsortedに記録(103行目)してから、作業用ディレクトリのbin/以下にコピーしていきます。なお、ここで使っているcopyは、省略した部分(5~22行目)で定義している内部コマンドで、宛先を$WDIR内に限定したコピーです。
同様に$sbinfilesに指定したコマンド(実行ファイル)を作業用ディレクトリ内へコピーします。こちらの実行ファイルは/sbin/以下にあるはずなので一手間省けますが、共有ライブラリは忘れずにチェックします。ここまでで、initrdに組み込む実行ファイルが必要とする共有ライブラリの一覧(重複あり)が$unsortedに記録されました。
次に、$unsortedに集めた共有ライブラリのリストからsortとuniqを使って重複を除き、残ったライブラリを作業ディレクトリのlib/以下にコピーします。155~157行目は、lddコマンドでは共有ライブラリとして表示されるものの、カーネルが提供する仮想的なライブラリで実体は存在しない"linux-vdso.1"や"linux-gate.so.1"を省く処理です。
このあたり、コードとしてはそれほど凝ったことをしているわけではないものの、initrdを自作しようと苦労したことがある人間にとって、これだけの処理でinitrdに必要な共有ライブラリが揃うというのは、結構「目から鱗」な体験でした。
udevは/lib/udevに支援用の実行ファイルがあるので、それらをまとめて作業用ディレクトリにコピーします。
ドライバ・モジュールのコピー
次に、findとcpioを使って、カーネルのドライバ・モジュールを作業用ディレクトリにコピーします。
このあたり、ちょっと凝ったコードなので多少わかりにくいですが、172~177行は一連のコマンドになっていて、まず"find"コマンドの引数にinitrdに収めたいドライバ・モジュールのあるディレクトリをbashの拡張機能である「ブレース展開("{...}")」で列挙し、"-type f"でそれらディレクトリにある通常ファイル(=ドライバ・モジュール)の一覧を表示します。
一方、そのファイル一覧をパイプ("|")経由で受けとるcpioコマンドは、"-p"オプションの指定で「コピーパスモード」で動作し、受けとったファイル名を作業用ディレクトリである$WDIR以下にコピーします。その際、"--make-directories"オプションを指定しているので必要なディレクトリも作成され、/lib/modules/$KERNEL_VERSION/kernel 以下のディレクトリ構成が、そのまま作業用ディレクトリの lib/modules/以下に再現されるわけです。
このあたり、cpioコマンドを使わずに書こうとすると、それぞれのドライバ・モジュールごとにdirnameして収められているディレクトリを調べ、mkdir -pでそのディレクトリを作った上で、basenameで調べたドライバ・モジュール名にコピーする、みたいな作業が必要になるでしょう。
cpioはUNIXの初期に開発されたアーカイブツールで、さまざまな機能が追加された長い歴史を持つ分、オプション指定が複雑で、初心者には敬遠されがちなコマンドです。その分、用途にぴったりとハマったこのような使い方を見せられると、「なるほど」と感心してしまいます。
ドライバ・モジュールの依存関係の更新とCPIOアーカイブ化
次に、カーネルに組みこんだドライバのリスト(modules.builtin)やドライバ・モジュールの一覧表(modules.order)をコピーし、depmodコマンドで作業用ディレクトリ内のモジュール・ドライバに限定した依存関係データベースを作成します。
以上でカーネルのドライバ・モジュールとそれらの依存関係情報、モジュールを組み込むためのコマンドやライブラリ一式が作業用ディレクトリに揃ったので、それらをCPIO形式にまとめてgzipで圧縮すればinitrdイメージが完成します。最後に、不要になった作業用ファイルとディレクトリを削除します。
なお、今回紹介したのはmkinitramfs-0.2のコードで、最近更新したmkinitramfs-0.4では、最後の部分にCPU用のfirmwareを追加する処理を加えており、それらについては次回紹介する予定です。
mkinitramfsの特徴
以上、Plamo-7.1で採用した、LFS/BLFS由来のmkinitramfsについて紹介しました。このmkinitramfsは、インストール済のドライバ・モジュールのうち、ルートファイルシステムのマウントに必要なモジュールは、使う使わないに関わらず全て収めてしまおう、という大胆な設計になっているのが特徴です。
この設計方針は従来のinitrdに慣れている人には奇異に見えるかも知れません。というのも、CentOSやArch Linuxなど主要なディストリビューションが採用しているinitrdでは、インストール済のドライバ・モジュールのうち、その環境で実際にカーネルに組み込まれたモジュールのみをinitrdに組み込む、という設計になっているからです。
前者の設計では、その環境には不要なドライバ・モジュールも大量にinitrdに組み込まれるため、起動時の読み込みに余計な時間がかかります。一方、後者の設計ではinitrdのサイズはコンパクトになるものの、動作中のカーネルに組み込まれたドライバ・モジュールのうち、どのモジュールをinitrdに持ち込むかの判断が必要で、その際はモジュールの依存関係にも考慮を払う必要があります。
また、前者の設計ではカーネル毎(ごと)にinitrdを作り置きできるのに対し、後者ではインストール毎にinitrdを作ることになるため、作成されるinitrdは環境によって異なり、トラブル時の対応が難しくなりそうです。
LFS/BLFSの場合、手作りできるシンプルさが何よりも重視されるので、多少initrdのサイズが大きくなっても構わない今回のような設計になったように思うものの、ディストリビューションとして使うには改善の余地がありそうなので、今後の検討課題と考えています。
今回紹介したLFS/BLFS由来のmkinitramfsは、主流のディストリビューションが採用しているmkinitramfsよりも粗い作りになっているものの、その分汎用性は高く、$binfilesや$sbinfilesに使いたいコマンドを追加し、initスクリプトを調整することで、ルートファイルシステムをマウントする以外の機能、たとえばルータやfirewall専用のinitrdイメージも簡単に作れそうです。このように既存ツールの新しい応用方法を考えていくのも、ソースコードが公開されているOSSならではの魅力でしょう。