はじめに
YoctoProjectで作成するLinuxイメージでもinitramfsを使用することができる。 これまであまりつかってこなかったので入門してみる。
使用するバージョンはkirkstone(4.0)。
環境構築
今回はqemuarm64のターゲットで実験する。
initramfsはカーネルイメージにバンドルして1つのファイルにする方法と、 別々のファイルにして、ブートローダに読み込ませる方法がある。
前者の方は、管理するファイルが少なくなり、u-bootなどのブートローダに対する設定項目も減るため、 特に理由がなければバンドルする方で問題ないと思われる。
poky
pokyを取得する。
$ ~/yocto/qemuarm64 $ git clone git://git.yoctoproject.org/poky.git -b kirkstone
環境変数の設定
$ source poky/oe-init-build-env
local.conf
Webでよく見かける使い方。local.confに下記を追加する。
MACHINE = "qemuarm64" INITRAMFS_IMAGE = "core-image-minimal-initramfs" INITRAMFS_IMAGE_BUNDLE = "1" INITRAMFS_FSTYPES = "cpio.gz"
これで、initramfsがくっついたカーネルイメージが作成されるようになる。
動作確認
ビルド
これでビルドをしてみる。
$ bitbake core-image-minimal
確かにinitramfs付きのカーネルイメージがビルドされているように見える。
ls ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin
動作確認
普通に起動したように見える。
$ runqemu nographic ... (snip) ... Poky (Yocto Project Reference Distro) 4.0.6 qemuarm64 ttyAMA0 qemuarm64 login:
initramfsを使用しているの確認したいが、痕跡がよくわからない。
先に結論を言うと、この状態ではinitramfsは使用されない。
使用するカーネルの変更
runqemuはcore-image-minimal-qemuarm64.qemuboot.conf
を参照して、QEMUを起動する。このファイルにはQEMUを起動する際に使用されるデフォルトのパラメータが記述されている。
デフォルトのカーネルイメージはImage--5.15.78+git0+f475b1a9de_a1d364fbe3-r0-qemuarm64-20230105065908.bin
となっている。
$ cat ./tmp/deploy/images/qemuarm64/core-image-minimal-qemuarm64.qemuboot.conf | grep qb_default_kernel qb_default_kernel = Image--5.15.78+git0+f475b1a9de_a1d364fbe3-r0-qemuarm64-20230105065908.bin
これは、Imageに貼られたシンボリックリンクの実体で初期状態のカーネルイメージとなっている。 つまり、initramfsはバンドルされていない。
カーネルを指定してrunqemuを実行する。
$ runqemu nographic ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin ... (snip) ... Poky (Yocto Project Reference Distro) 4.0.6 qemuarm64 ttyAMA0 qemuarm64 login:
起動した。initramfsを使用しているはずだが、痕跡を確認できない。
initramfsの構造
initramfsのディレクトリツリーを確認する。
$ pushd ./tmp/work/qemuarm64-poky-linux/core-image-minimal-initramfs/1.0-r0/rootfs $ ls bin boot dev etc home init init.d lib media mnt proc run sbin sys tmp usr var
initがinitramfsで最初に実行されるプログラムだと推測できる。
initramfsのinit
initの基本的な構造は下記のようになっている。
- 主要変数の定義
- /proc/cmdlineの解析
- modulesの実行
主要変数の定義
主要な変数の定義は下記のようになっている。注目すべきなのはMODULES_DIR=/init.d
の部分。
# Variables shared amoung modules ROOTFS_DIR="/rootfs" # where to do the switch root MODULE_PRE_HOOKS="" # functions to call before running each module MODULE_POST_HOOKS="" # functions to call after running each module MODULES_DIR=/init.d # place to look for modules EFI_DIR=/sys/firmware/efi # place to store device firmware information
/proc/cmdlineの解析
カーネルに渡されたブートパラメータは/proc/cmdline
を読むことで参照できる。
# populate bootparam environment for p in `cat /proc/cmdline`; do if [ -n "$quoted" ]; then value="$value $p" if [ "`echo $p | sed -e 's/\"$//'`" != "$p" ]; then eval "bootparam_${quoted}=${value}" unset quoted fi continue fi opt=`echo $p | cut -d'=' -f1` opt=`echo $opt | sed -e 'y/.-/__/'` if [ "`echo $p | cut -d'=' -f1`" = "$p" ]; then eval "bootparam_${opt}=true" else value="`echo $p | cut -d'=' -f2-`" if [ "`echo $value | sed -e 's/^\"//'`" != "$value" ]; then quoted=${opt} continue fi eval "bootparam_${opt}=\"${value}\"" fi done
このスクリプトではブートパラメータとして渡された内容は、bootparam_XXX
という変数として参照できるようになっている。
細かいルールは下記のようにスクリプト冒頭のコメントで説明されている。
# Boot parameters are available on environment in the as: # # 'foo=value' as 'bootparam_foo=value' # 'foo' as 'bootparam_foo=true' # 'foo.bar[=value] as 'foo_bar=[value|true]'
これを踏まえて、プリント関連の関数の実装を見てみると、下記のようになっている。
# Prints information msg() { echo "$@" >/dev/console } # Prints information if verbose bootparam is used info() { [ -n "$bootparam_verbose" ] && echo "$@" >/dev/console } # Prints information if debug bootparam is used debug() { [ -n "$bootparam_debug" ] && echo "DEBUG: $@" >/dev/console }
ブートパラメータにverbose
やdebug
を渡しておくと、initramfs内でのプリント出力が増えることが推測できる。
modulesの実行
modulesとはなにか?を一旦おいて、下記の部分を見る。
# Load and run modules for m in $MODULES_DIR/*; do # Skip backup files if [ "`echo $m | sed -e 's/\~$//'`" != "$m" ]; then continue fi module=`basename $m | cut -d'-' -f 2` debug "Loading module $module" ... (snip) ... # process module . $m if ! eval "${module}_enabled"; then debug "Skipping module $module" continue fi debug "Running ${module}_run" eval "${module}_run" ... (snip) ... done
MODULES_DIRにあるファイルを読み込んで何やら実行していることは読み取ることができる。
MODULES_DIRは/init.d
が設定されているので、下記を確認する。
$ ls ./init.d 01-udev 80-setup-live 90-rootfs 99-finish install-efi.sh install.sh
何やらスクリプトが格納されている。 moduleについては、スクリプト冒頭のコメントの下記の部分で説明されている。
# Modules need to provide the following functions: # # <module>_enabled : check if the module ought to run (return 1 to skip) # <module>_run : do what is need
modulesはinitramfsの/init.d
に格納されていて、<module>_enabled
と<module>_run
の2つの関数が実装されているスクリプトということになる。
modulesは辞書順に読み込まれるため、現状のcore-image-minimal-initramfsでは01-udevから99-finishまで順に実行される。
ちなみに、99-finishの中でswitch_rootするため、それ以降のファイルは実行されない。
出力を増やして動作確認
$ popd $ runqemu nographic ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin bootparams="verbose debug" ... (snip) ... DEBUG: Loading module udev DEBUG: Running udev_run Starting version 250.5+ [ 4.720551] EXT4-fs (vda): recovery complete [ 4.721767] EXT4-fs (vda): mounted filesystem with ordered data mode. Opts: (null). Quota mode: disabled. DEBUG: Loading module setup DEBUG: Calling module hook (pre): udev_shutdown_hook_handler DEBUG: Finished module hook (pre): udev_shutdown_hook_handler DEBUG: Running setup_run DEBUG: Loading module rootfs DEBUG: Calling module hook (pre): udev_shutdown_hook_handler DEBUG: Finished module hook (pre): udev_shutdown_hook_handler DEBUG: Running rootfs_run DEBUG: No e2fs compatible filesystem has been mounted, mounting /dev/vda... DEBUG: Loading module finish DEBUG: Calling module hook (pre): udev_shutdown_hook_handler DEBUG: Finished module hook (pre): udev_shutdown_hook_handler DEBUG: Running finish_run ... (snip) ...
initramfs中のmodulesが実行されていることが確認できた。
YoctoProjectのinitrmafsのmodulesについて
YoctoProjectのinitramfsはmodulesによって任意の処理を実行することができる。 これらのモジュールはどの様に選択、変更するのかを調査する。
core-image-minimal-initramfs.bbを参照する。
INITRAMFS_SCRIPTS ?= "\ initramfs-framework-base \ initramfs-module-setup-live \ initramfs-module-udev \ initramfs-module-install \ initramfs-module-install-efi \ " PACKAGE_INSTALL = "${INITRAMFS_SCRIPTS} ${VIRTUAL-RUNTIME_base-utils} udev base-passwd ${ROOTFS_BOOTSTRAP_INSTALL}"
PACKAGE_INSTALLがinitramfsにパッケージを追加するための変数で、INITRAMFS_SCRIPTS
を参照している。
INITRAMFS_SCRIPTSで指定されているパッケージは、initramfs-framework_1.0.bb
で提供されている。
... (snip) ... PACKAGES = "${PN}-base \ initramfs-module-exec \ initramfs-module-mdev \ initramfs-module-udev \ initramfs-module-e2fs \ initramfs-module-nfsrootfs \ initramfs-module-rootfs \ initramfs-module-debug \ initramfs-module-lvm \ initramfs-module-overlayroot \ " ... (snip) ... SUMMARY:initramfs-module-exec = "initramfs support for easy execution of applications" RDEPENDS:initramfs-module-exec = "${PN}-base" FILES:initramfs-module-exec = "/init.d/89-exec" SUMMARY:initramfs-module-mdev = "initramfs support for mdev" RDEPENDS:initramfs-module-mdev = "${PN}-base busybox-mdev" FILES:initramfs-module-mdev = "/init.d/01-mdev" SUMMARY:initramfs-module-udev = "initramfs support for udev" RDEPENDS:initramfs-module-udev = "${PN}-base udev" FILES:initramfs-module-udev = "/init.d/01-udev" SUMMARY:initramfs-module-e2fs = "initramfs support for ext4/ext3/ext2 filesystems" RDEPENDS:initramfs-module-e2fs = "${PN}-base" FILES:initramfs-module-e2fs = "/init.d/10-e2fs" SUMMARY:initramfs-module-nfsrootfs = "initramfs support for locating and mounting the root partition via nfs" RDEPENDS:initramfs-module-nfsrootfs = "${PN}-base" FILES:initramfs-module-nfsrootfs = "/init.d/85-nfsrootfs" SUMMARY:initramfs-module-rootfs = "initramfs support for locating and mounting the root partition" RDEPENDS:initramfs-module-rootfs = "${PN}-base" FILES:initramfs-module-rootfs = "/init.d/90-rootfs" SUMMARY:initramfs-module-debug = "initramfs dynamic debug support" RDEPENDS:initramfs-module-debug = "${PN}-base" FILES:initramfs-module-debug = "/init.d/00-debug" SUMMARY:initramfs-module-lvm = "initramfs lvm rootfs support" RDEPENDS:initramfs-module-lvm = "${PN}-base" FILES:initramfs-module-lvm = "/init.d/09-lvm" SUMMARY:initramfs-module-overlayroot = "initramfs support for mounting a RW overlay on top of a RO root filesystem" RDEPENDS:initramfs-module-overlayroot = "${PN}-base initramfs-module-rootfs" FILES:initramfs-module-overlayroot = "/init.d/91-overlayroot"
modulesを変更してみる
INITRAMFS_SCRIPTSの内容を修正することで実行するmodulesを変更できそうなことがわかった。
この変数はcore-image-minimal-initramfsにbbappendを作成して変更できるが、手っ取り早く試すにはlocal.conf
でも上書きすることができる。
local.confに下記の内容を追加する。
INITRAMFS_SCRIPTS = "\ initramfs-framework-base \ initramfs-module-debug \ " QB_DEFAULT_KERNEL = "Image-initramfs-qemuarm64.bin" QB_KERNEL_CMDLINE_APPEND:append = " debug shell"
QB_DEFAULT_KERNELで、runqemu実行時にinitramfs付きのカーネルを使用するように変更。 QB_KERNEL_CMDLINE_APPENDで、ブートパラメータを追加している。
initramfs-module-debug
は文字通り、initramfsをデバッグするためのmodulesで、下記のようにフックを追加している。
debug_run() { add_module_pre_hook "debug_hook_handler" add_module_post_hook "debug_hook_handler" }
フックは下記のような条件で実行される。
# Adds support to dynamic debugging of initramfs using bootparam in # following format: # shell : starts a shell before and after each module # shell=before:<module> : starts a shell before <module> is loaded and run # shell=after:<module> : starts a shell after <module> is loaded and run # # shell-debug : run set -x as soon as possible # shell-debug=before:<module> : run set -x before <module> is loaded and run # shell-debug=after:<module> : run set -x after <module> is loaded and run
今回はブートパラメータにshell
を追加しているので、他のすべてのモジュールが読み込まれる前後にsh
にフォールバックされる。
動作確認
下記のようにしてイメージを作り直し、動作確認する。
$ bitbake core-image-minimal-initramfs -c cleansstate $ bitbake core-image-minimal $ runqemu nographic
これで、ログインの前にシェルが実行されれば成功。
DEBUG: Loading module debug DEBUG: Running debug_run DEBUG: Calling module hook (post): debug_hook_handler Starting shell after debug... sh: can't access tty; job control turned off / # ls bin dev home init.d media proc run sys usr boot etc init lib mnt rootfs sbin tmp var / #
exitでシェルを抜けるときちんと次のモジュールの前と後ろで毎回シェルが実行される。
まとめ
YoctoProjectでinitramfsを使用する方法を調査した。 initramfsに関連するイメージレシピはいくつかあるが、core-image-minimal-initramfsを使用するので問題はない感じ。
その上で初期化処理を変更したりデバッグしたい場合はINITRAMFS_SCRIPTS
を上書きしてmodulesを変更する。
initramfsを使用するためにはINITRAMFS_IMAGEでinitramfsを有効化するだけでは不十分で、カーネルにバンドルした場合は、 デフォルトのカーネルイメージではなくinitramfsがバンドルされたカーネルをイメージを使用する様に、ブートローダなどの 設定を変更しておく必要がある。
DebianやUbuntuでinitramfs-toolsを使ってinitramfsをカスタマイズしたことがある人は、その作業手順や内容の違いにちょっと驚くかもしれない。