initramfs - VIVERの技術

今その価値が見直されようとしているかもしれないinitramfs。initramfs内ではほとんど何もできないかと思いきや、試してみると実は「何でもできる」ことが分かります。

そうなると、いろいろやりたくなるわけですが、initramfs内でやることと言えば大体決まっていて(ブートパラメータの解釈、ハードウェア認識、カーネルモジュールのロード、デバイスノードの作成、ネットワークの起動、HDDのマウント、tmpfsのマウントと引き継ぎファイルの避難、switch_rootなどなど)、そのあたりをVIVERの中からinitramfs内のコードを取り出してライブラリ風に使えるようにしようというアイディア(VIVER CORE)があって、ちまちまと作っています。が、WikiFormeなどなどに手を出しているうちに、素晴らしい記事が。


initramfs (initrd) の init を busybox だけで書いてみた - 仙石浩明の日記

じゃ、いよいよハードウェアの自動認識をして、 適切なモジュールのみを組み込むようにしてみようかと思って、 いろいろ探してみたのだが、 どうしたことか適当なスクリプトが見当たらない。 1CD Linux の /linuxrc をいろいろ読んでみたのだが、 いまいちパッとするものがない。 デバイスの ID などがゴリゴリ書いてあるものが大半で、 どれもアドホックすぎるように思えたのである。


かといって、ハードウェアの認識を udev などに行なわせる、 というのは牛刀過ぎるように思えた。 なんたって init を起動する前のブートストラップなのである。 目的は rootfs をマウントするだけなのであるから、 あまりに汎用的な仕掛けは、いかがなものかと思うのである。


というわけで、 busybox だけでハードウェアの自動認識 & モジュール読み込みを実現することを 目標にしてみた。 前フリが長くなった (長すぎ!) が、ようやくここからが本題である。

「1CD Linux の /linuxrc をいろいろ読んでみた」の中にVIVERは入っていたのでしょうか (^_^;) VIVERは1CD Linuxじゃないですね。




私自身VIVERを作るにあたってinitramfsをたくさんいじってきました。そのあたりのノウハウを書いておこうと思います。VIVERの概要はこのあたりのエントリを。




initramfsのcpioアーカイブ

ハードウェア自動認識から書こうと思ったのですが、長くなるので、まずは短い話題から。

作成できれば同じわけですが、私は以下の方法で作っています。VIVER 1.0のMakefile

# cd path/to/dir && find | cpio --quiet -c -o | gzip -9 -c > viverrd.gz

ブートパラメータの利用

ブートローダでカーネルに渡した引数は、/proc/cmdlineから得ることができます。VIVERでは以下のコードでパースしています。(Rubyparseメソッド

  def parse(path, rewrite = false)
    # …略…
    File.open(path) {|file|
      last_ns = GLOBAL_NS

      file.gets.split(" ").each {|cmd|
        if cmd =~ /\:$/
          # 最後が: (名前空間指定)
          last_ns = cmd[0, cmd.length - 1]
        else
          key, val = cmd.split("=", 2)
          if val === nil; val = ""; end
          @entries[ [last_ns, key] ] = val
          $log.debug "parameter: #{last_ns}::#{key} = #{val}"
        end
      }
    }
  end


いろいろ情報が足りていませんが、良きに推測してください ^_^;
見れば分かるかと思いますが、VIVERは名前空間付きのブートパラメータを採用しています。

vmlinuz  namespace1: example=hoge sample=fuga namespace2: param

VIVERはinitramfsが終わった後で「プラグインバンドル」を実行するのですが、それぞれのプラグインごとに名前空間を持ち、ブートパラメータで挙動を制御できる(=ブートローダの設定で挙動を制御できる=ネットワークブートする時にサーバーからクライアントの挙動を制御できる)というわけです。



デバイスノードの作成

/devにデバイスノードを作成する方法ですが、もちろんudevでも良いと思います。initramfsで何をやってもいいわけで、そのままinitramfs内でSSHサーバーを起動しようと、Webサーバーを起動しようと。On RAMで動作。
さて、デバイスノードですが、ブロックデバイスのデバイスノード(hda、sda1など)は、/sys/blockを元に作成できます。
シェルスクリプトで書けば↓こんな感じでしょうか。

for dev in /sys/block/*;do
  line=`cat $dev/dev`
  major=`echo $line | cut -d ":" -f 1`
  minor=`echo $line | cut -d ":" -f 2`
  mknod /dev/`basename $dev` b $major $minor
done

ハードウェア自動認識

さてさて、ハードウェア自動認識です。

VIVERではinitramfsにRubyインタプリタを入れていますので、いろいろ重量のあることをやっています。VIVERのソースコード(master.rbから開始)


まず、ハードウェアを自動認識にあたって、PCIバスに繋がったデバイスと、USBに繋がったデバイスを検出する場合では、処理を別ける必要があります。PCIに繋がったデバイスを検出するには、先の記事のように/sys/bus/pci/devicesと/lib/modules/`uname -r`/modules.pcimapを照らし合わせることで自動認識できます。sourcePCIメソッドdetectPCIメソッド


これで検出できたカーネルモジュールをロードすると、PCIバスに繋がったデバイスが使えるようになります。PCIバスに繋がったUSBホストコントローラなど。この後にUSBデバイスの検出を行います。(ちなみに、もう一度PCIの検出手順を繰り返すと、CardBusのデバイスも使えるようになります)



そもそもinitramfs内でUSB Mass Storage以外のUSBデバイスが必要になるのか?という話がありますが、続行します。

USBも基本的には/bus/usb/devicesと/lib/modules/`uname -r`/modules.usbmapを照らし合わせるだけで良いのですが、ファイルのフォーマットがPCIとは違うため、マッチングのコードは違うものを用意する必要があります。(PCIよりも情報が多い分複雑)
VIVERでは、「ベンダーID/デバイスIDによる判別」(PCIと同じ)だけではなく、「USBデバイスクラスによる判別」も行っています。sourceUSBメソッドdetectPCIメソッド

  • modules.usbmap
# usb module         match_flags idVendor idProduct bcdDevice_lo bcdDevice_hi bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass bInterfaceSubClass bInterfaceProtocol driver_info
snd-usb-usx2y        0x0003      0x1604   0x8001    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00               0x0
snd-usb-usx2y        0x0003      0x1604   0x8007    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00               0x0
…中略…
usb-storage          0x000f      0x03eb   0x2002    0x0100       0x0100       0x00         0x00            0x00            0x00            0x00               0x00               0x0
usb-storage          0x000f      0x03ee   0x6901    0x0000       0x0100       0x00         0x00            0x00            0x00            0x00               0x00               0x0
…以下略…
  • /sys/bus/usb/devices
# USBホストコントローラ
$ ls /sys/bus/usb/devices/1-0\:1.0/
bAlternateSetting  bInterfaceClass  bInterfaceNumber  bInterfaceProtocol  bInterfaceSubClass  bNumEndpoints  bus@  driver@  ep_81@  modalias  power/  subsystem@  uevent  usb_endpoint:usbdev1.1_ep81@

# USB HDD
$ ls /sys/bus/usb/devices/6-3/
6-3:1.0/             bDeviceClass     bDeviceSubClass  bMaxPower           bNumInterfaces  bmAttributes  busnum         dev     driver@  idProduct  manufacturer  power/   quirks  speed       uevent                 usb_endpoint:usbdev6.3_ep00@
bConfigurationValue  bDeviceProtocol  bMaxPacketSize0  bNumConfigurations  bcdDevice       bus@          configuration  devnum  ep_00@   idVendor   maxchild      product  serial  subsystem@  usb_device:usbdev6.3@  version

ベンダーID/デバイスIDによる判別の場合は、

  1. /sys/bus/usb/devices/X/にidVendorファイルとidProductファイルが存在する
  2. vendor = `cat idVendor`
  3. device = `cat idProduct`
  4. ("%s%s", vendor, device)を、usbmapのidVendor, idProductとマッチ(ただしidVendorが0x000のものは除外する)

で検出できるようです。



デバイスクラスによる判別の場合は、

  1. /sys/bus/usb/devices/X/にbInterfaceClassファイルが存在する
  2. base_class = `cat bInterfaceClass`
  3. base_classをusbmapのbInterfaceClassとマッチ

bInterfaceClassの他にbInterfaceSubClassもあるのですが、これは使ってもあまり意味がありません。サブクラスまでマッチしても結局カーネルモジュールは同じだったりします。



…と、自動判別もできるわけですが、実際には"ehci_hcd", "ohci_hcd", "uhci_hcd", "usb_storage", "usbnet", "usbkbd", "usbmouse", "usbhid"あたりをロードしておけば、大体事足ります。




ストレージモジュール

実は上記のハードウェア自動認識をしただけでは、HDDはまだ見えません。IDEなら"ide-disk.ko"(CDは"ide-cd.ko")、SCSIなら"sd_mod.ko"(CDはsr_mod.ko)…と、ディスクの接続方法別にストレージモジュール(←勝手に命名)(とそのモジュールが依存しているモジュール)をロードする必要があります。


VIVERは、PCIバスに繋がったストレージコントローラのClass ID(/sys/bus/pci/devices/*/class)を見ることで、ディスクがどのように接続されているかを判別し、対応するモジュールをロードしています。getModulePCIメソッド

…と、VIVERはがんばっているわけですが、実際のところ、sd_modやide-disk、libataくらいは決め打ちでロードしてしまえばいいというのが現実です。




ファイルシステムタイプ判別

ここまできたら、ファイルシステムタイプ(ext3、XFS、…)まで自動判別するべきです。そこまでして始めてマウントができます。

基本的な方針は、「知っているファイルシステムタイプでひたすらマウントしてみて、マウントできたら、そのファイルシステムタイプ」というもの。しかしこれだけでは格好悪い。というわけでVIVERは、ディスクの容量、接続方法(USB、IDE…)、IDEならプライマリかセカンダリかを元に、ファイルシステムタイプをある程度推測してからマウントを試みるようにしています。guessPartitionメソッドguessFSTypeメソッドとprobeFSTypeメソッド


容量が大きければ、おそらくHDDなので、USB接続ならFAT、IDEならNTFSかな?(←VIVERの自動認識を使うような環境では)容量が少なければおそらくフラッシュメモリなので、たぶんFAT。


ファイルシステムタイプを判別する前に、ファイルシステムモジュール(ext3.ko、xfs.koなど)をロードしておく必要があります。loadFilesystemModulesメソッド



どこまで自動認識するか、どこから決め打ちするかが問題になるわけですが、がんばっても実際は決め打ちで何とかなることが多い、というのが私の経験です。ネットワークブートなどでinitramfsを自作する場合でも、PCIデバイスの自動検出くらいにしておくのが無難かと思われます。(VIVER COREが出来るまでは!)