Linux CD-ROM ゲームシステム |
||
YAMAMORI Takenori ●yamamori |
前述の流れ図 に示した通り、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 に制御が移って、システムが起動する。 そして以前のテストと同様に、ゲームが起動するはずだ。