仙石浩明の日記

2007年9月29日

10行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く)

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

4件のコメント »

  1. 20行できる高精度ハードウェア自動認識

    さえないTips系のようなタイトルになってしまいましたが、これは驚きです。しかし一方で悲しい(今までの苦労は…)。 これまでLinuxのハードウェア自動認識と言えば、/sys/bus/pci/devices以下と、/lib/modules/`uname -r`/modules.pcimapを照らし合わせて解析していくのが

    コメント by 古橋貞之の日記 — 2007年9月30日 @ 01:45

  2. 「10行でできる高精度ハードウェア自動認識」の高速化

    仙石浩明の日記: 10行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く) すばらしい。確かに modules.alias を使う方が、簡単かつ確実に必要なモジュールを読み込むことができそう。さっそくこの方法を使って initramfs の init スクリプトを

    コメント by もしもし、matsuuですが... — 2007年9月30日 @ 12:38

  3. 最近の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もこのスクリプトでドライバの自動読み込みが可能でした。

    コメント by K — 2007年12月13日 @ 10:45

  4. 2行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く)

    「10行でできる高精度ハードウェア自動認識」にコメントを頂いた:
    最近の modprobe は、
    自分で勝手に modules.alias を探してくれるようになっているようです。
    この機能を使うと、
    より簡単かつ高速に自動認識が可能になります。
    そうだったのか… orz

    コメント by 仙石浩明の日記 — 2007年12月25日 @ 08:04

この投稿へのコメントの RSS フィード。

コメントする