これまで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