これまでLinuxのハードウェア自動認識と言えば、 /sys/bus/pci/devices 以下と、 /lib/modules/`uname -r`/modules.pcimap を照らし合わせて 解析していくのが定石でした。 USBにも対応しようとすると、もう一つ大変です。
しかしこれからの常識は、 /sys/bus/*/devices/*/modalias と
/lib/modules/`uname -r`/modules.alias です。古橋貞之の日記「20行できる高精度ハードウェア自動認識」から引用
すばらしい。 確かに modules.alias を使う方が、 簡単かつ確実に必要なモジュールを読み込むことができそう。 さっそくこの方法を使って initramfs の init スクリプトを書き直してみた。
tmp=/tmp/dev2mod echo 'dev2mod(){ while read dev; do case $dev in' > $tmp sort -r /lib/modules/`uname -r`/modules.alias \ | sed -n 's/^alias *\([^ ]*\) *\(.*\)/\1)modprobe \2;;/p' >> $tmp echo 'esac; done; }' >> $tmp . $tmp rm $tmp cat /sys/bus/*/devices/*/modalias | dev2mod
わずかに 8行 (^^)
(9/30追記: modules.alias を逆順ソートしておく必要があることが判明、sort -r を追加)。
シェルスクリプト版はRuby版と比べて40倍くらい遅いので注意。同ページ(古橋貞之の日記)から続けて引用
sh スクリプトの名誉のために言っておくと、 私が書いた上記 sh スクリプトだと、 古橋さんの Ruby 版と比べて 4倍くらいの遅さで済んでいる。
% time ./dev2mod ide_cd intel_agp intelfb uhci_hcd ...(中略)... libusual usbcore 0.252u 0.012s 0:00.77 33.7% 0+0k 0+0io 0pf+0w % time ./detect_kmod.rb ["ivtv", "snd_intel8x0", "intelfb", "libusual", "ftdi_sio", "usbhid", "uhci_hcd", "ehci_hcd", "usbcore", "via_velocity", "eepro100", "e100", "3c59x", "psmouse", "ide_cd", "i2c_i801", "hw_random", "intel_agp"] 0.072u 0.008s 0:00.18 38.8% 0+0k 0+0io 0pf+0w
ちなみに古橋さんのスクリプトは、
modules.alias の各行それぞれに対し、
マッチするデバイスが /sys/bus/*/devices/*/modalias に存在すれば、
そのモジュールを読み込む処理になっている。
しかしながら、これだと一つのデバイスに対し、
複数のモジュールが読み込まれてしまうことになるのではないだろうか?
古橋さんが同日追記されているように、 複数のモジュールが読み込まれること自体は簡単に修正可能で、 むしろモジュールの読み込み順が modules.alias に載っている順になることのほうが問題。 この問題点を解決するため、 /sys/bus/*/devices/*/modalias の各行それぞれに対し、 マッチするモジュールを modules.alias から見つける修正版が追記された。 さすが古橋さん、すばやい。9/30追記
例えば古橋さんのスクリプトだと、 私の手元のマシンでは e100 と eepro100 の両方のモジュールが読み込まれてしまう。 つまり、
% cat /sys/bus/pci/devices/0000:01:08.0/modalias pci:v00008086d00001050sv0000107Bsd00004043bc02sc00i00
が、modules.alias の次の二つの行にマッチするため、 このようなことが起こる。
alias pci:v00008086d00001050sv*sd*bc*sc*i* eepro100 alias pci:v00008086d00001050sv*sd*bc02sc00i* e100
modules.alias を検索する際は、 マッチする行が見つかった時点で以降の行はスキップしないと、 この例のように複数のモジュール読み込みが起きる恐れがある。 マッチした以降の行を読み飛ばすには、 私が書いた上記 sh スクリプトのように、 /sys/bus/*/devices/*/modalias の各行それぞれに対し、 マッチするモジュールを一つだけ modules.alias から見つけて読み込む処理のほうが、 簡単に書けるのではないかと思うがどうだろうか。
とはいえ、実際の NIC は Intel Pro 10/100 だったりする (^^;) ので、 読み込むべきモジュールは e100 であるような気もする。 もし e100 が正しいモジュールであるのなら、 modules.alias における eepro100 のパターンが適切ではないということになるのかも。9/30追記「*」を多く含むパターンは「後で」マッチさせたほうが、 より適切なモジュールを選択できると考えられるため、 modules.alias を逆順ソートしておくことにした。 これにより、eepro100 ではなく、e100 を読み込むようになった。9/30さらに追記
参考までに initramfs の /init スクリプト全体を添付しておく:
#!/bin/ash export PATH="/bin:/sbin:/usr/bin:/usr/sbin" KERNVER="`uname -r`" FILES=`echo /* | sed 's@/mnt @ @'` if [ -n "$AUFS" ]; then if [ -z "$INIT_TMPFS" ]; then mount -t tmpfs none /mnt cd /mnt cp -a $FILES . mkdir mnt export INIT_TMPFS=1 exec switch_root . /init fi fi mount -t proc none /proc ROOTFLAG="ro" INIT="/sbin/init" for p in `cat /proc/cmdline`; do v=`echo $p | sed 's/[^=]*=//'` case $p in root=*) ROOT=$v ;; rootfstype=*) ROOTPARM="-t $v" ;; rootflags=*) ROOTFLAG=$v ;; nfsroot=*) NFSROOT=`echo $v | sed 's/,.*//'` NFSOPTS=`echo $v | sed 's/[^,]*,*//'` NFSPARM='-t nfs' if [ -n "$NFSOPTS" ]; then NFSPARM="$NFSPARM -o $NFSOPTS" unset NFSOPTS fi ;; init=*) INIT=$v ;; [0-6S]) RUNLEVEL=$p ;; esac done unset p v ROOTPARM="$ROOTPARM -o $ROOTFLAG" unset ROOTFLAG if [ -n "$RUNLEVEL" ]; then INIT="$INIT $RUNLEVEL" fi hwclock --hctosys mount -t sysfs none /sys tmp=/tmp/dev2mod echo 'dev2mod(){ while read dev; do case $dev in' > $tmp sort -r /lib/modules/$KERNVER/modules.alias \ | sed -n 's/^alias *\([^ ]*\) *\(.*\)/\1)modprobe \2;;/p' >> $tmp echo 'esac; done; }' >> $tmp . $tmp rm $tmp unset tmp cat /sys/bus/*/devices/*/modalias | dev2mod modprobe pcmcia cat /sys/bus/*/devices/*/modalias | dev2mod umount /sys modprobe af_packet modprobe unix modprobe ext3 modprobe xfs if [ -z "$NIC_DEV" ]; then NIC_DEV=eth0 fi if [ -n "$NFSROOT" ]; then ifconfig lo 127.0.0.1 if ifconfig $NIC_DEV; then ifconfig $NIC_DEV up sleep 3 udhcpc --retries=2 -i $NIC_DEV mount -t nfs $NFSPARM $NFSROOT /mnt fi elif [ -n "$ROOT" ]; then mount $ROOTPARM $ROOT /mnt fi if [ -n "$INIT_DEBUG" ]; then set exec /bin/sh fi umount /proc if [ -n "$AUFS" ]; then case $AUFS in /*) mnt="/mnt$AUFS" ;; *) mnt="/mnt" ;; esac mkdir /.rw /.root mount -t aufs -o br:/.rw:${mnt}=ro none /.root unset mnt cd /.root if [ -n "$INIT_TMPFS" ]; then test -d initrd || mkdir initrd pivot_root . initrd initrd/bin/busybox rm -rf `echo $FILES /.root | sed 's@/@initrd/@g'` exec $INIT else cp -a /bin/busybox . rm -rf $FILES ./busybox mount --move . / exec ./busybox chroot . /bin/sh -c "rm ./busybox; exec $INIT" fi fi exec switch_root /mnt $INIT
20行できる高精度ハードウェア自動認識
さえないTips系のようなタイトルになってしまいましたが、これは驚きです。しかし一方で悲しい(今までの苦労は…)。 これまでLinuxのハードウェア自動認識と言えば、/sys/bus/pci/devices以下と、/lib/modules/`uname -r`/modules.pcimapを照らし合わせて解析していくのが
Comment by 古橋貞之の日記 — 2007年9月30日 @ 01:45
「10行でできる高精度ハードウェア自動認識」の高速化
仙石浩明の日記: 10行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く) すばらしい。確かに modules.alias を使う方が、簡単かつ確実に必要なモジュールを読み込むことができそう。さっそくこの方法を使って initramfs の init スクリプトを
Comment by もしもし、matsuuですが... — 2007年9月30日 @ 12:38
最近のmodprobeは、自分で勝手にmodules.aliasを探してくれるようになっているようです。この機能を使うと、より簡単かつ高速に自動認識が可能になります。
for modalias in /sys/bus/*/devices/*/modalias; do
modprobe `cat $modalias` > /dev/null 2>&1
done
もちろん、/lib/modules/`uname -r`/modules.aliasが存在して、modprobeがモジュールを自動検索できるようになっていなければなりません。
busyboxのmodprobeもこのスクリプトでドライバの自動読み込みが可能でした。
Comment by K — 2007年12月13日 @ 10:45
2行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く)
「10行でできる高精度ハードウェア自動認識」にコメントを頂いた:
最近の modprobe は、
自分で勝手に modules.alias を探してくれるようになっているようです。
この機能を使うと、
より簡単かつ高速に自動認識が可能になります。
そうだったのか… orz
…
Comment by 仙石浩明の日記 — 2007年12月25日 @ 08:04