ソースコード・リテラシーのススメ

第15回udevを読む

過去数回に渡って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)ハードディスクの最初のパーティションに対する操作になります。

一方、カーネルの側では、アプリケーションからの/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にマウントされた仮想的なファイルシステムで、動作中のカーネルの内部構造がファイルシステムの構造に投影されて表示され、ユーザ領域のアプリケーションから参照したり変更したりできるようになっています。

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.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といった名称の情報が届いた場合、グループをttyGROUP="tty"⁠、パーミッションを0660MODE="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の場合、タイムアウトの時間を長く取っています。

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情報の調べ方

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のルールファイルに指定して、自分の使っているメディアプレイヤは常に特定のマウントポイントにマウントする、といった指定も可能です。

おすすめ記事

記事・ニュース一覧