20行できる高精度ハードウェア自動認識
さえないTips系のようなタイトルになってしまいましたが、これは驚きです。しかし一方で悲しい(今までの苦労は…)。
これまでLinuxのハードウェア自動認識と言えば、/sys/bus/pci/devices以下と、/lib/modules/`uname -r`/modules.pcimapを照らし合わせて解析していくのが定石でした。USBにも対応しようとすると、もう一つ大変です。
しかしこれからの常識は、/sys/bus/*/devices/*/modaliasと/lib/modules/`uname -r`/modules.aliasです。
/lib/modules/`uname -r`/modules.aliasを見てみると、↓このようになっています。
# Aliases extracted from modules themselves. alias usb:v1604p8005d*dc*dsc*dp*ic*isc*ip* snd_usb_usx2y alias usb:v1604p8007d*dc*dsc*dp*ic*isc*ip* snd_usb_usx2y …中略… alias usb:v0D96p5200d000[1-9]dc*dsc*dp*ic*isc*ip* usb_storage alias usb:v0D96p410Ad[1-9]*dc*dsc*dp*ic*isc*ip* usb_storage …中略… alias pci:v00001725d00007174sv*sd*bc01sc06i00* sata_vsc alias pci:v00001106d00003249sv*sd*bc*sc*i* sata_via …
usbやpciの他に、ieee1394やpcmciaなどがあります。
/sys/bus/*/devices/*/modaliasは、↓こうなります。
$ cat /sys/bus/*/devices/*/modalias pci:v00001002d00005952sv00001002sd00005952bc06sc00i00 pci:v00001002d00005A34sv00000000sd00000000bc06sc04i00 …中略… usb:v0000p0000d0206dc09dsc00dp00ic09isc00ip00 usb:v056Ap0014d0314dc00dsc00dp00ic03isc01ip02 …
/sys/bus/*/devices/*/modaliasと、/lib/modules/`uanme -r`/modules.aliasの各行をワイルドカードでマッチしていけば、カーネルモジュール名が得られます。
これをRubyとシェルスクリプトで実装してみました。
まずはRuby。※同日追記: 問題あり。下に修正版
#!/usr/bin/env ruby def detect_kmod(modules_alias_path) devices = Dir.glob("/sys/bus/*/devices/*/modalias").sort.map {|path| File.read(path) } kmods = [] File.foreach(modules_alias_path) {|line| line.rstrip! trash, pattern, kmod = line.split(" ",3) devices.each {|dev| if File.fnmatch?(pattern, dev) kmods.push(kmod) end } } kmods.uniq end p detect_kmod("/lib/modules/#{`uname -r`.strip}/modules.alias")
続いてシェルスクリプト。※同日追記: 下に改良版
#!/bin/sh detect_kmod() { modules_alias_path="$1" kmods="" devices=`echo /sys/bus/*/devices/*/modalias | sort | xargs cat` while read line ; do pattern=`echo $line | cut -d ' ' -f 2` for dev in $devices; do case $dev in $pattern) kmods="$kmods `echo $line | cut -d ' ' -f 3`" ;; *) ;; esac done done < $modules_alias_path echo "$kmods" | tail -n +2 | sort | uniq } detect_kmod /lib/modules/`uname -r`/modules.alias
シェルスクリプト版はRuby版と比べて40倍くらい遅いので注意。
実行してみると、
$ time ./detect_kmod.rb ["snd_hda_intel", "usb_storage", "wacom", "usbmouse", "usbkbd", "usbhid", "ohci_hcd", "ehci_hcd", "usbcore", "aic7xxx", "shpchp", "skge", "sk98lin", "e1000", "serio_raw", "psmouse", "ohci1394", "generic", "atiixp", "pata_atiixp", "ata_generic", "ahci"] 0.41user 0.10system 0:00.51elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1166minor)pagefaults 0swaps
$ time ./detect_kmod.sh ahci aic7xxx ata_generic atiixp e1000 ehci_hcd generic ohci1394 ohci_hcd pata_atiixp psmouse serio_raw shpchp sk98lin skge snd_hda_intel usb_storage usbcore usbhid usbkbd usbmouse wacom 12.61user 10.33system 0:22.76elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+3548933minor)pagefaults 0swaps
確かに検出できています。
ちなみに、PCIバスに繋がっているコントローラを経由して繋がっているデバイス(USB, IEEE1394, CardBusなど)を検出するには、このスクリプトを2回使う必要があります。
まず1回目で、PCIバスにUSBコントローラ/IEEE1394コントローラ…用のカーネルモジュールをロードします。これでUSBデバイスやIEEE1394デバイスが見えるようになります。2回目で各デバイス用のカーネルモジュールをロードできます。
※同日追記:
シェルスクリプト版が大幅に改良されました!>10行でできる高精度ハードウェア自動認識 - 仙石浩明の日記
※2007/12/18追記:さらに良い方法がありました(後述)
10行でできる高精度ハードウェア自動認識 - 仙石浩明の日記より
ちなみに古橋さんのスクリプトは、 modules.alias の各行それぞれに対し、 マッチするデバイスが /sys/bus/*/devices/*/modalias に存在すれば、 そのモジュールを読み込む処理になっている。
しかしながら、これだと一つのデバイスに対し、 複数のモジュールが読み込まれてしまうことになるのではないだろうか?…中略…
modules.alias を検索する際は、 マッチする行が見つかった時点で以降の行はスキップしないと、 この例のような余計なモジュール読み込みが起きる恐れがある。 マッチした以降の行を読み飛ばすには、 私が書いた上記 sh スクリプトのように、 /sys/bus/*/devices/*/modalias の各行それぞれに対し、 マッチするモジュールを一つだけ modules.alias から見つけて読み込む処理のほうが、 簡単に書けるのではないかと思うがどうだろうか。
なるほど。私のマシンでもMarvell Yukon用のドライバで、skgeだけでなくsk98linも読み込まれてしまっています。
この問題だけを修正するなら、↓これで良いのですが、
#!/usr/bin/env ruby def detect_kmod(modules_alias_path) devices = Dir.glob("/sys/bus/*/devices/*/modalias").sort.map {|path| File.read(path) } kmods = [] File.foreach(modules_alias_path) {|line| line.rstrip! trash, pattern, kmod = line.split(" ",3) devices.each {|dev| if File.fnmatch?(pattern, dev) kmods.push(kmod) devices.delete(dev) # この行を追加 end } } kmods.uniq end p detect_kmod("/lib/modules/#{`uname -r`.strip}/modules.alias")
他にも問題が発覚しました。このスクリプトだと、モジュールがバスIDの順に読み込まれるのではなく、modules.aliasに載っている順に読み込まれます。モジュールのロード順が違うと、ネットワークインターフェスの番号(eth0, eth1, …)やデバイスノード名(/dev/sda, /dev/sdb, …)が変わってしまいます。(固定するように設定された環境なら変わらない)
バスID順とmodules.alias順のどちらか一方に統一すれば良いと思うのですが、バスID順の方が適切だと思うので、バスID順に読み込むように修正しました。
・改良版detet_kmod.rb
#!/usr/bin/env ruby def detect_kmod(modules_alias_path) kmods = [] modules_alias = [] File.foreach(modules_alias_path) {|line| line.rstrip! trash, pattern, kmod = line.split(" ",3) modules_alias.push([pattern, kmod]) } Dir.glob("/sys/bus/*/devices/*/modalias").sort.each {|path| dev = File.read(path).rstrip modules_alias.each {|pattern, kmod| if File.fnmatch?(pattern, dev) kmods.push(kmod) break end } } kmods.uniq end p detect_kmod("/lib/modules/#{`uname -r`.strip}/modules.alias")
実行してみます。(pの代わりにpp)
$ time ./detect_kmod.rb ["shpchp", "generic", "ohci_hcd", "ehci_hcd", "snd_hda_intel", "e1000", "aic7xxx", "ohci1394", "skge", "serio_raw", "usbcore", "wacom", "usbkbd", "usb_storage"] 0.43user 0.06system 0:00.49elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2015minor)pagefaults 0swaps
仙石さんの改良版シェルスクリプトと比べてみます。(modprobeの代わりにecho)
$ time ./dev2mod.sh shpchp shpchp generic ohci_hcd ohci_hcd ohci_hcd ohci_hcd ohci_hcd ehci_hcd generic snd_hda_intel e1000 aic7xxx ohci1394 skge serio_raw usbcore wacom usbcore usbkbd usbcore usbcore usbcore usbcore usbcore usb_storage usb_storage 2.74user 0.02system 0:02.77elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+4160minor)pagefaults 0swaps
同じ順番で検出されるようになりました。
※2007/12/18追記:
コメント欄でKさんに教えていただきました。
現在では、modprobeは(busyboxのmodprobeでも)、modaliasを使って非常に簡単にドライバを自動読み込みすることができるようになっています。具体的には、以下のようなコマンドを実行するだけです。
# modprobe `cat /sys/bus/pci/0000¥:00¥:1f.0/modalias`
これで、modprobeが自動的に/lib/modules/`uname -r`/modules.aliasを検索して、依存関係込みで適当なドライバを読んでくれます。これを使うと、もっと簡単で高速にドライバの自動読み込みができるようになります。参考下さい。
なんと!↓これで終わりです。
for mod in /sys/bus/pci/devices/*/modalias; do modprobe `cat $mod`; done
動きました!
おそらくこの方法が一番簡単で高速です。
情報ありがとうございましたm(_ _)m