">

ほそぼそストレージ研究所

Phase 00 Linuxの起動時・GRUB・initramfsについての基礎 - Theme 2 initramfsからsystemdまで

投稿日 2026.08.05 カテゴリ Linux レベル 初心者
Linux initramfs Linux kernel

initrdを覗く

前置き

前回はUEFI / GRUBについて見てみました。今回は、GRUBがKernelを起動してから実際にLinuxを触れる状態になるまでを見ていきます。
なお、環境はx86_64 Debianです。OS部分は共通ですが、Red Hat系などではユーザースペース上での動きは若干異なる可能性があります。

Kernel起動からの流れ

以下の流れを踏みます。
1. Linux kernel起動
2. initramfsが一時的なroot filesystemとして展開
3. initramfs内の初期化処理が、本物のroot filesystemを探す
4. root=UUID=...で指定されたfilesystemを見つけ、一旦mountする
5. 仮で使っていたinitramfs/から本物のroot filesystemへ切り替える
6. systemdが起動

initramfsはkernel本体とは別物です。今回の例で言うと、kernel本体はvmlinuz-...で、initramfsinitrd.img-...としてGRUBからkernelに渡される補助的なイメージです。
initramfsは一時的に/として展開されるものです。小さな起動用環境です。「本物のLinux環境に入る前に使う、最小限の作業場」というイメージです。

なぜこのような物が必要かというと、root filesystemがあるストレージを使える状態にするためです。
例えば、root filesystemがNVMe上にある場合、起動の早い段階でNVMeを認識し、root=UUID=に対応するデバイスを探す必要があります。
更に環境によっては、LVM, RAID, 暗号化ディスクなどの準備も必要になってきます。

そのため、まずkernelはinitramfsを展開し、その中にある初期化処理を動かします。
そこで必要なdriverや設定を使って本物のroot filesystemを探し、見つかったらそれを/としてmountします。

実際には、initramfsはなくても起動するケースもあります。必要なdriverがkernelに組み込まれており、LVM・RAID・暗号化diskなどearly userspaceでの準備も不要なら、、initramfsは不要になります。
ただ、現代のディストリビューションでは柔軟な構成に対応するために、一般的に使われている機能となっています。

initramfsを見てみる

では、initramfsを覗いていきましょう。
まずはkernelに対応するinitramfsを変数に入れておきます。

$ KVER=$(uname -r)
$ INITRD=/boot/initrd.img-$KVER
$ echo $INITRD
/boot/initrd.img-6.12.95+deb13-amd64
$ echo $KVER
6.12.95+deb13-amd64
$ ls -lh $INITRD
-rw-r--r-- 1 root root 36M  7月 10 13:42 /boot/initrd.img-6.12.95+deb13-amd64

では、initramfsの中身の一覧を見てみます。
そこそこたくさんのファイルが入っています。

$ lsinitramfs $INITRD
.
kernel
kernel/x86
kernel/x86/microcode
kernel/x86/microcode/AuthenticAMD.bin
.
usr
usr/lib
usr/lib/modules
usr/lib/modules/6.12.95+deb13-amd64
usr/lib/modules/6.12.95+deb13-amd64/kernel
usr/lib/modules/6.12.95+deb13-amd64/kernel/arch
usr/lib/modules/6.12.95+deb13-amd64/kernel/arch/x86
usr/lib/modules/6.12.95+deb13-amd64/kernel/arch/x86/crypto
usr/lib/modules/6.12.95+deb13-amd64/kernel/arch/x86/crypto/crc32-pclmul.ko.xz
usr/lib/modules/6.12.95+deb13-amd64/kernel/arch/x86/crypto/crc32c-intel.ko.xz
usr/lib/modules/6.12.95+deb13-amd64/kernel/crypto
usr/lib/modules/6.12.95+deb13-amd64/kernel/crypto/blake2b_generic.ko.xz
usr/lib/modules/6.12.95+deb13-amd64/kernel/crypto/crc32_generic.ko.xz
usr/lib/modules/6.12.95+deb13-amd64/kernel/crypto/crc32c_generic.ko.xz
usr/lib/modules/6.12.95+deb13-amd64/kernel/crypto/xor.ko.xz
usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers
usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/acpi
usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/acpi/battery.ko.xz

...

usr/bin/base64
usr/bin/awk
usr/bin/ash
usr/bin/ascii
usr/bin/arch
usr/bin/ar
usr/bin/[[
usr/bin/[
usr/sbin/watchdog

ちょっとわかりにくいので、いくつかピックアップします。

# /init関連
$ lsinitramfs $INITRD | grep -E '(^|/)init$'
init
usr/sbin/init
# script関連
$ lsinitramfs "$INITRD" | grep -E '(^|/)scripts/'
scripts/functions
scripts/init-bottom
scripts/init-bottom/ORDER
scripts/init-bottom/udev
scripts/init-top
scripts/init-top/ORDER
scripts/init-top/all_generic_ide
scripts/init-top/blacklist
scripts/init-top/keymap
scripts/init-top/udev
scripts/local
scripts/local-premount
scripts/local-premount/ORDER
scripts/local-premount/resume
scripts/nfs
# 設定ファイル系
$ lsinitramfs "$INITRD" | grep -E '(^|/)(conf|etc)/'
conf/arch.conf
conf/conf.d
conf/conf.d/resume
conf/initramfs.conf
etc/fstab
etc/ld.so.cache
etc/ld.so.conf
etc/ld.so.conf.d
etc/ld.so.conf.d/libc.conf
etc/ld.so.conf.d/x86_64-linux-gnu.conf
etc/modprobe.d
etc/modprobe.d/amd64-microcode-blacklist.conf
etc/mtab
etc/udev
etc/udev/udev.conf

次は更に中身を覗いてみましょう。展開してみます。
unmkinitramfsで展開する事ができます。kernelがinitramfsを展開した後に最初に実行するのは、この中の/initです。展開するとmain/initとして見えるようになっています。

$ mkdir -p initramfs-test
$ unmkinitramfs -v $INITRD initramfs-test
$ cd initramfs-test
$ tree -L 3
.
├── early
│   └── kernel
│       └── x86
└── main
    ├── bin -> usr/bin
    ├── conf
    │   ├── arch.conf
    │   ├── conf.d
    │   └── initramfs.conf
    ├── etc
    │   ├── fstab
    │   ├── ld.so.cache
    │   ├── ld.so.conf
    │   ├── ld.so.conf.d
    │   ├── modprobe.d
    │   ├── mtab -> /proc/mounts
    │   └── udev
    ├── init
    ├── lib -> usr/lib
    ├── lib64 -> usr/lib64
    ├── run
    ├── sbin -> usr/sbin
    ├── scripts
    │   ├── functions
    │   ├── init-bottom
    │   ├── init-top
    │   ├── local
    │   ├── local-premount
    │   └── nfs
    └── usr
        ├── bin
        ├── lib
        ├── lib64
        └── sbin

25 directories, 10 files

それっぽい構成が出てきました。こうなってるんですね。
スクリプトの中身を見たいところです。

$ find . -maxdepth 3 \( -type f -name init -o -path '*/scripts/*' \) | sort
./main/init
./main/scripts/functions
./main/scripts/init-bottom
./main/scripts/init-top
./main/scripts/local
./main/scripts/local-premount
./main/scripts/nfs

ちょっと中身を見てみましょう。

$ less ./main/init
#!/bin/sh

# Default PATH differs between shells, and is not automatically exported
# by klibc dash.  Make it consistent.
export PATH=/sbin:/usr/sbin:/bin:/usr/bin

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc

# shellcheck disable=SC2013
for x in $(cat /proc/cmdline); do
        case $x in
        initramfs.clear)
                clear
                ;;
        quiet)
                quiet=y
                ;;
        esac
done

if [ "$quiet" != "y" ]; then
        quiet=n
        echo "Loading, please wait..."
fi
export quiet

...

中身はshスクリプトです。結構な量なので、ざっくり解説。

/initの流れ

/initは、最初に走るスクリプトです。kernelがinitramfsを展開した後に実行されます。
これは、本物の/をmountするまでの段取り係です。以下の流れを実行しています。
1. initramfs内の基本環境を整える。
2. /proc, /sys, /dev, /runなどを用意
3. /proc/cmdlineを読んで、kernel parameterを解釈する
4. initramfs内の設定ファイルや関数を読み込む
5. 必要なdriver/moduleやudev処理を走らせる
6. root=UUID=...の本物のroot filesystemを探す
7. 見つけたroot filesystemを一時的に/rootなどへmountする
8. initramfs側の後処理を行う
9. run-init / switch_root的な処理で本物の/へ移る
10. 本物の/sbin/init (=systemd)を起動する
なお、/initはメインスクリプトで、細かい関数は/scripts下にて実装されています。

/proc/cmdlineの解釈

よく見ると、/proc/cmdlinecatで取得して読み込んでいますね。
最初はquietを読み込んでいますが、下の方でもいくつもコマンドを確認しています。具体的にはここらへん。ここで起動パラメータを解釈しています。

for x in $(cat /proc/cmdline); do
        case $x in
        init=*)
                init=${x#init=}
                ;;
        root=*)
                ROOT=${x#root=}
                if [ -z "${BOOT}" ] && [ "$ROOT" = "/dev/nfs" ]; then
                        BOOT=nfs
                fi
                ;;
        rootflags=*)
                ROOTFLAGS="-o ${x#rootflags=}"
                ;;
        rootfstype=*)
                # Linux interprets 'rootfstype=*tmpfs*' to control the
                # initramfs filesystem; we should remove 'tmpfs' from
                # the list
                ROOTFSTYPE="$(list_filter_out "${x#rootfstype=}" tmpfs)"
                ;;
        rootdelay=*)
                ROOTDELAY="${x#rootdelay=}"
                case ${ROOTDELAY} in
                *[![:digit:].]*)
                        ROOTDELAY=
                        ;;
                esac
                ;;
        nfsroot=*)
                # shellcheck disable=SC2034
                NFSROOT="${x#nfsroot=}"
                ;;
        initramfs.runsize=*)
                RUNSIZE="${x#initramfs.runsize=}"
                ;;
        ip=*)
                IP="${x#ip=}"
                ;;
...
        fsck.repair=no)
                fsckfix=n
                ;;
        esac
done

この中でも、root=*)という分岐でroot filesystemを探す情報を取得しているわけです。

最小構成でも必要なこと

root filesystemを探すには、kernelが認識したdevice情報を見たり、device nodeを使ったりする必要があります。
でも、initramfsは最小のUserspaceなので、それを扱うためのファイルを用意できていません。
そこで、/initでは/proc, /sys, /dev, /runをまず最初に用意します。
- /proc : kernel parameterやkernel内部情報を見る
- /sys : kernelが認識したdevice情報を見る
- /dev : device nodeを使う。/dev/nvme0n1p2のようなもの
- /run : 起動中の一次情報を置く
それこそ、/dev下が見えるようにならないと、root filesystemは見つけられないです。

/へのmountの変更

本物のroot filesystemは一旦/rootにmountされます。
その後、run-initが走り以下の処理を行います。
1. chdir("/root")を実行し、本物のrootへの参照をcurrent directoryの.として保持する。
2. initramfs内の不要なファイルを削除する。
- 削除対象はinitramfsのrootfs上にあるファイルだけで、本物のroot filesystemには降りないので削除されない。
- rootfs自体をunmountするわけではなく、中身を削除して使用していたメモリを解放する。
3. mount(".", "/", NULL, MS_MOVE, NULL)で本物のroot mountを/rootから/へ移動する
4. chroot(".")で、PIDが持つroot directory参照を本物のrootへ変更する。
5. 本物のrootにある/sbin/initexecve()する
ここで、いくつか知っておかなければならないことがあります。
- Linuxの各プロセスはroot directory、current directory、mount namespaceへの参照を持つ。
- root directory : 絶対パス/の検索開始位置
- current directory : 相対パスの検索開始位置
- mount namespace : どのfilesystemがどこへmountされているか
- システムコール群
- chdir
- shellの組み込みコマンドのcdに該当。プロセス参照のうち、current directoryを変更する。
- mount(".","/",NULL,MS_MOVE,NULL)
- current directoryが属している本物のroot mountを、mount tree上の/rootから/へ移す
- mount自体はfilesystemやディレクトリツリーを、mount namespace内のどこへ見せるかを操作する機能。
- シェルコマンドで打つと、mount --move . /となる。
- MS_MOVEが指定されている場合、sourceはデバイスではなく、「移動元の既存mount」となる。
- chroot
- current directoryとして保持している本物のrootを、以後このプロセスの/として扱う操作。
- 壊れたLinux環境の修復や、別root filesystemへのパッケージインストールなどで用いられる。
- execve()
- PIDを維持したまま、プログラム・メモリ・stackなどを新しい実行ファイルの内容へ置き換える。
- 例として、lsなどでも使われていて、shellが作成した子プロセスがexecve("/usr/bin/ls", ...)を呼び、自分自身をlsで置き換えている。
- shellではexecが該当する。
これらを踏まえて、状態の遷移を見ていきましょう。

段階 PID 1のroot dir PID 1のcurrent dir mount状態 PID 1
初期状態 initramfsの/ initramfsの/ 本物のrootは/root /init
chdir("/root") initramfsの/ initramfsの/から本物のrootの最上位へ変更 本物のrootは/root run-init
mount(".", "/", NULL, MS_MOVE, NULL) initramfsの/ 本物のrootの最上位 本物のroot mountが/へ移動 run-init
chroot(".") current dirにroot dirを変更することで、
本物のrootの最上位になる
本物のrootの最上位 本物のrootが/ run-init
execve("/sbin/init") 本物のrootの最上位 本物のrootの最上位 本物のrootが/ systemd

その他

Driverもどんなのがあるか見てみます。
ちょっと多すぎるので、例えばNVMeを見てみます。

$ find ./main -type f -name '*.ko*' | grep -Ei 'nvme'
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/common/nvme-keyring.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/common/nvme-auth.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/host/nvme-fc.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/host/nvme-tcp.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/host/nvme.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/host/nvme-rdma.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/host/nvme-core.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/host/nvme-fabrics.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/target/nvmet-fc.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/target/nvme-loop.ko.xz
./main/usr/lib/modules/6.12.95+deb13-amd64/kernel/drivers/nvme/target/nvmet.ko.xz

ちなみに、kernelにbuilt-inされているものもあります。そういったものはここには出てこないです。
例として、NVMeやext4のBuilt-in Driverを見てみます。

$ grep -E 'CONFIG_BLK_DEV_NVME|CONFIG_EXT4_FS' /boot/config-$(uname -r)
CONFIG_BLK_DEV_NVME=m
CONFIG_EXT4_FS=m
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS_SECURITY=y

=yならkernel本体に組み込み、=mならmoduleです。=mの場合はinitramfs内に.koとして含まれることがあります。

まとめ

今回はinitrd/initramfsについて見てきました。
ざっくり言うと、initramfsはLinuxを本格起動させる時に使うファイル群をまとめたものというイメージです。
なお、Debianの/boot/initrd.img-*は名前はinitrdですが、実体はinitramfsです。
initramfsは初期起動に必要なファイルを纏めたcpioアーカイブで、kernelによってrootfsへ展開されます。
initramfs展開後、本物のroot filesystemを探し、本物のroot mountを/へ移動し、さらにPID 1のroot directoryを切り替えて、ようやくsystemdが立ち上げられるのでした。