カーネル+initrd→RAMDISKに切り替え

USBがmountできなかった理由が判明しました。

なんとびっくり。nashのmkdirコマンドは「-p」で途中のディレクトリをすべて作る機能が入ってなかった!
nashのソースコードはこんな感じ。

int mkdirCommand(char * cmd, char * end) {
    char * dir;
    int ignoreExists = 0;

    cmd = getArg(cmd, end, &dir);

    if (cmd && !strcmp(dir, "-p")) {
        ignoreExists = 1;
        cmd = getArg(cmd, end, &dir);
    }

    if (!cmd) {
        printf("mkdir: directory expected\n");
        return 1;
    }

    if (mkdir(dir, 0755)) {
        if (!ignoreExists && errno == EEXIST) {
            printf("mkdir: failed to create %s: %d\n", dir, errno);
            return 1;
        }
    }

    return 0;
}

ご覧のとおり、「-p」は「すでにディレクトリが存在していてもエラーにしない」機能になってたり。。。

initrdにbusyboxを追加していたのでmkdirを/bin/mkdirとしてnashのmkdirを使わないように変更したら無事mountできた。

う〜ん。どうやらnashはソースコードをよく見てからじゃないと使い方がわからないらしい。


とりあえずカーネルにinitrdを忍び込ませるのはできたので、USBからRAMDISKファイルをロードしてルートファイルシステムを切り替えてみた。

gunzip -c /mnt/usb/ramdisk.img.gz > /dev/ram0
mount -t ext2 /dev/ram0 /sysroot
switchroot dummy /sysroot

ここでさらにはまってしまった。
nash はどうやらパイプ「|」が使えないっぽい。リダイレクト「>」は使えた。。。

あと、今回使ったバージョンのswitchrootは第一引数にダミー文字列を入れていないとダメっぽい。

    cmd = getArg(cmd, end, &new);
    if (cmd) {
        if (!strcmp(new, "--movedev"))
            moveDev = 1;
        cmd = getArg(cmd, end, &new);
    }

    if (!cmd) {
        printf("switchroot: new root mount point expected\n");
        return 1;
    }

nashのソースコードのこの部分がおかしい。
newに切り替え先ルートファイルシステムのトップディレクトリ(今回なら/sysroot)を指定するっぽいけど、引数に「--movedev」が入っているかどうか確認するコードのせいで第一引数が捨てられているし。
ということで、引数は無駄に2つ必要になってる。
ちなみにnashのバージョン6系のソースコードを見たらこの問題は修正されてるっぽい。
今回使ったソースは4系。なんで6系のソース使わないかって?コンパイルエラーでコケたからデス!


そんなこんなで最終的にnashのスクリプトはこんな感じなった。(initrdの/initととして保存)

#!/sbin/nash

mount -t proc /proc /proc
setquiet
echo nash script: Mounting proc filesystem
echo nash script: Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo nash script: Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo nash script: Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/rtc c 10 135
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
mknod /dev/ram0 b 1 0
sleep 10    # USBはLinuxに認識されるまでに5secくらい時間がかかるのでちょっと待ってみる
/bin/mkdir -p /mnt/usb
mknod /dev/sdb  b 8 16
mknod /dev/sdb1 b 8 17
mount -t ext3 /dev/sdb1 /mnt/usb
echo nash script: loading RAMDISK image.
gunzip -c /mnt/usb/ramdisk.img.gz > /dev/ram0
echo nash script: Mounting root filesystem.
mount -t ext2 /dev/ram0 /sysroot    # 今回使ったRAMDISKext2イメージファイル
echo nash script: Switching to new root and running init.
sleep 1
switchroot dummy /sysroot
echo nash script: Booting has failed.    # 本来ここには到達しない。エラーがあって/が切り替わらなかった場合にここにくる
/sbin/getty -L 115200 ttyS0    # シリアル(ttyS0)が使えるならここでloginできるようになるので救済される
sleep -1

initrdはいろいろ入れちゃったけど最低限以下が入っていればいけるはず。
gettyでの救済措置に対応するには追加でbin/login、etc/passwd、etc/shadow、bin/shが必要かな。

init
bin/busybox
bin/mkdir  -> busybox
bin/gunzip -> busybox
sbin/nash
proc
sys
dev

sleepとかecho、mountとかはnashのコマンドとして実装されているのでいらない。何がnashに入っているかはソースコードを見るのが一番っぽい。
ちなみにnashのコマンドに入っていないコマンドの場合は「/usr/bin:/bin:/sbin:/usr/sbin」のどこかにコマンドが入っていれば自動的にパスを探してきて実行するっぽい。nashのコマンドを使わずに自前のコマンドを実行する場合は絶対パスでコマンドを指定すれば良いっぽい。


最後にカーネルの引数はRAMDISK(容量32MB)に対応させるためにこんな感じで。

rw root=/dev/ram0 ramdisk_size=32768 console=ttyS0,115200