この記事はLinux Advent Calendar 2020 - Qiitaの1日目の記事です。
Tiny Core Linux(以下tcl)を使ってLinuxのブートプロセスを見てましょう。Tiny Core Linuxは軽量ディストリビューションで最小のisoイメージだと11MBほどです😃
ブートプロセスを見ると言っても電源onからの流れではなくてinitプロセスの実行に関する部分です。
この記事ではバージョン11.1のCore-current.iso を利用しています。
www.tinycorelinux.net
isoファイルの構成
まずはtclのisoがどんな感じで構成されていて、Linuxを起動させるのか確認しましょう。
isoファイルの構成はこのようになっています。
$ sudo mount -o loop Core-current.iso ./mnt
$ tree ./mnt/
./mnt/
└── boot
├── core.gz
├── isolinux
│ ├── boot.cat
│ ├── boot.msg
│ ├── f2
│ ├── f3
│ ├── f4
│ ├── isolinux.bin
│ └── isolinux.cfg
└── vmlinuz
2 directories, 9 files
vmlinuzはカーネル、core.gzはinitramfsのファイル、そして起動にはisolinuxを使っているのでそのファイル類が含まれています。
起動の設定はisolinux.cfgに書かれています。
display boot.msg
default microcore
label microcore
kernel /boot/vmlinuz
initrd /boot/core.gz
append loglevel=3
label mc
kernel /boot/vmlinuz
append initrd=/boot/core.gz loglevel=3
implicit 0
prompt 1
timeout 300
F1 boot.msg
F2 f2
F3 f3
F4 f4
Tiny Core Linuxnのinitramfsの中身
今回はisoファイルの内容を作業用のディレクトリを作ってそこでファイルを読んだりしていきます。
$ mkdir tcl-work
$ cp -a mnt/boot/ tcl-work/.
core.gzの中も展開します。core.gzはcpioファイルをgzipで圧縮したものなので展開してからcpioコマンドで中身を取り出します。
$ cd tcl-work
$ mkdir core-work
$ cd core-work
$ zcat ../../mnt/boot/core.gz | sudo cpio -i -H newc -dv
展開するとこのようになります。
$ ls -la
total 72
drwxrwxr-x. 17 masami masami 4096 Nov 22 10:30 ./
drwxrwxr-x. 4 masami masami 4096 Nov 22 10:30 ../
drwxr-xr-x. 2 masami masami 4096 Nov 22 10:30 bin/
drwxrwxr-x. 7 masami masami 4096 Nov 22 10:30 dev/
drwxr-xr-x. 8 masami masami 4096 Nov 22 10:30 etc/
drwxrwxr-x. 2 masami masami 4096 Nov 22 10:30 home/
-rwxr-xr-x. 1 masami masami 496 Nov 22 10:30 init*
drwxr-xr-x. 4 masami masami 4096 Nov 22 10:30 lib/
lrwxrwxrwx. 1 masami masami 11 Nov 22 10:30 linuxrc -> bin/busybox*
drwxrwxr-x. 2 masami masami 4096 Nov 22 10:30 mnt/
drwxrwsr-x. 2 masami masami 4096 Nov 22 10:30 opt/
drwxrwxr-x. 2 masami masami 4096 Nov 22 10:30 proc/
drwxrwxr-x. 2 masami masami 4096 Nov 22 10:30 root/
drwxrwxr-x. 3 masami masami 4096 Nov 22 10:30 run/
drwxr-xr-x. 2 masami masami 4096 Nov 22 10:30 sbin/
drwxrwxr-x. 2 masami masami 4096 Nov 22 10:30 sys/
drwxrwxrwt. 2 masami masami 4096 Nov 22 10:30 tmp/
drwxr-xr-x. 7 masami masami 4096 Nov 22 10:30 usr/
drwxrwxr-x. 8 masami masami 4096 Nov 22 10:30 var/
システムの起動に関連しそうなファイルとして /linuxrcと/initがありますね。linuxrcはbusyboxコマンドへのリンクで/initはシェルスクリプトになっています。このファイルの内容は後で見てみます。
ちなみにfedora 33のinitramfsはこのようになっていて/linuxrcはありません。
$ /usr/lib/dracut/skipcpio ./initramfs-5.9.9-200.fc33.x86_64.img | zcat | cpio -idv
$ ls -la
total 32680
drwxrwxr-x. 12 masami masami 4096 Nov 22 02:07 ./
drwxrwxr-x. 5 masami masami 4096 Nov 22 12:45 ../
lrwxrwxrwx. 1 masami masami 7 Nov 22 02:07 bin -> usr/bin/
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 dev/
drwxr-xr-x. 11 masami masami 4096 Nov 22 02:07 etc/
lrwxrwxrwx. 1 masami masami 23 Nov 22 02:07 init -> usr/lib/systemd/systemd*
lrwxrwxrwx. 1 masami masami 7 Nov 22 02:07 lib -> usr/lib/
lrwxrwxrwx. 1 masami masami 9 Nov 22 02:07 lib64 -> usr/lib64/
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 proc/
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 root/
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 run/
lrwxrwxrwx. 1 masami masami 8 Nov 22 02:07 sbin -> usr/sbin/
-rwxr-xr-x. 1 masami masami 3418 Nov 22 02:07 shutdown*
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 sys/
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 sysroot/
drwxr-xr-x. 2 masami masami 4096 Nov 22 02:07 tmp/
drwxr-xr-x. 8 masami masami 4096 Nov 22 02:07 usr/
drwxr-xr-x. 3 masami masami 4096 Nov 22 02:07 var/
では/linuxrcと/initはどのように使われるのでしょうか。/initや/linuxrcが利用される仕組みはEarly userspace support — The Linux Kernel documentationに説明があります。tclではinitramfsが利用されてます。この場合、以下の部分が該当します。/initが必須ということですね。
using initramfs. The call to prepare_namespace() must be skipped. This means that a binary must do all the work.
Said binary can be stored into initramfs either via modifying usr/gen_init_cpio.c or via the new initrd format, an cpio archive. It must be called “/init”.
This binary is responsible to do all the things prepare_namespace() would do.
起動時のログからもinitramfsが利用されていることがわかります。
tc@box:~$ dmesg | grep initramfs
Trying to unpack rootfs image as initramfs...
Tiny Linux Coreで起動シーケンスの確認
ではこれらの動作を確認してみましょう。
と、その前にqemuのGUIを立ち上げるとコンソール出力が見にくいのでシリアルコンソールを有効にしてしまいます。
編集するのはisolinux.confです。このファイルにラベルは2つありますがデフォルトはmicrocoreのほうなのでこちらに設定しましょう。console=ttyS0,115200をappendの行に追加します。
ついでなのでログレベルも最大にしてしまいます。そうするとこのような感じになります。
append loglevel=7 console=ttyS0,115200
つぎにinitramfsのファイルを弄りましょう。シリアルコンソールの設定をetc/inittabに追加します。
ttyS0::respawn:/sbin/getty -nl /sbin/autologin 115200 ttyS0 vt102
そうしたら本題の/iniitと/linuxrcをrenameしてみます。
-rwxr-xr-x. 1 root root 496 Nov 22 12:14 init.bk*
drwxr-xr-x. 4 root root 4096 Nov 22 12:14 lib/
lrwxrwxrwx. 1 root root 11 Nov 22 12:14 linuxrc.bk -> bin/busybox*
core.gzを作成しましょう。
$ sudo find | sudo cpio -o -H newc | gzip -2 > ../boot/core.gz
これで新しいintramfsのイメージもできたのでisoイメージを作成します。 実行はtcl-workディレクトリの1つ上の場所で行います。
$ sudo mkisofs -l -J -R -r -V TC-custom -no-emul-boot -boot-load-size 4 \
-boot-info-table -b boot/isolinux/isolinux.bin \
-c boot/isolinux/boot.cat -o test.iso tcl-work/
これでisoファイルが出来たのでqemuで起動します。
$ qemu-system-x86_64 -boot d -cdrom test.iso -m 512 -nographic
見事にカーネルパニックします😃
Floppy drive(s): fd0 is 2.88M AMI BIOS
FDC 0 is a S82078B
blk_update_request: I/O error, dev fd0, sector 0 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
floppy: error 10 while reading block 0
VFS: Cannot open root device "(null)" or unknown-block(2,0): error -6
Please append a correct "root=" boot option; here are the available partitions:
0100 8192 ram0
(driver?)
0101 8192 ram1
(driver?)
0102 8192 ram2
(driver?)
0103 8192 ram3
(driver?)
0104 8192 ram4
(driver?)
0105 8192 ram5
(driver?)
0106 8192 ram6
(driver?)
0107 8192 ram7
(driver?)
0b00 1048575 sr0
driver: sr
0200 4 fd0
driver: floppy
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
Kernel Offset: disabled
Rebooting in 60 seconds..
まずは/linuxrcの名前だけ戻して起動させます。
VFS: Cannot open root device "(null)" or unknown-block(2,0): error -6
Please append a correct "root=" boot option; here are the available partitions:
0100 8192 ram0
(driver?)
0101 8192 ram1
(driver?)
0102 8192 ram2
(driver?)
0103 8192 ram3
(driver?)
0104 8192 ram4
(driver?)
0105 8192 ram5
(driver?)
0106 8192 ram6
(driver?)
0107 8192 ram7
(driver?)
0b00 1048575 sr0
driver: sr
0200 4 fd0
driver: floppy
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
Kernel Offset: disabled
そうするとカーネルパニックとなりました。ではlinuxrcの名前は変更した状態で、/initのほうは名前を戻して起動してみましょう。
( '>')
/) TC (\ Core is distributed with ABSOLUTELY NO WARRANTY.
(/-_--_-\) www.tinycorelinux.net
tc@box:~$ e1000 0000:00:03.0 eth0: (PCI:33MHz:32-bit) 52:54:00:12:34:56
e1000 0000:00:03.0 eth0: Intel(R) PRO/1000 Network Connection
e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
tc@box:~$ ls -la /
total 4
drwxrwxr-x 17 1000 1000 380 Nov 22 05:28 ./
drwxrwxr-x 17 1000 1000 380 Nov 22 05:28 ../
drwxr-xr-x 2 root root 1380 Nov 22 03:14 bin/
drwxrwxr-x 12 root staff 4260 Nov 22 05:32 dev/
drwxr-xr-x 8 root root 680 Nov 22 05:32 etc/
drwxrwxr-x 3 root staff 60 Nov 22 05:32 home/
-rwxr-xr-x 1 root root 496 Nov 22 03:14 init
drwxr-xr-x 4 root root 800 Nov 22 03:14 lib/
lrwxrwxrwx 1 root root 11 Nov 22 03:14 linuxrc.bk -> bin/busybox
drwxrwxr-x 4 root staff 80 Nov 22 05:32 mnt/
drwxrwsr-x 2 root staff 160 Nov 22 03:14 opt/
dr-xr-xr-x 64 root root 0 Nov 22 05:32 proc/
drwxrwxr-x 2 root staff 80 Nov 22 03:14 root/
drwxrwxr-x 4 root staff 80 Nov 22 05:32 run/
drwxr-xr-x 2 root root 1200 Nov 22 03:14 sbin/
dr-xr-xr-x 12 root root 0 Nov 22 05:32 sys/
drwxrwxrwt 4 root staff 120 Nov 22 05:32 tmp/
drwxr-xr-x 7 root root 140 Nov 22 03:14 usr/
drwxrwxr-x 8 root staff 180 Nov 22 03:14 var/
今度は起動します。ということでinitramfsを利用する環境では/linuxrcなくてもOKというのが実際の動作からわかりました。
/initの処理
/initは次のような短いシェルスクリプトです。
#!/bin/sh
mount proc
grep -qw multivt /proc/cmdline && sed -i s/^#tty/tty/ /etc/inittab
if ! grep -qw noembed /proc/cmdline; then
inodes=`grep MemFree /proc/meminfo | awk '{print $2/3}' | cut -d. -f1`
mount / -o remount,size=90%,nr_inodes=$inodes
umount proc
exec /sbin/init
fi
umount proc
if mount -t tmpfs -o size=90% tmpfs /mnt; then
if tar -C / --exclude=mnt -cf - . | tar -C /mnt/ -xf - ; then
mkdir /mnt/mnt
exec /sbin/switch_root mnt /sbin/init
fi
fi
exec /sbin/init
execコマンドの実行箇所が3箇所ありますが、今回の環境では最初のifブロック部分が実行されます。tclの環境でmountコマンドの出力を見るとこんなふうになってます。
tc@box:~$ mount
rootfs on / type rootfs (rw,size=400480k,nr_inodes=141441)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
tmpfs on /dev/shm type tmpfs (rw,relatime)
fusectl on /sys/fs/fuse/connections type fusectl (rw,relatime)
この環境では搭載されているメモリサイズに応じたinode数やメモリの何%をファイルシステム容量上限とするかを設定した上でルートファイルシステムを再マウントし直しています。そして/sbin/initを実行します。
先に引用したinitramfsnの説明文で次のような一文がありました。
This binary is responsible to do all the things prepare_namespace() would do.
ここでのnamespaceはコンテナの文脈で使うnamepspaceとは別のものです。prepare_namespace()はルートファイルシステムをマウントしてそこにchrootする処理を行います。initramfsを使う場合はその処理は/initで行ってねということです。
起動処理において/initが必要というのはわかりましたが、カーネルからはどう動くのか?というのを見ていきます。tclの11.1ではカーネル5.4.3が使われているのでこのバージョンで確認しましょう。/initを実行するのはinit/main.cのrun_init_process()にて行います。このときの関数の呼ばれ方は次のようになります。
- start_kernel()
- arch_call_rest_init()
- rest_init()
- kernel_init()
- run_init_process()
start_kernel()の一番最後でarch_call_rest_init()を呼び出して流れていきます。arch_call_rest_init()は名前からしてアーキテクチャ毎に実装しする感じですが、s390以外は独自の実装はなくてinit/main.cにあるarch_call_rest_init()を利用しています。
run_init_process()は次のように実行します。
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
ramdisk_execute_commandはkernel_init_freeable()にて次のように/initを設定します。
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
ramdisk_execute_commandのデフォルト値はNULLですが、カーネルのコマンドラインパラメータでrdinit=で指定することができます。
static int __init rdinit_setup(char *str)
{
unsigned int i;
ramdisk_execute_command = str;
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);
特に指定がなければ/initが使われます。ということでinitramfsに/initがないとエラーになるのはこれってことがわかります。/initの実行に成功したら0を返してkernel_init()の処理は終了です。
/initは/sbin/initを実行するので本当のinit処理は/initから実行される感じです。