過去数回に渡ってLinuxの起動の仕組みを紹介してきました。今回はその最後としてudev とそれが利用するsysfs について紹介しましょう。udevを使えば、従来はあらかじめ用意しておく必要があったデバイスファイルを必要に応じて動的に作成することができ、周辺機器の活線挿抜などにも、より柔軟に対応することができます。
デバイスファイルとは
コンピュータが機能するためには、ディスプレイやキーボード、ハードディスクなど、さまざまな周辺機器が必要で、これら周辺機器をうまく取り扱うことがOSの重要な仕事になります。Linuxの元となったUNIXでは、さまざまな種類の周辺機器を統一的に扱うために「周辺機器もファイルと同様に扱う 」ことにしました。このアイデアを実現するために導入されたのがデバイスファイル (スペシャルファイル)です。
デバイスファイルは、アプリケーションからはファイルと同じように見えますが、これらデバイルファイルへの読み書きは、カーネルが自動的に対応する周辺機器のコマンドに変換して実行します。
デバイスファイルは/devディレクトリに置かれており、ls -lコマンドで見ると、先頭に - が表示される通常ファイルやdが表示されるディレクトリとは異なり、cかbが表示されます。また、ファイルサイズが表示されるべき欄には、サイズではなく2つの数字が並んで表示されます。
# ls -l /dev
crw-rw-rw- 1 root root 14, 12 2008-06-29 10:33 adsp
crw-rw-rw- 1 root root 14, 4 2008-06-29 10:33 audio
....
brw-rw---- 1 root floppy 2, 0 2008-06-29 10:32 fd0
crw-rw-rw- 1 root root 1, 7 2008-06-29 10:32 full
crw-rw---- 1 root root 250, 0 2008-06-29 10:32 fw0
brw-r----- 1 root disk 3, 0 2008-06-29 10:32 hda
brw-r----- 1 root disk 3, 1 2008-06-29 10:32 hda1
brw-r----- 1 root disk 3, 2 2008-06-29 10:32 hda2
....
デバイスファイルに付けられた2つの数字は、「 (デバイス)メジャー番号 」 、「 (デバイス)マイナー番号 」と呼ばれ、メジャー番号でハードウェアの種類を、マイナー番号で同種のハードウェアの中の種類を、それぞれ区別するために使われます。
たとえば上記のデバイスファイルの例では、/dev/hda、/dev/hda1、/dev/hda2はメジャー番号がそれぞれ3で、マイナー番号が0、1、2になっているので、一台目のATA(IDE)ハードディスクはメジャー番号が3、その上のパーティションはそれぞれマイナー番号で区別される(ハードディスク全体はマイナー番号0) 、ということがわかります。これらメジャー番号、マイナー番号は、カーネルにあらかじめ登録してあるハードウェアの識別番号になります。
デバイスファイルを利用することで、アプリケーションからは/dev/hda1にアクセスすれば、そこに接続されているハードディスクの種類に関わらず、1台目のATA(IDE)ハードディスクの最初のパーティションに対する操作になります。
ATA(IDE)HDDの場合は規格が統一されているのでそれほど問題にはなりませんが、SCSIアダプタ経由で接続するSCSI HDDの場合は、SCSIアダプタごとにコマンド体系が異なっているので、 それらをアプリケーションレベルで操作しようとすると大変な作業になります。 そのような手間を隠蔽して、アプリケーションには統一的な方法でハードウェアを利用させることが、 カーネルの重要な仕事です。
一方、カーネルの側では、アプリケーションからの/dev/hda1に対する操作は、デバイスファイルの定義によってメジャー番号3、マイナー番号1の周辺機器に対するものと判断され、その識別番号に該当するデバイスドライバに対してコマンドを発行して、実際のハードウェアを操作することになります。
UNIX/Linuxでは、このような仕組みを用いて個別の周辺機器に対する操作を隠蔽し、アプリケーションからは抽象化されたデバイスファイルを操作するだけで周辺機器を扱えるようになっています。
デバイスファイルの仕組みはアプリケーションにとっては便利なものですが、デバイスファイルを経由することに統一した結果、たとえカーネルに認識されていても、デバイスファイルが存在しない周辺機器はアプリケーションから操作することができなくなりました。そのため、従来のUNIX/Linuxでは、カーネルが操作できる(対応している)すべての周辺機器用のデバイスファイルをあらかじめ用意しておく必要がありました。
一方、最近のLinuxカーネルでは前回紹介したようなモジュールドライバの機能を使って、操作できる周辺機器を動的に変更することができるようになり、あらかじめ対応しているすべての周辺機器用のデバイスファイルを用意することは現実的ではなくなりました。また、最近ではUSBやメモリカードなど、動作中に着脱できる周辺機器が増えてきて、その意味からもあらかじめすべてのデバイスファイルを用意することは難しくなってきました。
このような状況を改善するために提案されたのがudev という仕組みです。
udev では、udevd というデーモンがカーネルの内部情報を監視ており、カーネルが新たな周辺機器を検知すれば、その機器に応じたデバイスファイルを動的に作成することができます。最近のLinuxではこの仕組みを用いて、あらかじめすべてのデバイスファイルを用意するのではなく、実際に接続されてカーネルが認識している周辺機器用のデバイスファイルのみを動的に生成できるようになっています。
udevdとsysfs
udevd がカーネルの内部情報を監視すると言いましたが、UNIX/Linuxの設計ではカーネルとカーネル以外のアプリケーションは隔離されており、カーネル内部のデータをアプリケーションが直接参照することはできません。そのために用意されているのが sys fs (sys ファイルシステム)という仕組みです。
sys fsは/sysにマウントされた仮想的なファイルシステムで、動作中のカーネルの内部構造がファイルシステムの構造に投影されて表示され、ユーザ領域のアプリケーションから参照したり変更したりできるようになっています。
かつては/procにマウントされた仮想ファイルシステムであるproc fs(procファイルシステム)を用いてカーネルの内部情報を利用していましたが、proc fsはもともとプロセス情報を管理するためのファイルシステムでカーネルの内部情報用のファイルシステムではないこと、また必要に応じて追加してきたad hocな拡張が積み重なって見通しが悪くなったため、最近のカーネルでは内部情報はsys fsを利用して公開するように変更されました。
sys fsは現在も開発が進行中の機能のためドキュメントも少なく、仕様などもカーネルのバージョンによっていろいろと変更されていて、筆者自身も手探り状態なため詳細な解説をすることはできませんが、たとえば/sys/blockの下にはカーネルが認識しているブロックデバイスの一覧の情報が表示されています。
# ls /sys/block
hda/ hdc/ ram0/ ram10/ ram12/ ram14/ ram2/ ram4/ ram6/ ram8/ sda/
hdb/ hdd/ ram1/ ram11/ ram13/ ram15/ ram3/ ram5/ ram7/ ram9/ sdb/
/sys/block/hdaディレクトリには、1台目のATA(IDE)HDDに関する情報が収められています。
# ls /sys/block/hda
capability device@ hda2/ hda4/ queue/ removable slaves/ subsystem@
dev hda1/ hda3/ holders/ range size stat uevent
# ls /sys/block/hda/device
block:hda@ bus@ drivename driver@ firmware media modalias model power/ serial subsystem@ uevent
# cat /sys/block/hda/device/model
WDC WD800BB-22HEA1
# cat /sys/block/hda/device/serial
WD-WMAJ51314493
このように、/sys/block/hda以下にはハードディスクのサイズやパーティションの切り方といった情報だけではなく、モデル番号やシリアルナンバーといった情報も登録されています。
一方udevは、システム起動の初期にデーモン(udevd )として起動され、カーネルから新しい周辺機器が接続された旨の通知を受けとると、このsysfsの情報を元に、その機器に適したデバイスファイルを作成します。その際に利用されるのが udev.rules と呼ばれる設定ファイル(ルールファイル)です。
かつてudevと同等の機能を提供するdevfsと呼ばれる仕組みが開発されたことがありました。udevがユーザ領域で動くのに対して、devfsはカーネル内部で動くように設計されていました。
この設計の結果、devfsはカーネル自身の情報を効率よく利用できるというメリットはあったものの、カーネルに組み込まれてしまうために設定ルールの柔軟性に欠け、開発のテンポもカーネルの他の部分の開発速度に追従できなかったため、公式リリースに至ることなく開発は中止され、必要な機能はudevとして新しく開発されることになったそうです。
udev.rulesを読む
udevのルールファイルは/etc/udev/rules.d ディレクトリにまとめられています。今回もfedora core 9の例を用いてみます。
# ls /etc/udev/rules.d/
05-udev-early.rules 60-persistent-storage-tape.rules 80-drivers.rules
10-console.rules 60-persistent-storage.rules 85-pcscd_ccid.rules
40-alsa.rules 60-wacom.rules 85-pcscd_egate.rules
40-multipath.rules 61-persistent-storage-edd.rules 88-clock.rules
40-redhat.rules 64-device-mapper.rules 90-alsa.rules
50-udev-default.rules 64-md-raid.rules 90-hal.rules
60-cdrom_id.rules 70-mdadm.rules 91-drm-modeset.rules
60-libmtp.rules 70-persistent-cd.rules 95-pam-console.rules
60-net.rules 70-persistent-net.rules 95-udev-late.rules
60-pcmcia.rules 75-cd-aliases-generator.rules 97-bluetooth.rules
60-persistent-input.rules 75-persistent-net-generator.rules
udevのルールファイルはこのように先頭に2ケタの数字を付けたファイル名になっており、番号の小さいファイルのルールから順に適用されるようになっています。 udevのルールは後述する"last_rule" オプションなどを指定しない限りすべてのルールが適用されるようになっており、1つの周辺機器に対して異なるルールファイルがそれぞれ別のデバイスファイルを作成することもあります。
それでは実際のファイルを眺めてみましょう。
# cat -n /etc/udev/rules.d/05-udev-early.rules
1 # do not edit this file, it will be overwritten on update
2
3 # sysfs is populated after the event is sent
4 ACTION=="add", KERNEL=="[0-9]*:[0-9]*", SUBSYSTEM=="scsi", WAIT_FOR_SYSFS="ioerr_cnt"
5
udevのルールファイルも他の多くの設定ファイル同様、#から行末まではコメントと見なされるので、05-udev-early.rulesの実際のルールは最後の1行だけです。
udevのルールファイルは‘ ==’ で条件を記述し、‘ =’ で条件に記述した際の動作を記述するようになっています。複数の条件を並べるとそれらが順に適用され、より細かい条件に絞り込むことも可能です。
ACTION, KERNEL, SUBSYSTEMというのはカーネルから通知される情報に対する条件で、ACTION が周辺機器の着脱について、KERNEL がカーネルが認識している周辺機器の名前について、SUBSYSTEM がカーネル内部のサブシステムの部位について、それぞれ一致するかどうかをチェックします。
上記の例では「周辺機器が新しく接続(カーネルに認識)された際(ACTION=="add") 」 、「 その機器の名前をカーネルが0から9までの数字とコロン(:)のみで認識し(KERNEL=="[0-9]*:[0-9]*") 」 、「 その機器を扱うのがSCSIサブシステムならば(SUBSYSTEM=="scsi") 」 、「 その機器用に定義されているioerr_cntのタイミング分、SYSFS の動作待ちをする(WAIT_FOR_SYSFS="ioerr_cnt") 」という意味になります。このルールはカーネルに認識されたSCSIデバイスが反応を返すようになるまでの時間待ち用です。
次に 50-udev-default.rules を見てみましょう。50-udev-default.rules はその名の通りudevのデフォルトルールで、多くの周辺機器がこのルールによってデバイスファイルと結び付けられています。このファイルは長いので一部だけを摘み読みします。
# cat -n /etc/udev/rules.d/50-udev-default-rules
1 # do not edit this file, it will be overwritten on update
2
3 KERNEL=="pty[pqrstuvwxyzabcdef][0123456789abcdef]", GROUP="tty", MODE="0660", OPTIONS="last_rule"
4 KERNEL=="tty[pqrstuvwxyzabcdef][0123456789abcdef]", GROUP="tty", MODE="0660", OPTIONS="last_rule"
5 KERNEL=="ptmx", GROUP="tty", MODE="0666", OPTIONS="last_rule"
このルールでは、最初にptyやttyといった端末入力用デバイスに関するルールが並んでいます。3行目から5行目のルールでは、カーネルからptyやttyといった名称の情報が届いた場合、グループをtty(GROUP="tty" ) 、パーミッションを0660(MODE="0660" )にした同名のデバイスファイルを/dev 以下に作成します。OPTIONS="last_rule" はこのルールが最終的なルールであって、以後の設定で同じ条件が適合した場合でもそのルールは適用されないことを指示します。
26 # input
27 KERNEL=="mouse*|mice|event*", NAME="input/%k", MODE="0640"
28 KERNEL=="ts[0-9]*|uinput", NAME="input/%k", MODE="0600"
29 KERNEL=="js[0-9]*", NAME="input/%k", MODE="0644", SYMLINK+="%k"
これらのルールはマウスやジョイスティックに関するもので、27行目ではカーネルからmouseやmice, eventという情報が来れば/dev/input以下にその名称でデバイスファイルを0640のパーミッションで作成することを指示します。%k はカーネルから届いた名称に置換され、mouse0ならば/dev/input/mouse0というデバイスファイルが作成されることになります。
29行目のSYMLINK はデバイスファイルと共にシンボリックリンクを作成する指示で、カーネルからjs0といった情報が届けば、/dev/input/js0を作ると共に、/dev/js0へシンボリックリンクを作成します。
61 # block, tapes, block-releated
62 SUBSYSTEM=="block", GROUP="disk", MODE="0640"
63 SUBSYSTEM=="block", KERNEL=="sr[0-9]*", SYMLINK+="scd%n"
64 SUBSYSTEM=="scsi", KERNEL=="[0-9]*:[0-9]*", ACTION=="add", ATTR{type}=="0|7|14", ATTR{timeout}="60"
65 SUBSYSTEM=="scsi", KERNEL=="[0-9]*:[0-9]*", ACTION=="add", ATTR{type}=="1", ATTR{timeout}="900"
これらはブロックデバイスに関するルールで、62行目では「カーネルのブロックサブシステムが操作する機器の場合(SUBSYSTEM=="block") 」 、デバイスファイルの所属グループを「diskグループ(GROUP="disk") 」 、パーミッションを「0640(MODE="0640") 」にして、デバイスファイルを作成することを指示してます。このルールはブロックサブシステムに該当する機器全般に適用されるルールで、HDDやCD-ROMに関する個別のルールは別途定義されています。
63行目はSCSI CD-ROMに関するルールで、カーネルからsr0といった情報が届けば/dev/sr0を作ると共に/dev/scd0 にリンクを張り、/dev/scd0 という名前でも使えるようにしています。%n はカーネルが認識している機器番号(sr0ならば0、 sr1ならば1、等)に置換されます。
64行目ではSCSIサブシステム(SUBSYSTEM=="scsi")が扱う機器で、カーネルが数字とコロンのみの名称で認識
( KERNEL=="[0-9]*:[0-9]*")した機器が接続され(ACTION=="add") 、その種類が0か7か14の場合(ATTR{type}=="0|7|14" ) 、タイムアウトまでの時間を60秒に設定します(ATTR{timeout}="60" ) 。65行目では同様に種類が1の場合、タイムアウトの時間を長く取っています。
ATTR{type}で示される機器の種類は、SCSIドライバあたりで定義されているのだと思いますが、どのような分類になっているかの具体的記述は見つけられませんでした。手元の環境で調べた限りではSCSI(SATA)HDDが0、 CD/DVDライタが5でした。
106 # do not delete static device nodes
107 ACTION=="remove", NAME=="?*", TEST=="/lib/udev/devices/$name", OPTIONS+="ignore_remove"
50-udev-default.ruleの最終行である107行目は接続されていた機器が取り外された際(ACTION=="remove")の指定です。通常、機器が取り外されるとそのデバイスファイルも削除されるのですが、この行では/lib/udev/device/を調べて、そこに名前が登録されているデバイスについては"ignore_remove" オプションを指定してデバイスファイルを削除しないようにしています。
今回はudevルールのごく一部しか紹介できませんでしたが、udevにはこれら以外にもさまざまな機能が用意され、デバイスファイルの動的な作成だけではなく、活線挿抜時のさまざまな動作が指定できるようになっており、最近のLinuxでは、これらの機能を使ってUSBやフラッシュメモリの活線挿抜に対応しています。
正確に言うと、udevが扱うのは活線挿抜時のデバイスファイルの作成やデバイスの自動マウントといったシステムの裏方的な作業で、接続したUSBメモリのアイコンがデスクトップ上に自動表示される、といったユーザ向けの作業は、カーネルからのイベント通知を受けたhalがdbus経由でアナウンスし、それを受け取ったデスクトップ環境のVFSレイヤーが適切なアイコンを表示する、という流れになっています。
udev情報の調べ方
sys fsにどのような情報が登録されているかを知るには、上述のように/sysディレクトリ以下を直接調べることもできますが、udevに付属のudevinfo というコマンドで調べることもできます。udevinfo に -a オプションを付けると、指定したデバイスだけでなく、そのデバイスが接続されている上位デバイスについても表示されます。また-pオプションはsys fs上の調べたいデバイス名です。以下の例は、USB接続のメディアプレイヤーについて調べています。
# udevinfo -a -p /sys/bus/usb/devices/1-2
looking at device '/devices/pci0000:00/0000:00:10.4/usb1/1-2':
KERNEL=="1-2"
SUBSYSTEM=="usb"
DRIVER=="usb"
ATTR{configuration}=="Media"
ATTR{bNumInterfaces}==" 1"
ATTR{bConfigurationValue}=="1"
ATTR{bmAttributes}=="80"
ATTR{bMaxPower}=="500mA"
ATTR{urbnum}=="68227"
ATTR{idVendor}=="041e"
ATTR{idProduct}=="4154"
ATTR{bcdDevice}=="1061"
ATTR{bDeviceClass}=="00"
ATTR{bDeviceSubClass}=="00"
ATTR{bDeviceProtocol}=="00"
ATTR{bNumConfigurations}=="1"
ATTR{bMaxPacketSize0}=="64"
ATTR{speed}=="480"
ATTR{busnum}=="1"
ATTR{devnum}=="4"
ATTR{version}==" 2.00"
ATTR{maxchild}=="0"
ATTR{quirks}=="0x0"
ATTR{authorized}=="1"
ATTR{manufacturer}=="CREATIVE"
ATTR{product}=="ZEN Stone"
ATTR{serial}=="40030033C9630A97"
looking at parent device '/devices/pci0000:00/0000:00:10.4/usb1':
KERNELS=="usb1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
....
この例からもわかるように、sys fsには周辺機器の種類によってさまざまに異なる情報が登録されます。どのような情報が登録されるかはデバイスドライバごとに決まっており、USBデバイスならば上記の例のように製品名やシリアル番号まで知ることができます。この製品名やシリアル番号をudevのルールファイルに指定して、自分の使っているメディアプレイヤは常に特定のマウントポイントにマウントする、といった指定も可能です。