今回は、Linuxシステムのdevfs(Device Filesystem)への完全な変換を行います。devfsに関する連載記事を今回初めてお読みになる方は、まずこの連載記事の第4回をお読みください。第4回の記事は、devfsがどのようにしてデバイス登録に関する問題をカーネル・レベルで解決するかについて説明しています。その上で第5回をお読みください。Linuxシステムをdevfs対応とし、devfsへの最終的な変換ができる状態にするステップを説明しています。
以下に示す手順を実行する前に、必ず第5回の記事をお読みください。第5回で説明しているステップを省くと、これからインストールするinitラッパーは、正常に機能しなくなり、その結果システムがブートせず、緊急のリカバリーが必要となるでしょう。これでは、せっかく導入した意味がありません。第5回をすでにお読みの方は、先に進んでください。
第5回の記事の最後に、initラッパーの概念について紹介し、initラッパーがdevfsの初期設定に関するいくつかの問題を解決する方法として最適である理由を説明しました。ここからは、initラッパーの完全版について順を追って検討し、各部分の機能について考察します。それでは、見ていきましょう。
以下の例で、スクリプトの先頭に#!/bin/bashがあることから分かるように、initラッパーは実際にはbashスクリプトです。initラッパーを実行するにはbash 2.0以上が必要です。/bin/bash --versionと入力し、現在使用しているbashシェルが2.0以上であるかどうか確認します。2.0以上でなければ、/bin/bash2実行可能プログラムがインストールされているかどうか確認します。2.0以上であれば、スクリプトの先頭行を#!/bin/bash2に変更します。
initラッパーの先頭部分
#!/bin/bash
# Copyright 2001 Daniel Robbins <drobbins@gentoo.org>, Gentoo Technologies, Inc.
# Distributed under the GNU General Public License, version 2.0 or later.
trap ":" INT QUIT TSTP
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
umask 022
if [ $$ -ne 1 ]
then
exec /sbin/init.system $*
fi
|
それでは、このスクリプトをザッと見てみましょう。trapコマンドは、スクリプト実行中にスクリプトへのユーザーの(たとえば、ブート中のCtrl + C)割り込みを防ぎます。次に、適切なデフォルト・パスをexportし、デフォルトのumaskを022に設定します。デフォルトのumaskは、常にブート・プロセスの最初の方に設定した方がよいでしょう。それは、初期の2.4カーネル・リリースの多くに、デフォルトのumaskが0になるバグがあり、セキュリティー上の問題となるためです。
次に、最初の条件付きステートメントif [ $$ -ne 1 ]があります。$$は、bashによって現在実行中のプロセスのプロセスIDに置換されるため、実際には「このプロセスIDは1以外か」という質問をしていることが分かります。これはどんな意味を持つのでしょうか。このinitラッパーがブート・プロセス間にカーネルによって起動されている場合、PIDは必ず「1」となります。これは、PID「1」がinitプロセス用に予約済みのためです。PIDが1ではない場合、システム・ブート後にコマンド・ラインから実行されていると考えられます。/sbin/initコマンドには、ブート済みのシステムのrunlevelをスーパーユーザーが変更できるようにするという目的もあるため、これはよくあることです。この場合には、/sbin/init.systemにリネームされている元の/sbin/initをexecするだけです。$*変数を使って任意のコマンド・ライン引数をinit.systemに渡すとinitラッパーは終了し、init.systemが実行を開始します。
ただしブート時に、このラッパーをカーネルが開始している場合は、bashのPIDは1となり、この条件付きステートメント全体がスキップされ、bashはこのラッパーの実行を継続します。その続きは、以下の行です。
initラッパーの続き
mount -n /proc
devfs="yes"
for copt in `cat /proc/cmdline`
do
if [ "${copt%=*}" = "wrapper" ]
then
parms=${copt##*=}
#parse wrapper option
if [ "${parms/nodevfs//}" != "${parms}" ]
then
devfs="no"
fi
fi
done
|
コードのこの部分までくると、このラッパーはブート・プロセス間にカーネルが実行していることになります。その次にまず行う操作は、ルート・ファイルシステムに対する/procのマウントですが、この時点ではまだ読み取り専用です。その後、便利なLinux機能を利用した、大きく複雑なbashコード部分を実行します。/proc/cmdlineの内容を見ると、どのようなオプションがLILOまたはGRUBによってカーネルに渡されたのか確認できます。私の開発用ボックスでは、/proc/cmdlineの内容は以下のとおりでした。
/proc/cmdlineの内容
# cat /proc/cmdline
root=/dev/hda6 hda=89355,16,63 mem=524224K
|
上記コードでは、/proc/cmdlineを使い、このwrapperというスクリプトで作成したカーネル・ブート変数を検索しています。カーネル・ブート・オプション内にwrapper=nodevfsがある場合、スクリプトはdevfsが有効になっていないと判断しますが、この変数が/proc/cmdline内にない場合には、このラッパーはdevfsの初期設定を開始します。これから分かることは、wrapper=nodevfsカーネル・ブート・オプションを使ってブートすることにより、devfsを簡単に無効化できるということです。その場合、devfs変数は「no」に設定され、それ以外の場合は「yes」に設定されます。
ラッパーの残りの部分は以下のとおりです。
initラッパーの残りの部分
if [ "$devfs" = "yes" ]
then
if [ -e /dev/.devfsd ] then
clear
echo
echo "The init wrapper has detected that /dev has been automatically mounted by"
echo "the kernel. This will prevent devfs from automatically saving and"
echo "restoring device permissions. While not optimal, your system will still"
echo "be able to boot, but any perm/ownership changes or creation of new compat."
echo "device nodes will not be persistent across reboots until you fix this"
echo "problem."
echo
echo "Fortunately, the fix for this problem is quite simple; all you need to"
echo "do is pass the \"devfs=nomount\" boot option to the kernel (via GRUB"
echo "or LILO) the next time you boot. Then /dev will not be auto-mounted."
echo "The next time you compile your kernel, be sure that you do not"
echo "enable the \"Automatically mount filesystem at boot\" devfs kernel"
echo "configuration option. Then the \"devfs=nomount\" hack will no longer be"
echo "needed."
echo "read -t 15 -p "(hit Enter to continue or wait 15 seconds...)" else
mount -n /dev /dev-state -o bind
mount -n -t devfs none /dev
if [ -d /dev-state/compat ]
then
echo Copying devices from /dev-state/compat to /dev
cp -ax /dev-state/compat/* /dev
fi
fi
/sbin/devfsd /dev >/dev/null 2>&1;
fi
exec /sbin/init.system $*
|
これで、devfsが「yes」に設定されている場合にのみ実行される、大きな条件付きステートメントまで来ました。そうでない場合には、devfs初期設定は完全にスキップされ、devfsのマウントも行われません。その場合、devfsではない通常のブートが実行されます。
ここではdevfsの設定を行っているわけですから、この条件付きステートメントの内容を検討しましょう。devfsがカーネルによってすでにマウントされているかどうか確認します。それには、/dev/.devfsdキャラクター型デバイスの有無を確認します。devfsがマウントされると、カーネルはこのデバイスを自動的に作成し、それ以降のdevfsdプロセスはそれを使ってカーネルと通信します。devfsがマウント済みの場合(ユーザーが「Automatically mount devfs at boot」カーネル・オプションを選択したために)、devfsの永続性機能をセットアップできないというメッセージが出力されます。これは、その操作を実行できるのは、devfsがカーネルによってマウントされていない場合のみであるためです。
特に問題がなければ、前回最後に説明したdevfsのセットアップを実行し、/devを/dev-stateにバインド・マウントし、devfsファイルシステムを/devにマウントします。次いで、前回説明しなかったステップを実行します。/dev-state/compatディレクトリーの有無を確認し、それが存在する場合はその内容を/devに再帰的にコピーします。この手順は、一見するとやや冗長に見えるかもしれませんが(devfsdのデバイス永続性機能を利用しようとしているため)、必要かつ有効であることが分かります。compatディレクトリーが必要なのは、devfs対応のドライバーがないとdevfsdの永続性機能が機能しないためです。
したがって、devfs以外のカーネル・モジュールを使用する場合、手動で/devにデバイス・ノードを作成する必要があります。この方法の問題点は、この新しいデバイス・ノードがdevfsdに無視され、次回リブート時には消えてしまうことです。この問題の解決策となるのが/dev-state/compatディレクトリーです。devfs以外のモジュールの場合、/dev-state/compat内に従来のデバイス・ノードを作成するだけで、この便利なinitラッパーのステップによって、ブート時におけるdevfsファイルシステムへの手動による追加が可能です。
最後に、devfsdを起動してから条件付きステートメントを終了し、実際のinit、/sbin/init.systemをexecして標準のシステム・ブート・プロセスを開始します。devfs対応システムになったことを除けば、すべて従来と変わりありません。
次に、initラッパーのインストール方法について説明します。まず、wrapper.shのソースを入手し、システム内(のどこか)に保存します。そして、以下のコマンドを実行します。
initラッパーのインストール
# cd /sbin
# cp init init.system
# cp /path/to/wrapper.sh init
# chmod +x init
|
これでinitラッパーがインストールされました。
initラッパーを使用することで、initscriptの複雑な微調整の大部分を回避することができますが、どうしてもそれが必要な場合が一つあります。devfsファイルシステムが/devにマウントされたことから、おそらく、rcスクリプトによるルート・ファイルシステムのアンマウントが簡単にはできなくなります。幸い、この修正は簡単にできます。使用しているディストリビューションのrcスクリプトのインストール先に応じてcd /etc/rc.d; grep -r umount *またはcd /etc/init.d; grep -r umount *と入力し、rcスクリプト・ディレクトリーをgrepし、umountの出現箇所をすべて検索します。その後、umountを参照しているすべてのスクリプトで、umountが-rオプション付きで呼び出されるようにします。特に重要なのは、ルート・ファイルシステムをアンマウントしている特定のumountコマンドですが、どれもこれもをumount -rとするのでもうまくいくはずです。
-rオプションを指定すると、umountはアンマウントが失敗した場合に読み取り専用としてファイルシステムの再マウントを試みます。ルート・ファイルシステムを整合状態とし、リブートできる状態にするにはこれで十分です。開いているデバイス・ノードが存在し、アンマウントできないものが/devにマウントされ、ルート・ファイルシステムのアンマウントができない場合でも問題はありません。
これで、ほぼリブートできる状態になりましたが、リブートの前にdevfsdについて考察し、互換デバイスとデバイス永続性が有効になるように/etc/devfsd.confを仕上げましょう。ご心配なく。devfsへの移行が完了するまで、あと1歩です。
使用しているエディターに/etc/devfsd.confをロードしてください。以下は、私がお勧めするdevfsd.confの最初の4行です。
devfsd.confの先頭部分
REGISTER .* MKOLDCOMPAT
UNREGISTER .* RMOLDCOMPAT
REGISTER .* MKNEWCOMPAT
UNREGISTER .* RMNEWCOMPAT
|
上記4行はそれぞれ、イベント(REGISTERまたはUNREGISTER)、正規表現(.*)、およびアクション(*COMPATストリング)からなります。これらにはどんな意味があるのでしょうか。最初の行は、任意のデバイス(.*は任意のデバイスと一致する正規表現)がカーネルに登録されている場合、MKOLDCOMPATアクションを実行するようにdevfsdに指示します。MKOLDCOMPATアクションは、devfsdに組み込まれているもので、「そのデバイスに対応する従来の互換デバイスをdevfsによって登録する」ものです。すでにお気づきと思いますが、デバイスの登録解除時に実行されるRM*COMPATアクションは、これらの特殊互換デバイスを適切に消去するためのものです。全体的に見ると、この4行は、デバイス登録時に互換デバイス(もしあれば)を作成し、デバイス登録解除時にその互換デバイスを削除するようにdevfsdに指示します。この行によって、IDEデバイス・ドライバーが/dev/ide/host0/bus0/target0/lun0/discdevfsスタイル・デバイスをシステムに登録すると、devfsは一致する/dev/hda互換スタイル・デバイスを自動的に作成します。これは、mountやfsckなど、従来のデバイス名を含む/etc/fstabを読み込む可能性のあるコマンドにとって非常に役立ちます。多くの場合、互換デバイスの作成によってdevfsの移行はシームレスなものとなります。私がお勧めするdevfsd.confの次の行は以下のとおりです。
devfsd.confの続き
LOOKUP .* MODLOAD
|
このエントリーは、任意のデバイス(.*)が「ルックアップ」されている場合は必ずMODLOADアクションを実行するようにdevfsdに指示します。プログラムが特定のデバイス・ノードの存在を探す場合に、このルックアップが行われます。MODLOADアクションにより、modprobe /dev/mydevが実行されます。/dev/mydevは、特定のプロセスが見付けようとしているデバイス名です。この機能(および適切に構成された/etc/modules.conf)によって、音楽プレイヤーなどの起動時に、オンデマンドでのサウンド・カード・ドライバーの自動ロードが可能となります。
devfsd.confの次の数行は、以下のとおりです。
devfsd.confの続き
REGISTER ^pt[sy]/.* IGNORE
CHANGE ^pt[sy]/.* IGNORE
REGISTER .* COPY /dev-state/$devname $devpath
CHANGE .* COPY $devpath /dev-state/$devname
CREATE .* COPY $devpath /dev-state/$devname
|
これらの行は、デバイス・アクセス権や所有権の変更のため、またユーザーが作成した新しい互換デバイスのためのリポジトリーとして/dev-stateを使用するようdevfsdに指示します。最初の2行は、疑似端末デバイスをカーネルに登録する場合、あるいはその属性を変更する場合に、いずれの特殊アクションも実行しないようdevfsdに対して明示的に指示します。これらの行がないと、リブートしても疑似端末のアクセス権や所有権が保持されることになります。ただし、システムの起動直後に疑似端末デバイスに対するデフォルトのアクセス権をそれぞれ設定する必要があるため、これは最適なものとは言えません。
次の3行で、他のすべてのデバイスに対して/dev-state永続性を有効にします。具体的には、デバイス登録時またはdevfsdの起動時(ならびに既存の互換デバイスに上書きする際)に/dev-stateからすべての属性を復元し、属性の変更ならびに互換デバイスの新規作成後、直ちに/dev-stateにバックアップします。
私の推薦するdevfsd.confの末尾は、以下のようなものです。
devfsd.confの末尾
REGISTER ^cdrom/cdrom0$ CFUNCTION GLOBAL symlink cdroms/cdrom0 cdrom
UNREGISTER ^cdrom/cdrom0$ CFUNCTION GLOBAL unlink cdrom
REGISTER ^misc/psaux$ CFUNCTION GLOBAL symlink misc/psaux mouse
UNREGISTER ^misc/psaux$ CFUNCTION GLOBAL unlink mouse
|
この最後の4行はオプションですが、一考の価値があります。/dev-stateの永続性は、デバイス・ノードに対してはうまく働きますが、シンボリック・リンクに対してはまったく機能せず、シンボリック・リンクは無視されます。ここで1つの疑問が生じます。/dev/mouseまたは/dev/cdrom symlinkを存在させるだけでなく、リブートを行っても永続性を保持するようにするにはどうすればよいのでしょうか。幸い、devfsdはきわめて柔軟な構成が可能であり、これら4行(またはシステムに合わせてカスタマイズした行)によってそれを行うことができます。最初の2行は、/dev/cdrom/cdrom0デバイスの登録時に/dev/cdrom symlinkを示すようにdevfsdに指示します。そのために、devfsdは指定されたlibc関数(この場合はsymlink()とunlink())の動的呼び出しを実際に実行します。最後の2行は、同じ方法を用いて、/dev/misc/psaux(PS/2マウス)デバイスをdevfsに登録する際に/dev/mouse symlinkを作成するものです。システムに合わせてこれらの行をカスタマイズし、このファイルを保存します。このdevfsd.confをダウンロードして使用することもできます。
リブートを行う前に、Richard Gooch氏のdevfs FAQに目を通しておくことをお勧めします。devfsの命名規則に関する情報があり、新しいスタイルのデバイス名に慣れた場合、特に役立ちます(以下の参考文献を参照)。また、ブート関連の問題修正のため「非常時のbashの救済」が必要となった場合に備えて、この連載記事(第5回)のコピーを印刷しておくこともお勧めします。何らかの理由でこの新しいinitラッパーがうまく機能しない場合、非常時の救済指示に従ってルート・ファイルシステムを読み取り/書き込み用として再マウントし、以下のステップを実行すれば、いつでも削除することができます。
必要に応じて、ラッパー適用前の状態に戻すためのステップ
# cd /sbin
# mv init wrapper.sh
# mv init.system init
|
上記ステップの実行後、ファイルシステムを読み取り専用として再マウントし、リブートすれば、システムはラッパーを適用する前の状態に戻ります。では、上記作業を行ってリブートし、devfsを楽しんでください。
- この記事で使用したコード・ソースをコピーできます。
- Daniel Robbins氏によるこの連載記事をお読みください。それぞれのトピックは、以下のとおりです。
- ジャーナリングとReiserFSの利点(第1回)
- ReiserFSシステムのセットアップ(第2回)
- 仮想記憶(VM)ファイルシステムとバインド・マウントの使い方(第3回)
- デバイス管理用ファイルシステムdevfsの利点(第4回)
- devfsのセットアップ(第5回)
-
devfsd tarballの最新版をダウンロードしてください。最新版は1.3.20です。
- O'Reilly発行の『Linux Device Drivers, 2nd Edition』は優れた書籍であり、デバイス登録ならびにLinuxデバイス・ドライバー・プログラミング全般についてより詳細に知りたい方にとても役立ちます。
- Richard Gooch氏(Linux devfsの開発に携わった)によるLinux Devfs FAQを是非お読みください。devfsの命名規則に関する内容が特に役立ちます。また、Richard Gooch氏のメイン・ページもご覧ください。devfs情報の他にも素晴らしい情報が掲載されています。
- devfsのメーリング・リストに参加するには、メッセージの本文をsubscribeとしたメールをmajordomo@oss.sgi.comをお送りください。
-
Linux Weekly News は、最新のカーネル開発状況を常に知っておくための貴重な参考文献です。
- developerWorksに掲載されているその他のLinux参考文献もご覧ください。
- developerWorksに掲載されているその他のOpen source参考文献もご覧ください。
Daniel Robbins氏は、ニューメキシコ州アルバカーキーに住んでいます。彼は、Gentooプロジェクトのチーフ・アーキテクト、Gentoo Technologies Inc. の社長/CEOです。著書に、Macmillanから出版されているCaldera OpenLinux Unleashed、SuSE Linux Unleashed、Samba Unleashed があります。Daniel氏は、小学2年のとき初めてLogoプログラム言語や、中毒になる恐れのあったPac Manに出会って以来、何らかの形でコンピューターに関係してきています。これで、彼がなぜSONY Electronic Publishing/Psygnosisでリード・グラフィック・アーチストを務めているかが分かるでしょう。愛妻Maryさんや、生まれたばかりの愛娘Hadassahちゃんとの時間をとても大切にしています。彼の連絡先はdrobbins@gentoo.org です。