玩式草子─ソフトウェアとたわむれる日々

第9回P-Plamoの内部構造その1]

前回紹介したように、P-PlamoはHDDにインストールしなくても、DVDから直接起動して利用することができるPlamo Linux版のLiveDVDです。

インストール作業が不要なLiveDVDは便利なツールですが、元々UNIX/Linux環境は読み書きが自由にできるHDDにインストールして使うことが前提になっているので、書き込み不可なDVDメディアとはあまり相性がよくありません。

もちろん、オープンソースであるLinuxの場合、組み込み環境向けのディストリビューションのように、HDDのない環境で動作するように改造することも不可能ではありませんが、わざわざLiveDVD用に専用のディストリビューションを作るのも大変な作業です。

そこで既存の環境を流用しながら、どうやってうまくDVDから起動して運用できるようにするかにLiveDVDの作者は頭を絞ることになります。

幸い、linuxには今回紹介するinitrdをはじめ、ファイルをファイルシステムとしてマウントするループバックファイルシステムや、さまざまな圧縮ファイルシステム自動的に増減するtmpfsといったトリッキーな(?)機能が豊富に用意されているので、これらを組み合わせて使いやすい機能をどのように実現するかが作者の腕の見せ所です。

P-Plamo DVDの中身

通常、OSを動かすためには数千を越えるファイルが必要になりますが、P-PlamoのようなLiveDVDの場合、実際のファイルはsquashfs上に圧縮されているので、DVDの中身はごくわずかのファイルしかありません。

DVDをマウントして中身を見てみると、以下のようなファイルだけになっています。

% ls -lRh /cdrom
/cdrom:
合計 6.1M
-r--r--r-- 1 root root 5.3K  3月 29日  15:19 ChangeLog
-r--r--r-- 1 root root 6.0M  3月 28日  01:47 initrd
dr-xr-xr-x 2 root root 2.0K  3月 29日  10:19 isolinux/

/cdrom/isolinux:
合計 1.6G
-r--r--r-- 1 root root 2.0K  3月 29日  15:21 boot.cat
-r--r--r-- 1 root root 2.3M  3月 29日  15:21 initrd.gz
-r--r--r-- 1 root root  14K  3月 29日  10:57 isolinux.bin
-r--r--r-- 1 root root  658  3月 29日  15:20 isolinux.cfg
-r--r--r-- 1 root root 120K  2月  6日  21:00 plamo41.lss
-r--r--r-- 1 root root 4.1K  2月 14日  14:28 pplamo.lss
-r-xr-xr-x 1 root root 1.6G  3月 28日  02:46 rootimg.squash*
-r--r--r-- 1 root root  464  3月 28日  11:16 sample.msg
-r--r--r-- 1 root root  738  3月 29日  15:20 syslinux.cfg
-r--r--r-- 2 root root 2.2M  3月 29日  10:19 vmlinuz
-r--r--r-- 2 root root 2.2M  3月 29日  10:19 vmlinuz-2.6.32.10-plamoSMP

これらのファイルのうち、P-Plamoに必要なのはisolinuxディレクトリ以下のファイルで、DVDのルートディレクトリにあるChangeLogは変更履歴の簡単なメモ、initrdは後述する起動時ramdiskの圧縮前のイメージで、参照用に置いているのでP-Plamoの動作には不要です。

また、isolinuxディレクトリ以下のファイルでも、pplamo.lss(イジりかけの起動画面イメージ)syslinux.cfg(USBブート用の設定ファイル)はDVD起動の場合は利用されませんので、この2つのファイルを除いた残りのファイルについて簡単に説明しましょう。

boot.cat
CD/DVDからPCを起動するために提案されたEl Torito規格が使う起動イメージのカタログファイルです。El Torito規格では、1枚のCD/DVDから複数のOSを起動することが想定されており、それぞれのOSのブート用の領域がどこにあたるかを記録するためにこのファイルが利用されます。
initrd.gz
圧縮した起動用ramdiskファイル。カーネルとともにメモリ上に読み込まれ、起動用のルートファイルシステムとして利用されます(詳細は後述⁠⁠。
isolinux.bin
isolinuxと呼ばれるCD/DVD起動用のブートローダです。次のisolinux.cfgの設定に従って、カーネルや起動用ramdiskをメモリに読み込みます。
isolinux.cfg
isolinux.binが参照する設定ファイルで、読み込むべきカーネルや起動用ramdisk、カーネルに与えるオプションパラメータ等を設定します。
plamo41.lss
DVD起動時の背景に表示される画像データファイルです。syslinux/isolinux専用の、lssと呼ばれるシンプルだけどやや特殊な画像形式になっています。
rootimg.squash
squashfs化したPlamo-4.72のファイルシステムをLZMA形式で圧縮したファイルです。このファイルがP-Plamoのルートファイルシステムになります。
sample.msg
起動時に表示されるメッセージを記述したファイルで、指定可能なラベルやログイン用の情報などを記しています。
vmlinuz、vmlinuz-2.6.32.10-plamoSMP
linuxカーネル。vmlinuz-2.6.32.10-plamoSMPが本来の名称ですが、El Torito規格を用いてCD/DVDから起動する際には、長いファイル名を格納するロックリッジ拡張機能が利用できないため、vmlinuz-2.6.32.10-plamoSMPからvmlinuzという名前にリンク(ハードリンク)を張って、短いファイル名で参照できるようにしています。

これらのファイルのうち、isolinux.cfgとsample.msgがテキストファイル、その他はバイナリファイルになっています。

P-Plamo用initrd

initrdは起動用ramdisk(INITial RamDisk)の謂で、ブートローダによってカーネルと共にメモリ上に読み込まれ、カーネルがHDD上にある本来のルートファイルシステムを読み込む際に必要な準備作業(ドライバを読み込んだり、RAID/LVMを設定したり)を行うための小さなファイルシステムです。

initrdはカーネルのモジュール化が始まったlinux-2.0のころから採用されている機能です。ブロックデバイスやファイルシステムのドライバもモジュール化されるようになった最近のカーネルでは、そのままではHDDを読むためのドライバはHDDの中状態になってしまうので、たいていのディストリビューションではHDDを読むために必要なドライバ類は、initrdを使って起動時にカーネルに組み込むようになっています。

Plamo Linuxでは、ブロックデバイスやファイルシステム用のドライバはカーネル組み込みにしてしまう方がカーネルの再構築や更新が簡単だと考えているので、現時点ではinitrdは採用していませんが、DVD上のsquashfsをルートファイルシステムに見せるような処理にはinitrdが必須なので、P-Plamoでは独自のinitrdを用意して必要な機能を実現しています。

先ほどは「必要なドライバ類を事前にinitrdを使ってカーネルに組み込む」と書きましたが、initrdが提供するのは単なるファイルシステムであり、その上にドライバモジュール類を置けば自動的に組み込んでくれるわけではありません。ドライバをカーネルに組み込むためには専用のコマンドが必要ですし、どのようなドライバをどういう順番に組み込むかといった制御処理にはシェルスクリプトのような機能も必要になります。

また、モジュールドライバを組み込むためにはカーネル内部の情報を利用するためのproc fsやsysfsといった仮想ファイルシステムも必要になりますし、周辺機器を操作するにはデバイスファイルも必要です。すなわちinitrdは小さなファイルシステムではあるものの、必要なコマンドやディレクトリ構造を備えた完結した1つのLinux環境です。

P-Plamoで使っているinitrdは、前節で紹介したように非圧縮の状態でDVDのルートディレクトリにも置いているので ループバック機能を使ってマウントすれば簡単に中身を見ることができます。

# mount -o loop /cdrom/initrd /loop
# ls -l /loop
合計 32,768
drwxr-xr-x 2 root root  1,024  3月 29日  00:07 bin/
drwxr-xr-x 2 root root  1,024  2月  3日 2009年 cdrom/
drwxr-xr-x 6 root root  3,072  2月 21日  21:58 dev/
drwxr-xr-x 2 root root  1,024  3月 29日  13:40 etc/
-rwxr-xr-x 1 root root  5,380  3月 29日  15:15 init*
drwxr-xr-x 4 root root  1,024  3月  5日  23:48 lib/
drwxr-xr-x 2 root root  1,024  2月  3日 2009年 loop/
drwx------ 2 root root 12,288  2月  3日 2009年 lost+found/
drwxr-xr-x 2 root root  1,024  2月  4日 2009年 new_root/
drwxr-xr-x 2 root root  1,024  2月  3日 2009年 proc/
drwxr-xr-x 2 root root  1,024  2月  3日 2009年 put_old/
drwxr-xr-x 2 root root  1,024  3月  6日 2009年 sbin/
-rwxr-xr-x 1 root root    620  3月  5日  23:40 shutdown*
drwxr-xr-x 2 root root  1,024  2月 27日 2009年 sys/

P-Plamoのinitrdではbusyboxを使ってモジュールの組み込みやスクリプト処理を行っています。busyboxは組み込み環境向けに開発されている「十徳ナイフ(Swiss Army Knife⁠⁠」のようなツールで、cpやmv、lsをはじめとして、よく使われるUNIX/Linuxの基本コマンドをサイズ重視の方針で再実装し、約1.2Mバイトほどのサイズのバイナリに230ほどのコマンドを詰め込んでいます。

%  /loop/bin/busybox 
BusyBox v1.13.2 (2009-02-03 20:32:13 JST) multi-call binary
Copyright (C) 1998-2008 Erik Andersen, Rob Landley, Denys Vlasenko
and others. Licensed under GPLv2.
See source distribution for full notice.

Usage: busybox [function] [arguments]...
   or: function [arguments]...

	BusyBox is a multi-call binary that combines many common Unix
	utilities into a single executable.  Most people will create a
	link to busybox for each function they wish to use and BusyBox
	will act like whatever it was invoked as!

Currently defined functions:
	[, [[, addgroup, adduser, adjtimex, ar, ash, awk, basename, blkid, bunzip2, bzcat, bzip2, cal, cat, catv, chattr,
	chgrp, chmod, chown, chpasswd, chroot, chrt, chvt, cksum, clear, cmp, comm, cp, cpio, cryptpw, cut, date, dd,
	deallocvt, delgroup, deluser, devmem, df, diff, dirname, dmesg, dos2unix, du, dumpkmap, echo, ed, egrep, eject,
	env, expand, expr, false, fbset, fbsplash, fdflush, fdformat, fdisk, fgrep, find, findfs, fold, free, freeramdisk,
	fsck, fsck.minix, ftpget, ftpput, fuser, getopt, getty, grep, gunzip, gzip, halt, hd, hdparm, head, hexdump, hostid,
	hostname, hwclock, id, ifconfig, init, insmod, install, ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute, iprule,
	iptunnel, kbd_mode, kill, killall, killall5, klogd, length, less, linux32, linux64, linuxrc, ln, loadfont, loadkmap,
	logger, login, logname, logread, losetup, ls, lsattr, lsmod, lzmacat, makedevs, md5sum, mdev, mesg, mkdir, mkfifo,
	mkfs.minix, mknod, mkswap, mktemp, modprobe, more, mount, mountpoint, mv, netstat, nice, nohup, nslookup, od,
	openvt, passwd, patch, pidof, ping, pipe_progress, pivot_root, pkill, poweroff, printenv, printf, ps, pwd, raidautorun,
	rdate, rdev, readahead, readlink, readprofile, realpath, reboot, reset, resize, rm, rmdir, rmmod, route, rtcwake,
	run-parts, runlevel, script, sed, seq, setarch, setconsole, setfont, setkeycodes, setlogcons, setsid, sh, sha1sum,
	showkey, sleep, sort, split, start-stop-daemon, stat, strings, stty, su, sulogin, sum, swapoff, swapon, switch_root,
	sync, syslogd, tac, tail, tar, tee, telnet, test, time, top, touch, tr, true, tty, ttysize, udhcpc, umount, uname,
	uncompress, unexpand, uniq, unix2dos, unlzma, unzip, uptime, usleep, uudecode, uuencode, vi, vlock, wc, wget,
	which, who, whoami, xargs, yes, zcat

この例に見られるように、busyboxには[(スクリプト中で各種条件をテストするコマンド)addgroupをはじめ、viwgetといった機能(function)が1つのバイナリに詰め込まれ、それぞれの機能は引数として指定するか、各機能名のシンボリックリンク経由でbusyboxを呼び出して使うようになっています。

% ls -lh /loop/bin
合計 1.2M
-rwxr-xr-x 2 root root 1.2M  2月  4日 2009年 busybox*
lrwxrwxrwx 1 root root    7  3月 28日  01:49 cat -> busybox*
lrwxrwxrwx 1 root root    7  3月 29日  00:07 chmod -> busybox*
lrwxrwxrwx 1 root root    7  3月 28日  01:49 chroot -> busybox*
lrwxrwxrwx 1 root root    7  3月 28日  01:49 cp -> busybox*
lrwxrwxrwx 1 root root    7  3月 28日  01:49 cpio -> busybox*
...

このシンボリックリンクの設定により、/loop/bin/catを実行すれば、/loop/bin/busyboxをcat機能を指定して実行したものと見なされます。

busyboxはこのように便利なツールではありますが、サイズ重視で再実装しているため、各機能のオプション指定には制限があり、普段使っている便利なオプションが使えないこともよくあります。

Plamo Linux標準のcatコマンドは、GNU coreutils由来で、以下のような豊富なオプションを誇ります。

% /bin/cat --help
使用法: /bin/cat [オプション]... [ファイル]...
Concatenate FILE(s), or standard input, to standard output.

  -A, --show-all           equivalent to -vET
  -b, --number-nonblank    number nonempty output lines
  -e                       equivalent to -vE
  -E, --show-ends          display $ at end of each line
  -n, --number             number all output lines
..

一方、busyboxのcatには-uというオプションしかありませんし、しかもそのオプションは無視されるそうです。

% /loop/bin/cat --help
 BusyBox v1.13.2 (2009-02-03 20:32:13 JST) multi-call binary
 
 Usage: cat [-u] [FILE]...
 
 Concatenate FILE(s) and print them to stdout
 
 Options:
 	-u	Use unbuffered i/o (ignored)

busyboxのストイックな設計は、GNUソフトウェアの豊富すぎる機能に慣れた身には戸惑うこともよくありますが、単機能コマンドを組み合わせて必要な処理を実現するのはパズル解き的な快感がありますし、これだけの機能を詰め込んだバイナリが、静的リンクでも1.2Mバイト程度に収まっているのは驚異的であると同時に、きわめて魅力的です。

P-Plamoのinitrdは、このbusyboxのシェル(bashの元になったBourne shell互換のシェル)を使った/initシェルスクリプトを実行してLiveDVDに必要な初期化処理を実現しています。

initrdの初期化処理(その1)

initrdは、isolinux.cfg の設定に従ってカーネルと共にブートローダ(isolinux.bin)によってメモリ上に読み込まれ、isolinux.cfgのinit=/initの指定によって、自らの初期化処理を終えたカーネルは/initスクリプトを起動し、以後の処理を委ねます。

上述のように/initコマンドはsh互換のシェルスクリプトになっていますので、その前半部分を紹介しましょう。

 1 #!/bin/sh
 2 export PATH=/bin:.
 3 
 4 mount -t proc proc /proc
 5 mount -t sysfs sys /sys

まずPATHをbusyboxを収めている/binに設定し、次にカーネルの内部情報を利用するためのproc fsやsysfsをマウントしています。ここで使っているmountコマンドも、その実体はbusyboxです。

 6 
 7 for i in exportfs.ko aufs.ko  squashfs.ko ; do
 8   insmod /lib/modules/$i
 9 done
10 
11 # nls_euc-jp.ko needs vfat.ko
12 for i in isofs.ko nls_iso8859-1.ko nls_ascii.ko nls_cp932.ko cdrom.ko loop.ko sr_mod.ko sg.ko fat.ko vfat.ko  nls_euc-jp.ko ; do
13    insmod /lib/modules/$i
14 done
15 
16 # for keyboard operation
17 for i in libps2.ko atkbd.ko hid.ko usbhid.ko ; do
18     insmod /lib/modules/$i
19 done

これらはカーネルに各種モジュールドライバを組み込む作業です。P-PlamoではPlamo-4.72と同等のカーネルを使っているので、squashfsのドライバやloopbackファイルシステム、CD/DVDのファイルシステム用のドライバなどもモジュールになっているため、必要なドライバは事前にinitrd上でカーネルに組み込んでおかないと、DVDをマウントしたり、DVD上のファイルをループバック機能を用いてマウントすることができません。また、後述するシェルに落ちた場合に操作できるように、キーボード用のドライバも読み込ませています(17行目⁠⁠。

これらのモジュールはinitrd上の/lib/modules ディレクトリに格納されています。

20 
21 mount_ok=0
22 
23 # check USB device first
24 if [ $mount_ok -eq 0 ]; then
25     sleep 5
26     echo -n "trying USB device:"
27     for i in sda sdb sdc sdd sde sdf sdg sdh ; do
28     if [ -d /sys/block/$i ]; then
29             removable=`cat /sys/block/$i/removable`
30             if [ $removable -eq 1 ]; then
31                 echo -n "$i "
32                 partition="$i"1
33                 mount  /dev/$partition /cdrom 2> /dev/null
34                     if [ $? = "0" ]; then
35                         if [ -f /cdrom/isolinux/rootimg.squash ]; then
36                             echo "found P-Plamo image on /dev/$partition"
37                             CD_DEV="/dev/$partition"
38                             mount_ok=1
39                             break
40                         else
41                             umount /cdrom
42                         fi 
43                     fi
44             fi
45     fi    
46     done
47 fi

最近のP-PlamoはUSBメモリからブートしても使えるようにしており、起動時にはまずUSBメモリ上に必要なファイルがあるかを調べています。

USBメモリはSCSIデバイスとして認識されるので、27行目からのループで、sdaからsdhまでの SCSIデバイスを対象に、sysfs上にその名前のブロックデバイスが存在するかを調べて(28行目⁠⁠、もしあればそのデバイスがリムーバブルデバイスかをチェックして(29行目⁠⁠、リムーバブルデバイスならばP-Plamoの入ったUSBメモリの可能性があるので、そのデバイスの最初のパーティションをinitrd上の/cdromにマウントしてisolinux/rootimg.squashファイルが存在するかをチェックしています(35行目⁠⁠。

マウントしたUSBメモリ上にisolinux/rootimg.squashファイルが見つかればマウント処理は終了しますが、見つからなかった場合はumountして(41行目⁠⁠、次のデバイスをチェックします。

USBメモリ上にisolinux/squashfs.imgファイルが見つからなければ、次にDVDメディアのチェックに移ります。DVDメディアはUSBメモリよりも待ち時間を長めにしないといけないので、チェックは後回しにしています。

48 
49 # check CD device
50 if [ $mount_ok -eq 0 ]; then
51     sleep 10
52     echo -n "trying CD device:"
53     for i in sr0 sr1 sr2 ; do
54     if [ -d /sys/block/$i ]; then
55         echo -n "$i "
56         mount -t iso9660 /dev/$i /cdrom -o ro 2> /dev/null
57         sleep 3
58         if [ $? = "0" ]; then
59             if [ -f /cdrom/isolinux/rootimg.squash ]; then
60                 echo "found P-Plamo image on /dev/$i"
61                 CD_DEV="/dev/$i"
62                 mount_ok=1
63                 break
64             else
65                 umount /cdrom
66             fi 
67         fi
68         fi
69     done
70 fi

最近のPlamo Linuxでは、従来のATAドライバを廃して、ATAデバイスも新しいlibata経由でアクセスするようにしているので、/dev/hdc等のATAデバイスに接続されたDVDドライブもSCSIのCD/DVDドライブである/dev/sr0等に見えます。

53行目にあるように、最初から3つのディスクドライブ(/dev/sr[012])について、USBメモリの時と同様、sysfsでブロックデバイスとして認識できるデバイスをマウントしてisolinux/rootimg.squashの有無をチェックしています。

71 
72 # cannot find any CD device
73 if [ $mount_ok -eq 0 ]; then
74     echo "cannot find partition include squashfs. cannot continue booting. exit"
75     exec /bin/sh
76 fi

USBメモリやDVD上にisolinux/rootimg.squash が見つからなければ、その旨のメッセージを出力し(74行目⁠⁠、initrd 上のシェル(/bin/sh 実体はbusybox)に後を任せてスクリプトを終了します(75行目⁠⁠。この状態でシェルに落ちるということは何か致命的な問題が生じているのですが、先にキーボード用のモジュールドライバも組み込んでいるので、問題が何かはbusyboxを対話的に操作して調べることができます。

実のところ、Red Hat系のinitrdが使っているnashのような専用コマンドではなく、SuSE系のように処理に必要なコマンドのみをlibcと共にinitrdに組み込む形式でもなく、P-Plamoのinitrdにbusyboxを使っているのは、このようにトラブった場合にシェルが使えて対話的な操作でトラブルの原因を追求できるからです。

この後、initの処理はsquashfsをルートファイルシステムにするための処理に移りますが、だいぶ長くなったので、それらは次回に譲りましょう。

おすすめ記事

記事・ニュース一覧