Linux CD-ROM ゲームシステム |
||
| YAMAMORI Takenori ●yamamori@kt.rim.or.jp | ||
前述の流れ図 に示した通り、CD-ROMゲームシステムは initrd を巧妙に使ってブートする。そこで、このゲームシステム用の initrd である initrd-game.img の作成方法を以下に説明する。
システムのブート時には、initrd-game が仮の“/”として最初にマウントされる。 しかしこの中には、/bin/shなど、ごく一部のコマンドしかなく、 lsコマンドすらない(ここではlsの代わりにecho * とやるのが常套手段)。 そして、この環境上でCD-ROMをマウントしなければならないのだが、 実は、initrd-game の中には mount コマンドもないのだ。
だからといって、通常のLinux上の mount コマンドを initrd-game 内にコピーしてはいけない。なぜなら、通常のmount コマンドは libc と動的リンクされているため、libc も必要になるからだ。 そして、libcはサイズが大きいため、initrd-game に入れたとしても、それがあとでブートFDに入らなくなるのだ。
そこで、静的リンクされた単機能の特別なmountコマンドを 以下のようにC言語で作成する。ファイル名はcdmount.cとした。
#include <sys/mount.h>
main(int argc, char **argv)
{
int data = 0;
return mount(argv[1], argv[2], argv[3], MS_RDONLY|MS_MGC_VAL, &data);
}
|
cdmount.cのコンパイルは、必ず以下のようにオプションを付けて行なう。
gcc -O2 -static -s -o /tmp/cdmount cdmount.c
静的リンクのバイナリを作成し、かつ不要なシンボルテーブルを ストリップするのがポイントだ。 cdmount.cでは、余分な処理は一切省略してある。 ただ、mount()が成功したかどうかの戻り値を、 main()のreturnで返すようにしてある。
ここで試しに、
# /tmp/cdmount /dev/cdrom /mnt/cdrom iso9660
と実行してみるとよい。 これで普通にCD-ROMがマウントできればOKだ。
Linuxには、/sbin/mkinitrdという、initrdを作成するための シェルスクリプトが用意されている。これは、SCSI HDDを“/”として マウントするシステムにおいて、カーネルのブート時にSCSIモジュールを ロードさせるためのinitrdを作成するのに必要になるコマンドだ。
initrdはLinuxのext2ファイルシステムイメージであり、 これをgzipで圧縮してブート時に読み込ませる。
ここでは、mkinitrdが作成するinitrdをテンプレートとして、 それに修正を加える方法でinitrd-gameを作成する。
具体的な作業手順を以下に示す。
(initrdのテンプレートの作成) ---- # /sbin/mkinitrd -v -f /boot/initrd-game.img 2.2.14-1vl6 # zcat /boot/initrd-game.img > /tmp/initrd-game ← gzip展開し、/tmp以下に置く ----
(initrdの中身の修正)
----
# mkdir /mnt/tmp
# mount -o loop /tmp/initrd-game /mnt/tmp ← ext2としてループバックマウント
# cd /mnt/tmp ← initrd-gameの内部に入る
# mkdir -p mnt/cdrom
# mv bin dev etc lib mnt/cdrom
# ln -s mnt/cdrom/* . ← bin dev etc lib のシンボリックリンク
# ln -s mnt/cdrom/usr mnt/cdrom/sbin mnt/cdrom/var mnt/cdrom/root .
↑ これは、この時点ではシンボリックリンク先が存在しないことに注意
# touch fastboot ← ブート時にfsckさせないようにする
# mkdir proc
# mkdir tmp
# chmod 1777 tmp
# cp -a /dev/hd[abcd]* /dev/scd0 /dev/sda* mnt/cdrom/dev
# cp -p /tmp/cdmount . ← 先に作成したcdmountのバイナリをここにコピーする
# vi linuxrc
… linuxrcの修正は以下参照 …
# cd / ← initrd-gameから抜ける
# umount /mnt/tmp ← ループバックマウントを解除
# /sbin/losetup -d /dev/loop0 ← /etc/mtabを変更しているため
“losetup -d”が明示的に必要。
# gzip -9 < /tmp/initrd-game > /boot/initrd-game.img ← gzip圧縮して保存する
----
linuxrcの中には、mkinitrd によってすでに記述されている行があるが、 これはそのまま残しておく。 その後ろに以下ように記述し、linuxrcを作成する。
#!/bin/sh ← ここはすでに記述されている
echo "Loading aic7xxx module" ← SCSIシステムの場合はこのような記述がある
insmod /lib/aic7xxx.o ← IDEシステムにはない。
/cdmount /dev/hdc /mnt/cdrom iso9660 ||
/cdmount /dev/hdd /mnt/cdrom iso9660 ||
/cdmount /dev/hdb /mnt/cdrom iso9660 ||
/cdmount /dev/scd0 /mnt/cdrom iso9660 ||
(
echo 'cannot mount CD-ROM, invoke /bin/sh'
/bin/sh
)
mount -t proc none /proc
echo 0x100 > /proc/sys/kernel/real-root-dev
umount /proc
|
以上、少々ややこしいが肝心なところなのでよく理解して欲しい。
ここまでの結果、initrd-gameの中身のディレクトリ構成は 以下のようになっているはずである。
/sbin -> mnt/cdrom/sbin
/bin -> mnt/cdrom/bin
/usr -> mnt/cdrom/usr
/lib -> mnt/cdrom/lib
/etc -> mnt/cdrom/etc
/var -> mnt/cdrom/var
/dev -> mnt/cdrom/dev
/root -> mnt/cdrom/root
/
/tmp/
/proc/
/
/linuxrc
/cdmount
/fastboot
/
/mnt/cdrom/bin/sh
/ /insmod
/
/lib/aic7xxx.o ← SCSIモジュール(IDEシステムの場合はない)
/
/etc/
/dev/console
/null
/hdc
/scd0
/hda1
:
|
mkinitrdが作成した/bin などのディレクトリは、/mnt/cdrom 以下にシンボリックリンクで飛ばしている。 /mnt/cdrom以下には、CD-ROMをマウントする前にも必要な、 最低限のファイルがある。 たとえば、/bin/shの実体は/mnt/cdrom/bin/shにある。
linuxrcの中で、先に作成したcdmountコマンドが実行され、 CD-ROMがマウントされる。 ここで、CD-ROMのデバイスとして/dev/hdc,hdd,hdb,scd0の順に、 cdmount をシェル文法の“||”でつないで実行していることに注意して欲しい。 つまり、IDEおよびSCSIの、可能性のあるCD-ROMデバイスを エラーコードが返らなくなるまで順番にマウントしているのである。 cdmount.cで、mount() の戻り値をちゃんと返すようにしたのはこのためである。
CD-ROMは、/mnt/cdromディレクトリに覆いかぶさるようにマウントされる。 すると、もと/mnt/cdromの下にあった/binや/dev などはもう見えなくなり、代わりにCD-ROM上の/binや/dev が見えるようになる。
ところで、すべてのデバイスについてCD-ROMのマウントが失敗した場合は、 'cannot mount CD-ROM, invoke /bin/sh'とメッセージを出して シェルを起動するようにしてある。これは、CD-ROMを焼く前に HDDでテストする際に、このシェルを利用するためだ。
CD-ROMのマウント後、linuxrcは今度はCD-ROM内の本物のmount コマンドを用いて、/procファイルシステムをマウントする。 そして、/proc内のreal-root-devに、initrd を示すデバイス番号である、0x100 を書き込んで、正規の“/”としてinitrd-game自身を指定する。
なお、initrd-game内に/fastbootという空のファイルを作って ファイルシステムがfsckされるのを防いでいるのに注意して欲しい。 これを忘れると、あとでCD-ROMのみでブートした際に、/etc/fstab にある古い記述によってHDDをfsckしようとしてしまい、 その際にデバイスが存在しないため、システムの起動が不能になってしまうのだ。
initrd-gameの内部の修正が済んだら、 umountしてからイメージファイルをgzipで圧縮し、 /boot/initrd-game.imgとして保存する。
以上でinitrd-game.imgが作成できたはずだ。 まず、このinitrd-game.imgを、HDD上のLILOを使ってブートのテストを行なう。
ここで、HDD上の/etc/lilo.confは以下のようになっているはずだ。
boot=/dev/hda
map=/boot/map
install=/boot/boot.b
prompt
timeout=50
append="apm=on"
default=linux
image=/boot/vmlinuz-2.2.14-1vl6
label=linux
read-only
root=/dev/hda1
|
このうしろに、以下のように追記する。
image=/boot/vmlinuz-2.2.14-1vl6
label=game
initrd=/boot/initrd-game.img
root=0x101 ← 実際の値は無視されるが、必ずroot=0x100以外にする
|
そしてLILOをインストールする。
# /sbin/lilo ← LILOのインストール Added linux * Added game
この状態で、HDDを使って起動テストする。 なお、この時点ではCD-ROMを挿入してはいけない。
マシンをリーブートし、LILO boot: のプロンプトにすぐに gameと入力する。 すると、vmlinuzとinitrdのロード後、initrd-game の中のlinuxrcで、 CD-ROMをマウントしようとして失敗し、シェルが起動されるはずだ。 そこで、cdmountコマンドで、HDDを以下のように手動でマウントする。
# /cdmount /dev/hda1 /mnt/cdrom ext2
=========
|
+--- 実際のHDDのパーティションに応じて変えること
この状態ではHDDはリードオンリーでマウントされており、 これが、あとでCD-ROMにするためのテストになっているのだ。
cdmountコマンドの実行直後、ls など、通常のUNIXコマンドが 使えるようになっていればOKである。
この状態からexitでシェルを抜けると、 linuxrcが終了し、/sbin/init に制御が移って、システムが起動する。 そして以前のテストと同様に、ゲームが起動するはずだ。