initrdでLinuxを使う方法

initrdの状態でLinuxを使う方法を試行錯誤してみた。例えば、initrdの環境でbashを使えるようにするとか、ちょっと遊んでみた。

先ずはinitrdについての基礎知識

initrdはLinuxが起動するときに使う最初のファイルシステムでメモリ上に構築される。そのため"init(初期の)"の"rd(ram disk)"というのかなと思う*1FedoraUbuntuで標準のブートローダとして使われているgrubの設定ファイルでも"kernel"とともに"initrd"を指定する。grubローダはkernelをメモリ上に展開、次にinitrdをメモリ上に展開してrootfsとしてkernelに渡すようだ。

kernelはHDD等のファイルシステムをいきなり使うのではなく、一旦initrdをルートファイルシステムとして動き出し、次のステップでHDD等のファイルシステムをマウントして"switch root"する。initrdにはLinuxとして最小限のファイルしか入っていない。(正確にはHDDのファイルシステムをマウントして制御を渡すまでに必要なファイルだけ。)initrdは次のコマンドで解凍と圧縮ができる。
initrdの解凍:

[root@f8 ~]# mkdir /tmp/initrd.f8
[root@f8 ~]# cd /tmp/initrd.f8
[root@f8 initrd.f8]# zcat /boot/initrd-2.6.25.6-27.fc8.img | cpio -i
16442 blocks
[root@f8 initrd.f8]# ls
bin  dev  etc  init  lib  lib64  proc  sbin  sys  sysroot  usr
[root@f8 initrd.f8]# 

initrdへの圧縮:

[root@f8 initrd.f8]# ls
bin  dev  etc  init  lib  lib64  proc  sbin  sys  sysroot  usr
[root@f8 initrd.f8]# find . | cpio -o -c | gzip -c > /tmp/initrd-new.img
16443 blocks
[root@f8 initrd.f8]# 

Fedora 8(Linux Kernel 2.6.25.6)のinitrdに含まれるファイルは以下の通り(空のディレクトリは省略)。

[root@f8 initrd.f8]# ls -RF
.:
bin/  dev/  etc/  init*  lib/  lib64/  proc/  sbin@  sys/  sysroot/  usr/

./bin:
insmod*  modprobe@  nash*  rmmod*

./dev:
console  null  ram@  ram1  systty  tty0  tty10  tty12  tty3  tty5  tty7  tty9   ttyS1  ttyS3
mapper/  ptmx  ram0  rtc   tty     tty1  tty11  tty2   tty4  tty6  tty8  ttyS0  ttyS2  zero

./etc:
ld.so.cache  ld.so.conf  ld.so.conf.d/

./etc/ld.so.conf.d:
qt-x86_64.conf

./lib:
ata_generic.ko  ehci-hcd.ko  firmware/  libata.ko   ohci-hcd.ko   pata_jmicron.ko  scsi_wait_scan.ko  uhci-hcd.ko
ata_piix.ko     ext3.ko      jbd.ko     mbcache.ko  pata_acpi.ko  scsi_mod.ko      sd_mod.ko          usb-storage.ko

./lib64:
ld-2.7.so*             libcrypto.so.6@                libgcc_s.so.1@            libpopt.so.0.0.0*  libuuid.so.1.2*
ld-linux-x86-64.so.2@  libc.so.6@                     libglib-2.0.so.0@         libresolv-2.7.so*  libz.so.1@
libblkid.so.1@         libdevmapper.so.1.02*          libglib-2.0.so.0.1400.6*  libresolv.so.2@    libz.so.1.2.3*
libblkid.so.1.0*       libdl-2.7.so*                  libm-2.7.so*              libselinux.so.1*
libc-2.7.so*           libdl.so.2@                    libm.so.6@                libsepol.so.1*
libcrypto.so.0.9.8b*   libgcc_s-4.1.2-20070925.so.1*  libpopt.so.0@             libuuid.so.1@

./usr/lib64:
libbdevid.so.6.0.19*        libdhcp6client-0.10.so.0*  libnash.so.6.0.19*  libnl.so.1.1*        libparted-1.8.so.6.0.0*
libdhcp4client-3.0.6.so.0*  libdhcp.so.1*              libnl.so.1@         libparted-1.8.so.6@
[root@f8 initrd.f8]#

このように必要最小限のコマンドしか入っていない。(/sbin は /binへシンボリックリンクされていて、initrdの環境では同一、というか混在。)

kernelはこのinitrdを最初のファイルシステムとして使うが、その中からinitを探す。initrdではルートディレクトリのすぐ直下に配置されている。ただし、通常のLinuxのinitとは違い、initrdでは"nash"と呼ばれるLinux起動用コマンドへ手順(スクリプト)となっている。(nashは/binディレクトリに格納されている。)

[root@f8 initrd.f8]# cat init
#!/bin/nash

mount -t proc /proc /proc
setquiet
echo Mounting proc filesystem
echo Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo Creating initial device nodes
mknod /dev/null c 1 3
	:
	:
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro /dev/sda1
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1
[root@f8 initrd.f8]#

"nash"は"sh"という名前がついているが、(シェルとして機能も最小限は持っているが)シェルと言うよりは起動専用の簡易バッチ処理コマンドと考えた方がいい。initでは"mount"や"mkdir"、"mknod"などのお馴染のコマンド名が使われているが、initrdの/binの下にはnashの他にはinsmod、modprobe、rmmodの3つのコマンドしかないことから分かるように、mount、mkdirなどのコマンドはnashの内部コマンドである。(後述するように、外部コマンドも実行できる。)nashは通常のLinux環境でも /sbin/nash として収録されているので、"--force"オプションをつけることで通常環境でも実行することはできる。

なお、Linux Kernelは起動すると最初に"init"を探し、最初のプロセスとして起動するが、initがバイナリオブジェクトであれば、そのまま実行し、何らかのスクリプトであればインタプリタを起動してからそのスクリプトを実行するようになっているらしい。一般的なinitrdでは、initはnashに対するスクリプトとして記述されているが、「PXEによるディスクレス・クライアント」で使ったディスクレス用のinitrdではbashを使ったシェルスクリプトで記述してあった。

このinitを自由に書き換えることができれば、本番のLinuxを立ち上げる前に、色々と「技」が使えるようになったり、特定用途向け(例えばThin Clientなど)のメモリだけで動作するLinuxを作ることもできるだろう。

initを変更してブートしてみる

手始めとして、nashのinitをちょっと変更して、どのようになるか試してみた。

先ずは、現行のLinuxのinitdを作業用ディレクトリに展開する:

[root@f8 tmp]# mkdir initrd-adsaria
[root@f8 tmp]# cd initrd-adsaria
[root@f8 initrd-adsaria]# zcat /boot/initrd-2.6.25.6-27.fc8.img | cpio -i
16539 blocks
[root@f8 initrd-adsaria]# ls
bin  dev  etc  init  lib  lib64  proc  sbin  sys  sysroot  usr

次にinitを変更してみる:

[root@f8 initrd-adsaria]# vi init
[root@f8 initrd-adsaria]# cat init
#!/bin/nash

mount -t proc /proc /proc
#setquiet
echo Mounting proc filesystem
	:
	:
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro LABEL=/
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
echo Switching to new root and running init.
#switchroot
echo Booting has failed.
sleep -1
[root@f8 initrd-adsaria]#

ここでは、nashが最後に実行するコマンド、"switchroot"をコメントアウトしてみる。次に、このinitを含むinitrdイメージファイルを作成する。くれぐれも現行のinitrdと同じ名前にしないように。

 [root@f8 initrd-adsaria]# find . | cpio -o -c | gzip -c > /boot/initrd-adsaria.img
 16539 blocks
 [root@f8 initrd-adsaria]# ls -l /boot
 total 9904
 -rw-r--r-- 1 root root   79661 2008-06-14 05:26 config-2.6.25.6-27.fc8
 drwxr-xr-x 2 root root    4096 2008-07-01 08:54 grub
 -rw------- 1 root root 3386923 2008-06-29 01:08 initrd-2.6.25.6-27.fc8.img
 -rw-r--r-- 1 root root 3407929 2008-07-01 08:59 initrd-adsaria.img
 -rw-r--r-- 1 root root 1139271 2008-06-14 05:26 System.map-2.6.25.6-27.fc8
 -rw-r--r-- 1 root root 2065112 2008-06-14 05:26 vmlinuz-2.6.25.6-27.fc8
 [root@f8 initrd-adsaria]#

そして、今作成したinitrdを使ってシステムをブートできるように /boot/grub/grub.confを変更する。

[root@f8 initrd-adsaria]# vi /boot/grub/grub.conf
	:
	:
[root@f8 initrd-adsaria]# cat /boot/grub/grub.conf
# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You do not have a /boot partition.  This means that
#          all kernel and initrd paths are relative to /, eg.
#          root (hd0,0)
#          kernel /boot/vmlinuz-version ro root=/dev/sda1
#          initrd /boot/initrd-version.img
#boot=/dev/sda
default=0
timeout=5
splashimage=(hd0,0)/boot/grub/splash.xpm.gz
hiddenmenu
title Fedora (2.6.25.6-27.fc8)
        root (hd0,0)
        kernel /boot/vmlinuz-2.6.25.6-27.fc8 ro root=LABEL=/ rhgb quiet
        initrd /boot/initrd-2.6.25.6-27.fc8.img
title Adsaria-Test
        root (hd0,0)
        kernel /boot/vmlinuz-2.6.25.6-27.fc8 ro root=LABEL=/ rhgb
        initrd /boot/initrd-adsaria.img
[root@f8 initrd-adsaria]# 

この例では、"Adsaria-Test"というエントリを追加(現行の"Fedora"をコピーして修正)してある。Kernelは通常のカーネルを使うが、initrdは特注品を指定している。
これで準備ができたのでリブートする。先ずは、grubのメニュー画面でAdsaria-Testを選択する。

起動途中で当然、失敗するので次のような画面でストップする。

ここでnashの入力モードになるのかと思ったが、残念ながら何も受け付けない。仕方ないのでマシンをリセットしてリブートする。(今回はgrub画面で通常のブートをしてする。)

initrdでbashを使えるようにする

次にinitrdの環境でbashを使えるようにしてみた。
まず、initrdの /binディレクトリにbashをコピーする。通常のLinux環境の/binからinitrdの/binにコピーする。

[root@f8 initrd-adsaria]# ls
bin  dev  etc  init  lib  lib64  proc  sbin  sys  sysroot  usr
[root@f8 initrd-adsaria]# ls bin
insmod  modprobe  nash  rmmod
[root@f8 initrd-adsaria]# cp /bin/bash bin
[root@f8 initrd-adsaria]# ls bin
bash  insmod  modprobe  nash  rmmod
[root@f8 initrd-adsaria]#

しかし、このままではbashが使うライブラリファイルがinitrd環境に無いのでまだbashを使うことはできない。そこでまず、ライブラリのディレクトリにてinitrdの環境でインストールされているライブラリを確認する。(今回はx86_64アーキテクチャカーネルを使ったので /lib64になっている。)

[root@f8 initrd-adsaria]# cd lib64
[root@f8 lib64]# ls
ld-2.7.so             libdl-2.7.so                  libpopt.so.0.0.0
ld-linux-x86-64.so.2  libdl.so.2                    libresolv-2.7.so
libblkid.so.1         libgcc_s-4.1.2-20070925.so.1  libresolv.so.2
libblkid.so.1.0       libgcc_s.so.1                 libselinux.so.1
libc-2.7.so           libglib-2.0.so.0              libsepol.so.1
libcrypto.so.0.9.8b   libglib-2.0.so.0.1400.6       libuuid.so.1
libcrypto.so.6        libm-2.7.so                   libuuid.so.1.2
libc.so.6             libm.so.6                     libz.so.1
libdevmapper.so.1.02  libpopt.so.0                  libz.so.1.2.3
[root@f8 lib64]#

次にbashが呼び出すライブラリを調べる。

[root@f8 lib64]# ldd /bin/bash
        linux-vdso.so.1 =>  (0x00007fffe36d8000)
        libtinfo.so.5 => /lib64/libtinfo.so.5 (0x0000003b18a00000)
        libdl.so.2 => /lib64/libdl.so.2 (0x0000003b05200000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003b04a00000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003b04600000)
[root@f8 lib64]#

この内、initrdの環境にないものを(通常のLinux環境の)/lib64からコピーする。この場合はlibtinfo.so.5が足りない。

[root@f8 lib64]# cp /lib64/libtinfo.so.5.6 .
[root@f8 lib64]# ln -s /lib64/libtinfo.so.5.6 libtinfo.so.5
[root@f8 lib64]# ls
ld-2.7.so             libdl.so.2                    libresolv.so.2
ld-linux-x86-64.so.2  libgcc_s-4.1.2-20070925.so.1  libselinux.so.1
libblkid.so.1         libgcc_s.so.1                 libsepol.so.1
libblkid.so.1.0       libglib-2.0.so.0              libtinfo.so.5
libc-2.7.so           libglib-2.0.so.0.1400.6       libtinfo.so.5.6
libcrypto.so.0.9.8b   libm-2.7.so                   libuuid.so.1
libcrypto.so.6        libm.so.6                     libuuid.so.1.2
libc.so.6             libpopt.so.0                  libz.so.1
libdevmapper.so.1.02  libpopt.so.0.0.0              libz.so.1.2.3
libdl-2.7.so          libresolv-2.7.so
[root@f8 lib64]#

ちなみに、私はlibtinfoの実体ファイルの"libtinfo.so.5.6"をコピーしてシンボリックリンクを構成したが、"libtinfo.so.5"としてコピーしてもOK。
これで、bashがつかえるようになったので、initを変更してbashを呼び出すようにする。

[root@f8 lib64]# cd ..
[root@f8 initrd-adsaria]# vi init

[root@f8 initrd-adsaria]# tail init
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
echo Start bash
bash
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1
[root@f8 initrd-adsaria]#

ここでは、先ほどのswitchrootのコメントを外して、その代りにswitchrootの直前でbashを呼び出すようにした。これで準備が出来た。initrdイメージを作成してリブートする。

[root@f8 initrd-adsaria]# find . | cpio -o -c | gzip -c > /boot/initrd-adsaria.img
18367 blocks
[root@f8 initrd-adsaria]# reboot

grubメニューで"Adsaria-Test"を指定すると、initrdの状態でbashのプロンプトが出る。

しかし、"ls"コマンドとかは使えない。lsコマンドはinitrdの環境にはまだコピーしていないので。使えるのはbashの内部コマンド(cdやedho、pwdなど)だけだ。("ls"が使えないときにファイル名を表示するにはここにあるように"echo *"を使う。昔、先輩から教えてもらった方法だが、こんなところでも役に立つとは。)

initrdの/sysrootというディレクトリの下には通常の(HDDにインストールされている)Linuxのルートがマウントされている。

もし、必要であれば自分の好きなコマンドをbashと同じ要領でコピーしてくれば使える。(もちろん、中には環境が複雑で、すべてをinitrd上で構築しないと使えないコマンドもあるだろう。)
ちなみに、この環境ではbashを終了することでnashのスクリプトの続きが始まり、通常のLinuxが起動することになる。

busyboxを使う

上の方法でbash等のコマンドをinitrdで利用できるようになる。しかし、普段使っている全てのコマンドをコピーするのは大変だし、そのコマンドが使うライブラリをいちいち調べてコピーするのも大変である。何よりもHDDを使わずにメモリ空間だけで実現されるinitrdでは、多くのコマンドコピーするとファイルサイズが一気に増えてしまうので、コピーできるコマンドの数も限定される。

そうした時に便利なのがbusyboxである。busyboxは1つのコマンドで普段よく使うlsやcat、shなど、250以上のコマンドの機能を実現している。

まず、busyboxをインストールする。

[root@f8 ~]# yum install busybox

次に /sbin/busyboxをinitrdのbinディレクトリにコピーする。

[root@f8 ~]# cd ~/tmp/initrd-adsaria
[root@f8 initrd-adsaria]# ls
bin  dev  etc  init  lib  lib64  proc  sbin  sys  sysroot  usr
[root@f8 initrd-adsaria]# cp /sbin/busybox bin
[root@f8 initrd-adsaria]# ls bin
busybox  insmod  modprobe  nash  rmmod
[root@f8 initrd-adsaria]#

busyboxはダイナミックリンク・ライブラリは使用せず、スタティックリンクで作られているので単独で実行が可能で、ライブラリのコピーは必要ない。
次にbinディレクトリへ移動してシンボリックリンク・ファイルを作成する。busyboxを使う上で、シンボリックリンクを張ることは必ずしも必要ないが、よく使うコマンドに関してはこのようにしておけば、単に"ls"とか"mount"とか入力するだけで、busyboxのls機能やmount機能を呼び出せるので便利だ。

[root@f8 initrd-adsaria]# cd bin
[root@f8 bin]# ln -s busybox sh
[root@f8 bin]# ln -s busybox ln
[root@f8 bin]# ln -s busybox ls
[root@f8 bin]# ln -s busybox cat
[root@f8 bin]# ln -s busybox mount
[root@f8 bin]# ls -l
total 1984
-rwxr-xr-x 1 root root 1851232 2008-08-04 19:52 busybox
lrwxrwxrwx 1 root root       7 2008-08-04 19:53 cat -> busybox
-rwxr-xr-x 1 root root    9760 2008-08-04 19:52 insmod
lrwxrwxrwx 1 root root       7 2008-08-04 19:53 ln -> busybox
lrwxrwxrwx 1 root root       7 2008-08-04 19:53 ls -> busybox
lrwxrwxrwx 1 root root      10 2008-08-04 19:52 modprobe -> /sbin/nash
lrwxrwxrwx 1 root root       7 2008-08-04 19:54 mount -> busybox
-rwxr-xr-x 1 root root  100904 2008-08-04 19:52 nash
-rwxr-xr-x 1 root root   14256 2008-08-04 19:52 rmmod
lrwxrwxrwx 1 root root       7 2008-08-04 19:53 sh -> busybox
[root@f8 bin]# ls
busybox  cat  insmod  ln  ls  modprobe  mount  nash  rmmod  sh
[root@f8 bin]# 

あとは、initrdのルートディレクトリにあるinitを変更する。ここでは持つsh機能を"sh"というシンボリックリンク・ファイルから使えるようにしているので次のように変更した。

[root@f8 bin]# cd ..
[root@f8 initrd-adsaria]# vi init
[root@f8 initrd-adsaria]# tail init
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
echo Start sh
sh
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1
[root@f8 initrd-adsaria]#

これで準備は完了したので、initrdファイルを作成してリブートする。(/boot/grub/grub.confは上で作成したものをそのまま使う。)

[root@f8 initrd-adsaria]# find . | cpio -o -c | gzip -c > /boot/initrd-adsaria.img
20161 blocks
[root@f8 initrd-adsaria]# reboot

すると次の画面の通り、initrdの状態でbusyboxのsh機能が呼び出され、lsやmountなどのコマンド(実はbusyboxの機能)が利用できるようになる。

なお、busyboxに関しては「3種類のbusyboxは何が違うのか?」が関連トピックとしてある。

*1:"init"プロセスを起動するためのRDファイルシステムという意味かも。歴史的な経緯は知らない