前回は本連載で紹介してきたソースコードリテラシーの実践編として、pmountコマンドを組み込む際に遭遇したトラブルとその調査、解決方法を紹介しました。今回は、もうひとつの実例として、最近、P-Plamoの開発時に遭遇したpivot_rootがらみのトラブルの調査、解決の例を紹介しましょう。
P-Plamo とは?
P-Plamoとは、筆者が現在開発を始めているDVDメディアから直接起動するPlamo Linuxの名称です。HDDにインストールする必要がなく、CDやDVD、USBメモリといったメディアから直接起動して利用できるLinuxシステムは、一般にLiveLinuxと呼ばれ、DebianベースのKnoppixやSlackwareベースのSlaxなどが有名です。
Plamo Linuxでも過去にCD-Plamoという名称で CDメディアから起動して使えるシステムを開発したことがありましたが、昨今の多機能、大規模化が進む各種ソフトウェアを700MバイトというCDメディアのサイズに合わせるために取捨選択するのが面倒で、長く放置したままになっていました。
一方、最近では、4.7GBまで使えるDVDメディアの価格も数年前のCDメディア程度にまで下ってきましたし、数Gバイト規模のUSBメモリも気軽に買える値段になってきました。4GバイトくらいあればKDEやGNOMEといったデスクトップ環境まで含めたPlamo Linuxがそのまま入りますし、LiveLinuxで広く使われている圧縮ファイルシステム機能を使えば、pTeXやOpenOfficeまで収めることも可能でしょう。
そこで、Plamo-4.6の開発が一段落したのに合わせて、久しぶりにPlamo LinuxベースのLiveLinuxに取り組んでみたのがP-Plamoです。P-Plamoでは、かってのように700MバイトというCDメディアのサイズには拘泥せず、4.7GバイトのDVDメディアに収まればいい、ただし、あまり節操無くパッケージを追加するのもどうかと思うので、2Gバイト程度のUSBメモリに、自分のファイルと共に書き込める規模を目指す、という方針で開発を始めました。
トラブル発生
一般的なLiveLinuxではCDやDVDメディア上に置いた実際のルートファイルシステムを使う前に、メモリに読み込んだファイルシステム上で必要な初期化処理を行います。この仕組みはPlamo Linuxのインストーラと同じなので、P-Plamoの起動処理の部分はインストーラ同様のinitramfs機能を用いて実装しました。
限られた容量に可能な限りファイルを詰め込むためのファイルシステムの圧縮にはSquashFSを、リードオンリーになるルートファイルシステムに設定ファイルやログファイルを書き込むためにはaufsを用いたunion mountを使うことにしました。いずれの技術もまだ公式のカーネルソースには含まれていませんが、Slax等のLiveLinuxで広く使われており、各種パッチも公開されています。
これらLiveLinuxに必要な機能を提供するモジュールとCD-ROM関連のモジュールを、メモリに読み込んだ初期化用ファイルシステム上でBusyBoxを使ってカーネルに組み込み、DVDのありそうなデバイスを総当り的に探してDVDメディアをマウント、見つかったDVDメディア上のSquashFS化した正式なルートファイルシステムをloopback形式でマウントして、switch_rootでルートファイルシステムを切り替えるという形で、とりあえず動くバージョンをまとめることができました。
とりあえず動くものができたので、P-Plamo 0.1としてメーリングリストで紹介したところ、「終了時にDVDをejectして欲しい」というリクエストがありました。確かに現状ではDVDメディアをマウントしたまま電源断してしまうので、DVDを取り出すためには再度電源を入れてejectしないといけないので不便です。
当初は、終了処理の最後にでもejectコマンドを実行すればいいだろう、と簡単に考えていましたが、調べ始めたところ、かなり深刻な問題に気づきました。
P-Plamo 0.1ではswitch_rootでルートファイルシステムを切り替えて、DVDメディア上のSquashFSにある/sbin/initに処理を委ねてしまうため、ランレベルを切り替えて終了処理を実行している際も(DVDメディア上にある)initが動き続けるのでDVDメディアをアンマウントできません。
さてどうしたものか…、と、BusyBoxのejectコマンドをメディアのロックを無視するように改造する方法や、initコマンドの最後でejectを呼ぶように改造する方法なども検討しましたが、ヘンにローカルに改造したBusyBoxを使うのも気持ちよくないし、使用中のメディアを強制排出するのも行儀よくないでしょう。
そのため処理的には多少面倒になりますが、ルートファイルシステムを切り替える際、古いルートファイルシステムを捨ててしまうswitch_rootではなく、別のディレクトリ以下に残しておけるpivot_rootを使い、終了時には古いルートファイルシステムに戻った上でDVDをアンマウトしようとしました。
ところが、switch_rootの代りにpivot_rootを使おうとすると“Invalid argument”のエラーになり、ルートファイルシステムが切り替わってくれません。
よく似た機能なのに switch_root と pivot_root って何が違うのかな、と調べてみることにしました。
pivot_root のソースコード
今回使っているswitch_rootとpivot_rootは共にBusyboxに含まれているコマンドです。そこでまずBusyBoxのマニュアルを調べてみました。
BusyBox のマニュアルには上記のような使い方の簡単な記述しかなく、これだけでは手掛りになりません。
仕方ないのでBusyBoxのソースコードを眺めてみました。
BusyBoxのpivot_root.cのコードはこれだけで、パラメータ数をチェックしてpivot_rootシステムコールを呼び出しているだけです。
紹介はしませんでしたが、pivot_rootはutil-linuxパッケージにも含まれているLinuxの標準コマンドなので、そちらのマニュアルページも調べたところ「pivot_rootはシステムコールのpivot_rootを呼び出しているだけなので、詳細はシステムコールのpivot_rootを参照せよ」とありました。このソースコードを見る限り、BusyBoxのpivot_rootもシステムコールのpivot_rootを呼び出しているだけのようです。
カーネルソースの調査
システムコールとなるとカーネルソースの中の話になるので簡単には手が出せそうにありませんが、とりあえずpivot_rootがどこで処理されているかを調べるためにソースコードをgrepしてみました。以下に示すfind コマンド中の-followはシンボリックリンクも辿る指示、cat -nは行番号の表示用です。
各種アークテクチャ(CPU)用のアセンブラコードからドキュメントファイルまで92箇所ほどでpivot_rootへの言及があるようです。これらのファイルのうちx86用のファイルから眺めてみましたが、ヘッダファイルやアセンブラコードはシステムコール番号の定義のみで、実際の処理はありません。
実際の処理はどこだろうな…、としばし悩みましたが、fs/namespace.cに見つかりました。
どういう処理をしているのかを見る前に、ざっとコメントに目を通しておこうと眺めたところ、2170行目に“the current root cannot be on the 'rootfs' (initial ramfs) filesystem.”という記載がありました。
そもそもinitial ramfs(initramfs)をルートファイルシステムにしている状態ではpivot_rootは使えないようです。
Documentation/filesystems/ramfs-rootfs-initramfs.txtを見よ、とあるので、さっそくそのファイルを調べてみました。
このファイルは以前、Plamoのインストーラをinitrd形式からinitramfs形式に変更する際に調べたことがありました。その際はinitramfs形式の作り方を中心に読んだのですが、今回は改めて頭から読み直しました。
このファイルでは、まずカーネルのディスクキャッシュの仕組みを用いたramfsというファイルシステムについて紹介し、従来使われていたram diskとの違いを解説しています。新しく実装されたramfsは、カーネルのキャッシュ機能を利用して動的に大きさが変更できるのに対して、従来のram diskではあらかじめメモリ上に決まった容量を割り当てなければならないので無駄が多いこと、ram diskの機能の多くはloopbackデバイスを使えば実現できるので時代遅れになっていることなどが説明されています。
そして、ramfsの特別な例としてのrootfsについて紹介されていました。
rootfsはramfsで実現されたカーネル内蔵のファイルシステムで、カーネルがファイルシステムを探す際の起点として使うのでアンマウントはできず、カーネルの起動時パラメータで指定するroot=/dev/hda2等のHDD上の実際のルートパーティションは、このrootfsの上にマウントされることでルートファイルシステムと見なされるようです。
もう少し読み進めていくと、initramfsとinitrdの違いについても紹介してありました。
以前、initramfsの作り方を中心に読んだ時にはこのあたりの話は読み飛していたのですが、改めて読み直してみるとinitrdとinitramfsの重要な違いが説明されています。
initrdの場合はメモリ上に割りあてたram diskをrootfs上にマウントしているので、pivot_rootすることで別のファイルシステムをrootfsにマウントし直すことができるのに対し、initramfsの場合はrootfsそのものにcpio+gzipで固めたファイルを書き込んでルートファイルシステムにしているため、データを消去することで使っていた領域を解放することはできるものの、pivot_rootでルートパーティションを切り替えることはできない、ということです。
実のところ、P-Plamoの参考にするためにSlaxの起動の仕組みなどを調べたことがあります。その際、Slaxでは従来のinitrd形式を使っているので、なぜ作りやすいinitramfs形式を使わないのだろう?と疑問を感じたのですが、終了時にCD/DVDメディアを排出するためには、pivot_rootで元のルートファイルシステムに戻る必要があり、そのためには従来のinitrd形式が必須だったようです。
実際にinitrd形式ならpivot_rootできるかどうかを試してみるために、initramfs形式で作っていた起動用ファイルシステムをinitrd形式に変更して試してみました。
まずddコマンドでinitrd用に固定サイズのファイルをあらかじめ確保しておきます。この例では4Mバイト分、/dev/zeroから0を書きこんだファイル(miniroot)を作りました。
そのファイルをmke2fsでフォーマットし、/loopにloopback形式でマウント、そこにinitramfs用に作っていたファイル一式をtarコマンドを使ってコピーし、念のためumountしてからgzipで固めてinitrd.gzを作りました。
このinitrd.gzに入れ替えて作成したDVDイメージで起動し、pivot_rootの動作を試しました。
起動直後はinitrdが/dev/rootになり、DVDメディア上のLiveLinux用のルートファイルシステムは/loop以下に配置されているのに対し、/loop ディレクトリでpivot_root . put_oldした後は、/loop/tmpが/tmpになる等、LiveLinux 用のルートファイルシステムが本来の位置に配置され、元のinitrd上のファイルシステムはput_oldディレクトリ以下に移動しています。
この状態で、再度put_old以下のディレクトリに移動してpivot_rootを実行すれば、無事、元のinitrd上のファイルシステムがルートファイルシステムに戻りました。
pivot_rootでルートファイルシステムを行き来できるメドがついたので、最終的な終了処理はinitrd上のshutdownコマンドで行い、そこからejectコマンドを実行するようにして、何とか終了時にDVDメディアを排出できるようになりました。
今回はinitramfsでpivot_rootできない原因をカーネルのソースコードまで辿って調べてみましたが、実際の処理そのものを調べるまでもなく、コメントと付属のドキュメントで原因は判明しました。
前回のpmountもそうでしたが、ソースコードを読む、といっても、アルゴリズムや処理のステップを丁寧に追うまでもなく、デバッグメッセージやコメント行を調べる程度で解決する問題は多数あります。ソースコードや付属ドキュメントを「難しそうだから」「英語だから」と敬遠せず、本連載で紹介してきたように、機会を見つけてはチェックするようにすることが、問題解決の勘を養い、初心者から一歩を踏み出す近道になるでしょう。
さて、春は出会いと別れの季節です。約2年ほど続いたこの連載も、カーネルソースまで辿りついた今回で一段落させてもらうことになりました。ご愛読を感謝すると共に、これからも皆さんがソースコードやドキュメントに親しんでいかれることを願って、終了の挨拶とさせていただきます。長い間、ありがとうございました。