ルートFSのサイズが小さいLinuxでは、OSの起動時にRAM上にすべてのファイルを展開することで、 ストレージデバイスに全くアクセスしないシステム構成を捕ることができる。
これはKnoppixやPuppy Linuxなど古くから存在する手法である。下記にRAM上での動作が可能なLinuxディストリビューションの一覧を確認することができる。
https://en.wikipedia.org/wiki/List_of_Linux_distributions_that_run_from_RAM
実装方法
Casperの実装
UbuntuのLiveイメージで使用されるCaspterのtoramオプションがこのような挙動となる。その実装はここで確認することができる。
toramオプションを指定されると、copy_live_to関数が呼び出される。
この関数を紐解いていくと次のような処理を行っていることがわかる。
- ルートFSをマウント
- ルートFSのサイズをduで計算
- bcでサイズを1.3倍
- tmpfs(RAM)を
/dev/shmにマウント - ルートFSのファイルをすべて/dev/shmにコピー
- ルートFSをアンマウント
- tmpfsをルートFSがマウントされていた場所にmoveマウント
これらのことをinitramfs上で行っている。
ルートFSを構成するすべてのファイルをtmpfsでマウントされた場所にコピーすることで、ルートFSがRAM上に置かれるということを行っている。
Yocto Projectのinitramfs
Yocto Projectのinitramfsの使い方については以前紹介したとおり。
/init.dに格納されたスクリプトが順に実行されていく。90-rootfsで最終的にルートFSとなるディレクトリツリーが/rootfsに展開される。
91-toramなどのスクリプトを作成して、この/rootfsの内容をtmpfsにコピー、マウントポイントを入れ替えるようにmoveマウントすればRAM上で動作するLinuxは実現できそうだ。
環境構築
YoctoProject ラズベリーパイでinitramfsを参考に環境を作成していく。
作業環境
$ mkdir -p ~/yocto/rpi-scarthgap $ cd ~/yocto/rpi-scarthgap
pokyの取得
$ git clone -b scarthgap git://git.yoctoproject.org/poky.git
環境変数の設定
$ source poky/oe-init-build-env
meta-raspberrypiの取得
$ bitbake-layers layerindex-fetch meta-raspberrypi
local.confを編集
initramfsをバンドルしたカーネルで起動するイメージを作成する。 rootfsモジュールが実行された後にシェルにフォールバックする設定を追加している。
MACHINE = "raspberrypi4-64" # enable uart ENABLE_UART = "1" # systemd INIT_MANAGER = "systemd" # Bundle initramfs INITRAMFS_IMAGE = "core-image-minimal-initramfs" INITRAMFS_IMAGE_BUNDLE = "1" BOOT_SPACE = "1073741" INITRAMFS_MAXSIZE = "315400" IMAGE_FSTYPES_pn-${INITRAMFS_IMAGE} = "${INITRAMFS_FSTYPES}" # fallback to shell in the initramfs INITRAMFS_SCRIPTS = "\ initramfs-framework-base \ initramfs-module-debug \ bc \ " CMDLINE_DEBUG = "debug shell=after:rootfs" IMAGE_BOOT_FILES = "${BOOTFILES_DIR_NAME}/* \ ${@make_dtb_boot_files(d)} \ ${KERNEL_IMAGETYPE}-${INITRAMFS_LINK_NAME}.bin;${SDIMG_KERNELIMAGE} \ "
ビルド
RAM上に展開するので一番小さいイメージを作成する。
$ bitbake core-image-minimal
マイクロSDの作成
出来上がったwicイメージをSDカードに書き込む。
$ pushd ./tmp/deploy/images/raspberrypi4-64 $ sudo bmaptool copy ./core-image-minimal-raspberrypi4-64.wic.bz2 /dev/sdX
ディスクレスの確認
手動でディスクレス設定
作成したイメージでターゲットを輝度すると、rootfsモジュールが実行された後医にinitramfsのシェルに入る。 そこで下記のコマンドを実行する。
$ size=$(du -sk "/rootfs" | awk '{print $1}') $ size=$(echo "($size * 1.3)/1" | bc) $ mkdir -p /dev/shm $ mount -t tmpfs -o size="$size"k tmpfs /dev/shm $ cp -a /rootfs/* /dev/shm $ umount /rootfs $ mount -o move /dev/shm /rootfs $ sed -i 's|/dev/mmcblk0p1|#/dev/mmcblk0p1|' /rootfs/etc/fstab $ exit
そのままではSDカードのブートパーティションが/bootにマウントされてしまうので、/rootfs/etc/fstabを書き換えている。
ディスクレスの動作確認
rootでログインしてmountコマンドを実行するとSDカード(/dev/mmcblk1pX)がマウントされていないことが確認できる。
Poky (Yocto Project Reference Distro) 5.0.7 raspberrypi4-64 ttyS0 raspberrypi4-64 login: root WARNING: Poky is a reference Yocto Project distribution that should be used for testing and development purposes only. It is recommended that you create your own distribution for production use. root@raspberrypi4-64:~# mount proc on /proc type proc (rw,relatime) sysfs on /sys type sysfs (rw,relatime) devtmpfs on /dev type devtmpfs (rw,relatime,size=1664796k,nr_inodes=416199,mode=755) tmpfs on / type tmpfs (rw,relatime,size=103444k) securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=666) tmpfs on /run type tmpfs (rw,nosuid,nodev,size=776204k,nr_inodes=819200,mode=755) cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursivepr) bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700) mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime) debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime) tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime) tmpfs on /tmp type tmpfs (rw,nosuid,nodev,nr_inodes=1048576) configfs on /sys/kernel/config type configfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /var/volatile type tmpfs (rw,relatime)
この状態でSDカードを抜くと、割り込みが上がりSDカードが抜かれたことが検出される。
root@raspberrypi4-64:~# [ 272.895023] mmc0: card 1234 removed
その後、lsなどでファイルシステムにアクセスしても問題なく動作する。
root@raspberrypi4-64:/# ls /
bin etc lost+found proc sbin tmp
boot home media root srv usr
dev lib mnt run sys var
この状態で試しに電源LED(赤)を点滅させてみるときちんと動作することが確認できる。
root@raspberrypi4-64:/# echo timer > /sys/class/leds/PWR/trigger
レシピ化
実機上でディスクレスが実現できることが確認できたのでこれをレシピ化する。
レイヤの作成
Casperのtoram機能を模倣するのでmeta-toramを作成する。
$ bitbake-layers create-layer -p10 -a ../poky/meta-toram
レシピの作成
格納場所
他のinitramfsモジュールと同じ構成する。
$ mkdir -p ../poky/meta-toram/recipes-core/initrdscripts/initramfs-module-toram
スクリプト
実際に処理を行うスクリプトをmeta-toram/recipes-core/initrdscripts/initramfs-module-toram配下にtoram.shとして作成する。
#!/bin/sh -e toram_enabled() { return 0 } toram_run() { size=$(du -sk "$ROOTFS_DIR" | awk '{print $1}') size=$(echo "($size * 1.3)/1" | bc) mkdir -p /dev/shm mount -t tmpfs -o size="$size"k tmpfs /dev/shm cp -a $ROOTFS_DIR/* /dev/shm umount $ROOTFS_DIR mount -o move /dev/shm $ROOTFS_DIR }
レシピ
レシピはmeta-toram/recipes-core/initrdscripts配下に,initramfs-module-toram_0.1.bbとして作成する。initramfs上に本番用のルートFSをマウントするモジュールが90-rootfsなので、それより後にtoram.shを動作させるために91-toramとしてインストールしている。また、bcコマンドを使用するので依存関係を設定している。
SUMMARY = "initramfs-framework module for toram" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420" RDEPENDS:${PN} = "initramfs-framework-base bc" SRC_URI = "file://toram.sh" S = "${WORKDIR}" do_install() { install -d ${D}/init.d install -m 0755 ${S}/toram.sh ${D}/init.d/91-toram } FILES:${PN} = "/init.d/91-toram"
wksファイル
手動で試したときには/bootがマウントされるのを回避するためにfstabを修正した。
しかし、toram実現の処理とは直接関係ないためtoram.shで実装することは避ける。
/bootがマウントされるのはmeta-raspberrypiのsdimage-raspberrypi.wksで下記の行が記述されているため。
part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 100
格納ディレクトリを作成する。
$ mkdir -p ../poky/meta-toram/wic
sdimage-raspberrypi-toram.wksを以下の内容で作成する。
ブートパーティションの行からマウントポイントの設定を削除している。
part --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 100 part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096
ビルド
local.conf
local.confのディスクレス設定の追加部分は下記のようになる。
MACHINE = "raspberrypi4-64" # enable uart ENABLE_UART = "1" # systemd INIT_MANAGER = "systemd" # Bundle initramfs INITRAMFS_IMAGE = "core-image-minimal-initramfs" INITRAMFS_IMAGE_BUNDLE = "1" BOOT_SPACE = "1073741" INITRAMFS_MAXSIZE = "315400" IMAGE_FSTYPES_pn-${INITRAMFS_IMAGE} = "${INITRAMFS_FSTYPES}" # fallback to shell in the initramfs INITRAMFS_SCRIPTS = "\ initramfs-module-toram \ " CMDLINE_DEBUG = "debug shell=after:rootfs" IMAGE_BOOT_FILES = "${BOOTFILES_DIR_NAME}/* \ ${@make_dtb_boot_files(d)} \ ${KERNEL_IMAGETYPE}-${INITRAMFS_LINK_NAME}.bin;${SDIMG_KERNELIMAGE} \ " WKS_FILE = "sdimage-raspberrypi-toram.wks"
ビルド
$ bitbake core-image-minimal
動作確認
mmcblk1がマウントされていないことが確認できる。
root@raspberrypi4-64:~# mount proc on /proc type proc (rw,relatime) sysfs on /sys type sysfs (rw,relatime) devtmpfs on /dev type devtmpfs (rw,relatime,size=1664796k,nr_inodes=416199,mode=755) tmpfs on / type tmpfs (rw,relatime,size=103112k) securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=666) tmpfs on /run type tmpfs (rw,nosuid,nodev,size=776204k,nr_inodes=819200,mode=755) cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursivepr) bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700) mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime) debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime) tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime) tmpfs on /tmp type tmpfs (rw,nosuid,nodev,nr_inodes=1048576) configfs on /sys/kernel/config type configfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /var/volatile type tmpfs (rw,relatime)
まとめ
起動時にルートFSの内容をtmpfsにコピーしてそこをルートFSとしてマウントすることで、システム全体をRAM上で動作扠せられることを確認した。 今回はブートにSDカードを使用したが、ネットワークからブートするなどシーケンスを工夫すれば完全にディスクレスでブートできる可能性がある。 tmpfsにコピーできれば良いので、ルートFSはsquashfsイメージなどでも良い。
initramfs自体自由度が高いため実装によっては様々な構成に対応することができる。
Yocto Projectのinitramfsのフレームワークはわかりやすい実装になっているため、比較的簡単にこのようなことが実現できるのは興味深い。