Linuxには、サードパーティが提供しているディストリビューションを利用することが一般的ですが、ディストリビューションによって、デスクトップ用、サーバー用など様々な利用形態を、ディストリビューションを提供している組織が想定する利用環境に応じて作成しています。
その中には、組み込みシステム用ディストリビューションなども存在し、有償サポートを利用することによって、ユーザーが想定している組み込みシステムの構築を行っている組織もあります。
組み込みシステムでは、サーバーやルーターといったネットはあるが画面の無い環境、ビデオレコーダーのように画面はあるがネットはない環境、自動販売機や工作機器のように画面もネットも無い環境など、様々な形態が考えられるので、ディストリビューションの、画面を見ながらCD-ROMからインストールなどのイメージとは全く違うLinuxのインストールが必要になります。
このサイトでは、Buildrootという、ソースコードディストリビューションを利用して、組み込みシステムを構築していく方法を解説していきます。Buildrootは一般的なデスクトップのディストリビューションとは異なり、ソースコードオンリーのディストリビューションで、組み込みシステムにおいて必要最低限のツールをコンパイル、パッケージにするだけでなく、必要なクロスコンパイラ等もコンパイル環境に合わせて構築されるために、かなり厳密に組み込みファームウェアのバージョン管理等も行うことができます。
また、構造も単純なため、x86のPC以外でも、各種チップメーカーから出されているリファレンスコードをBuildrootのソースツリーに組み込むことも比較的単純に行うことができます。
Initプロセス
Linuxカーネルは、起動すると、ルートファイルシステムをマウントし、”init”というプログラムを起動します。”init”は/etc/inittabファイルを参照し、それに記載された動作を行います。
※一部の解説書では”init”が /etc/init.d/rcXXを読み込んでと書いてあるものもありますが、”init”が読むのは/etc/inittabだけです。/etc/inittabの中で、/etc/init.d/rcXXを実行しろと書いてあれば、それを読み込みますし、/etc/hogehogeを読めと書いてあればそちらを読みます。
とりあえず、Buildroot標準の/etc/inittabを見てみましょう。
# /etc/inittab
# # Copyright (C) 2001 Erik Andersen # # Note: BusyBox init doesn’t support runlevels. The runlevels field is # completely ignored by BusyBox init. If you want runlevels, use # sysvinit. # # Format for each entry: # # id == tty to run on, or empty for /dev/console # runlevels == ignored # action == one of sysinit, respawn, askfirst, wait, and once # process == program to run # Startup the system ::sysinit:/bin/mount -t proc proc /proc ::sysinit:/bin/mount -o remount,rw / ::sysinit:/bin/mkdir -p /dev/pts ::sysinit:/bin/mkdir -p /dev/shm ::sysinit:/bin/mount -a ::sysinit:/bin/hostname -F /etc/hostname # now run any rc scripts ::sysinit:/etc/init.d/rcS # Put a getty on the serial port console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL # Stuff to do for the 3-finger salute #::ctrlaltdel:/sbin/reboot # Stuff to do before rebooting ::shutdown:/etc/init.d/rcK ::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r |
最初のコメント欄を見ると、このファイルはBuildrootではなくてBuildrootで使用しているBusybox由来のファイルであることが分かります。
システム起動直後に実行すべき項目は、sysinitとして記載されています。
::sysinit:/bin/mount -t proc proc /proc
ここでは、最初にporcファイルシステムをマウントしています。
::sysinit:/bin/mount -o remount,rw /
この項目は、ルートファイルシステムを書き込み可能としてマウントしなおしています。これを実行しないと、次のmkdirコマンドが実行できません。
::sysinit:/bin/mkdir -p /dev/pts
ここでは、ptsデバイスを配置するためのディレクトリを作成しています。
::sysinit:/bin/mkdir -p /dev/shm
ここでは、shmデバイスを配置するためのディレクトリを作成しています。
::sysinit:/bin/mount -a
/etc/fstabに記載されているファイルシステムのマウントを行います。
::sysinit:/bin/hostname -F /etc/hostname
Hostnameの設定を行っています。
# now run any rc scripts
::sysinit:/etc/init.d/rcS
ここから、/etc/init.d以下のコマンドを実行して、デーモンの起動を行います。
ptsや、shmを使用しないのであれば、2-4行目は削除しても問題ありません。
idのフィールドは、デバイス名を記述します。起動後に、シリアルポートにもログインプロンプトを表示したい場合などでは、idに/dev/ttyS1などと書きます。
次は、runlevelのフィールドですが、Busyboxの/etc/inittabでは、runlevelを無視していることがわかります。Runlevelの概念は、初期のUNIXシステムでは、カーネル起動時のオプションが数字1文字しか指定できなかったために、それを用いて、システムの起動条件を変えるために使用していたのですが、最近のLinux(UNIX)システムでは、カーネル起動時の引数をいろいろ指定することができるので、わざわざrunlevelとして使用する必要もなくなりました。今でも、デスクトップシステムでは、GUIログインと、CUIログインを区別するために利用されることもあるようですが、ほかの引数で代用することも可能ですので、なくても問題ないでしょう。“:”のフォーマットは、他のシステムの/etc/inittabとの互換性のため残してあるようです。
次のフィールドは、起動条件になります。sysinitは起動時に一回だけ実行、respwanは実行したプログラムが終了したら再度実行(ログインプロンプトを出す場合などに使用)、ctlaltdelは Ctrl+Alt+Delが押された場合に実行するプログラムを指定、shutdownはシャットダウンを行う場合に実行するファイルを指定します。
これらによると、起動時は、特殊なファイルシステムのマウントを行い、ホスト名を設定した後で、/etc/init.d/rcSを実行するように指定されれてます。起動後は、コンソールにログインプロンプトを表示し、ログアウトされたら再度表示。Ctrl+Alt+Delが押された場合は、rebootコマンドを実行。シャットダウン時には、/etc/init.d/rcKスクリプトの実行後スワップを無効にしています。
※組み込みシステムでは、”init”自体を置き換えてしまうことも可能です。8Bitマイコンから、Linux環境に移行してきた場合などは、APIやドライバはカーネル提供の物を使い、アプリケーションや、ファイルシステムなどは必要ないとした場合に、”init”をユーザーのプログラムに置き換えてしまうことによって、非常にシンプルな構成のシステムとすることも可能です。8Bitマイコンのプログラムは、それ自体でメモリ管理や、割り込み制御などを行っているので、単純に移植だけをするときなどは、移植のための工数を最小限にすることができます。
次に、inittabで指定されている/etc/init.d/rc.Sを見ていきましょう。
#!/bin/sh
# Start all init scripts in /etc/init.d # executing them in numerical order. # for i in /etc/init.d/S??* ;do # Ignore dangling symlinks (if any). [ ! -f “$i” ] && continue case “$i” in *.sh) # Source shell script for speed. ( trap – INT QUIT TSTP set start . $i ) ;; *) # No sh extension, so fork subprocess. $i start ;; esac done |
/etc/init.d/rc.Sでは、/etc/init.d以下の Sで始まっているファイルを、アルファベット順に実行していってます。そのうち、.shで終わるものは、再帰的にshellを起動せずに実行し、その他のファイルは、startオプションをつけて実行していってます。この再帰的にshellを実行しないことによる速度アップはたいしたことないと思われますので、あまり気にしなくてもいいでしょう。
このような方式をとるメリットとしては、あるサービスを起動するにあたって、例えばWebサービスはネットワークが起動した後で起動したいとした場合、Webサービスのプログラムのインストーラーは、他のスクリプトの存在や、他のスクリプトの中身を気にせずに、S41以降の番号を起動スクリプトにつけておけばいいということになるので、デスクトップOSのように、ユーザーがプログラムを必要に応じてインストールするような環境では有利に働きます。
一方、組み込みシステムのように、ユーザーがプログラムを追加することは考えなくていいシステムであれば、こういった構成をとる必要はありません。(こちらが使いやすいと思えば、このままでもいいし、他の構成をとった方がいいのであれば、他の構成をとることもできます。)
ディストリビューションで用いられているinitrd
ディストリビューションで用いられているLinuxのブートプロセスは、
ブートローダー
カーネル、initrdの読み込み カーネル起動 カーネル initrdをルートファイルシステムとしてマウント initrdの中の”init”を起動 init initrdの中のドライバをロード inittab中で、ロードしたドライバを用いてDISKドライブをマウント DISKドライブをルートファイルシステムに変更 DISKドライブ中のrcスクリプトを実行 |
といった手順で初期化されていきますが、今回のシステムでは、
ブートローダー
カーネル、initrdの読み込み カーネル起動 カーネル initrdをルートファイルシステムとしてマウント initrdの中の”init”を起動 init initrdの中のドライバをロード initrdの中のrcスクリプトを実行 |
といった手順となります。つまり、ルートファイルシステムは、initrdのままアプリケーションを実行することになります。これによって、ディスクドライブがない状況でLinuxが動作することになり、ファイルシステムの破損などを考慮する必要がなくなります。