2012/11/04

DreamPlugのinitramfsを作ってみた

内蔵のmicroSDへの書き込みを減らしたくてLiveCDの様に読み出しはSDから
書き込みはメモリへ書くようにしてみようかと思ってやってみた
一部のパーティションだけなら簡単なんだろうけどrootまるごとやろうと思うと
/をマウントする前に下準備をしないといけないですね
という訳でinitramfsを作ってみました

書き込みをメモリに逃がす方法は以下のページにあるようにいろいろありますが
今回は特にパッチも必要なさそうなdevice-mapperのsnapshotを使用する方法で試しました
番外編: LiveCDのファイルシステム(1) - O'Reilly Japan Community Blog

目標としては root ファイルシステムがある /dev/sda2 は読み込み
書き込みは tmpfs で確保した領域に書き込みます
したがって再起動時に tmpfs が開放されるので再起動後は元の状態に戻ります
また必要に応じて通常通り /dev/sda2 を書き込みでマウントする起動方法も選べるようにします

device-mapper で扱うにはブロックデバイスである必要があるみたいなので
最終的には以下のような構成になります
/dev/sda2 (ro) -------+
                      |
tmpfs -> cow.img (rw) ---> /dev/mapper/rootfs (rw) -> / としてマウント

まずカーネルで以下の項目を有効にする必要があります
当然全て組み込みにしておいたほうがいいと思います
General setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support
Device Drivers -> Generic Driver Options -> Maintain a devtmpfs filesystem to mount at /dev
Device Drivers -> Multiple devices driver support (RAID and LVM) -> Device mapper support
Device Drivers -> Multiple devices driver support (RAID and LVM) -> Device mapper support -> Snapshot target

次に必要なパッケージをインストールします
# emerge -v app-arch/cpio dev-embedded/u-boot-tools sys-fs/lvm2
# USE=static emerge -v sys-apps/busybox

これで必要なものは一式揃ったので initramfs のシステムの作成とスクリプトを書きます
以下のページが非常に参考になります
Initramfs - Gentoo Linux Wiki
Early Userspace Mounting - Gentoo Wiki

ディレクトリ構造を /usr/src/initramfs 以下に作成していきます
# mkdir /usr/src/initramfs
# cd /usr/src/initramfs

まず必要なディレクトリの作成
/mnt/root に最終的な root ファイルシステムをマウント
/mnt/cowfs は tmpfs をマウントしてその中にディスクイメージを作成します
# mkdir -p bin dev etc mnt/cowfs mnt/root proc sbin sys

次に必要なコマンドをコピー
busybox と dmsetup のスタティックビルドされているものが必要です (ldd で確認しています)
# which busybox
/bin/busybox
# ldd /bin/busybox 
        not a dynamic executable
# cp /bin/busybox bin/
# which dmsetup.static
/sbin/dmsetup.static
# ldd /sbin/dmsetup.static 
        not a dynamic executable
# cp /sbin/dmsetup.static sbin/

コマンドはコピーしたのであとは init を書きます
今回は以下のようにしました
#!/bin/busybox sh

rescue_shell() {
    echo fallback to busybox shell
    exec /bin/sh
}

mount -t proc none /proc || rescue_shell
mount -t sysfs none /sys || rescue_shell
mount -t devtmpfs none /dev || rescue_shell

/bin/busybox --install -s /bin

for c in $(cat /proc/cmdline); do
    case $c in
    root=*)
        ROOT=$(echo $c | cut -d= -f2-)
        ;;
    rootdelay=*)
        DELAY=$(echo $c | cut -d= -f2-)
        ;;
    snapshot)
        SNAPSHOT=y
        ;;
    esac
done

if [ -z "$ROOT" ]; then
    echo root is empty
    rescue_shell
fi

if [ -n "$DELAY" ]; then
    echo waiting for rootfs $DELAY sec
    sleep $DELAY
fi

if [ -z "$SNAPSHOT" ]; then
    mount -o ro $ROOT /mnt/root || rescue_shell
else
    mount -t tmpfs none /mnt/cowfs || rescue_shell
    dd if=/dev/zero of=/mnt/cowfs/cow.img bs=1 count=1 seek=$(blockdev --getsize64 $ROOT) || rescue_shell
    LOOP=$(losetup -f)
    losetup $LOOP /mnt/cowfs/cow.img
    dmsetup.static create rootfs --table "0 $(blockdev --getsz $ROOT) snapshot $ROOT $LOOP P 0" || rescue_shell

    mount /dev/mapper/rootfs /mnt/root || rescue_shell
    if [ ! -d /mnt/root/mnt/cowfs ]; then
        mkdir -p /mnt/root/mnt/cowfs
    fi
    sed -i "s|^$ROOT|#\0|" /mnt/root/etc/fstab
    mount -o remount,ro /dev/mapper/rootfs /mnt/root || rescue_shell
    mount --move /mnt/cowfs /mnt/root/mnt/cowfs || rescue_shell
fi

umount proc sys dev

exec switch_root /mnt/root /sbin/init

肝心な部分は以下で
else
    mount -t tmpfs none /mnt/cowfs || rescue_shell
    dd if=/dev/zero of=/mnt/cowfs/cow.img bs=1 count=1 seek=$(blockdev --getsize64 $ROOT) || rescue_shell
    LOOP=$(losetup -f)
    losetup $LOOP /mnt/cowfs/cow.img
    dmsetup.static create rootfs --table "0 $(blockdev --getsz $ROOT) snapshot $ROOT $LOOP P 0" || rescue_shell

    mount /dev/mapper/rootfs /mnt/root || rescue_shell
    if [ ! -d /mnt/root/mnt/cowfs ]; then
        mkdir -p /mnt/root/mnt/cowfs
    fi
    sed -i "s|^$ROOT|#\0|" /mnt/root/etc/fstab
    mount -o remount,ro /dev/mapper/rootfs /mnt/root || rescue_shell
    mount --move /mnt/cowfs /mnt/root/mnt/cowfs || rescue_shell
fi
  1. まず /mnt/cowfs に tmpfs をマウント
  2. 次に dd でスパースファイルを作成、この際 root のデバイスと同じサイズにする
    (ここは tmpfs と同じサイズにしたほうがいいかもしれない)
  3. losetup を使用して作成したディスクイメージをブロックデバイスとひもづけ
  4. dmsetup の snapshot を使用して新たなデバイスを作成
    (成功すると /dev/mapper/rootfs が作成される)
  5. 起動後のシステムから /mnt/cowfs が見えるようにするために一度 rw でマウントしてディレクトリを作成
  6. /etc/fstab を編集して root をマウントしないようにする
  7. ro でリマウント (必要ないかも)
  8. mount --move を使用して /mnt/cowfs を起動後も見える位置へ移動

というような具合になっています
起動時のカーネルのコマンドラインに snapshot を付けるとここに入ってきて付けなければ
従来通り rw でマウントして起動するようになっています (if の部分)

init を忘れず実行可能にして必要なファイルは揃ったのでアーカイブにまとめてイメージを作成します
PC の場合は cpio.gz のファイルで起動できた気がするのですが U-Boot はそれだけではダメで
更にイメージに変換する必要がありました
Gentoo Forums :: View topic - [Solved] Help with initramfs for Marvell OpenRD-Client LE
# chmod +x init
# find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
# mkimage -A arm -O linux -T ramdisk -C gzip -n "Gentoo Busybox" -d ../initramfs.cpio.gz /boot/initramfs.kwb

以上でようやく必要なファイルが全部揃いました
上記で /boot/initramfs.kwb にコピーしていますが /boot は /dev/sda1 です
U-boot でこのファイルを読み込んで起動するように設定すれば完了です
カーネルの引数に snapshot を追加するのと initramfs の読み込み
bootm に kernel initramfs dtb の順にアドレスを渡すようにします
以下に自分の環境の例をあげます

Marvell>> print
baudrate=115200
bootcmd=${x_bootcmd_usb}; ${x_bootcmd_kernel}; ${x_bootcmd_init}; ${x_bootcmd_dtb}; setenv bootargs ${x_bootargs} ${x_bootargs_snapshot}; bootm 0x6400000 0x6700000 0x6900000
bootdelay=3
eth1addr=xx:xx:xx:xx:xx:xx
ethact=egiga0
ethaddr=xx:xx:xx:xx:xx:xx
stderr=serial
stdin=serial
stdout=serial
x_bootargs=console=ttyS0,115200 root=/dev/sda2 rootdelay=3
x_bootargs_snapshot=snapshot
x_bootcmd_dtb=ext2load usb 0 0x6900000 kirkwood-dreamplug.dtb
x_bootcmd_init=ext2load usb 0 0x6700000 initramfs.kwb
x_bootcmd_kernel=ext2load usb 0 0x6400000 uImage
x_bootcmd_usb=usb start

Environment size: 618/4092 bytes

と、これでおおよそ期待通りに動いているんですが
いくつか問題があります

まずメモリには限りがあるのでそれを超えるデータを書き込んだ場合正常に動作しなくなります
例えばディスクの空き領域が1Gでメモリ上に確保した領域が10Mだった場合
書き込みは全てメモリへ行われるので当然10MBまでしか書き込めません
システムはまだ990MB空いていると思っているのでおかしなことになるのだと思われます

他に、新たにファイルを作成してメモリ上へ行われた書き込みは
ファイルを削除しても開放されないという事です
ディスクイメージをスパースファイルで作成しましたがこれの動作によるものです
なので10MBのファイルを作成したあとにそれを削除してもメモリは確保されたままです
これによってひとつめの問題が起こりやすくなっています

最後に、終了時に /mnt/cowfs をアンマウントできないと言われます
これはまぁ当然と言えば当然なのですが (/ より上位でマウントしているため)
/ をアンマウントしてからでないとアンマウントできないですよね
これについてはもしかしたら解決方法があるのかもしれないですが今のところわかっていません

0 件のコメント:

コメントを投稿