hotplug カーネルサブシステムはデバイスの追加と削除を、udevd
の助けを借りて適切なドライバを読み込んだり関連するデバイスファイルを作成することで、動的に取り扱います。現代的なハードウェアと仮想化を使えば、ほとんどすべてのデバイスはホットプラグ対応と言ってよいでしょう。具体的に言えば、USB/PCMCIA/IEEE 1394 周辺機器から SATA ハードドライブ、さらには CPU やメモリにいたるまでのほとんどすべてがホットプラグに対応しています。
カーネルは必要なドライバとデバイス ID を関連付けるデータベースを持っています。このデータベースは起動中にさまざまなバスで検出された周辺機器用のすべてのドライバを読み込んだりする際、追加的なホットプラグデバイスが接続された際に使われます。デバイスの使用準備が整ったら、メッセージが udevd
に送信され、udevd
は対応するエントリを /dev/
内に作成します。
ホットプラグ接続の出現前、デバイスに固定された名前を割り当てることは簡単でした。名前は単純にデバイスが接続されたバスの位置を基にしていました。しかしこのやり方はデバイスが接続されるバスの位置が決まっていない場合に問題となります。典型的な例は、コンピュータがディスクドライブとして認識するデジタルカメラや USB メモリを使う場合です。最初に接続されたデバイスは /dev/sdb
、2 番目に接続されたデバイスは /dev/sdc
と名付けられるかもしれません (/dev/sda
はコンピュータのハードドライブを表します)。デバイスに対するデバイス名は固定されませんし、デバイス名はデバイスが接続された順番に依存します。
さらに、デバイスのメジャー/マイナー番号に動的な値を使うドライバが増えています。動的なメジャー/マイナー番号を使うことで、あるデバイスに対する静的なエントリを持つことが不可能になります。なぜなら、これらのエントリは再起動の後に変化するかもしれないからです。
udev はまさにこの問題を解決するために作られました。
udev は新しいデバイスの出現についてカーネルから通知を受けると、/sys/
内の対応するエントリを調べて、与えられたデバイスに関するさまざまな情報 (特にデバイスを一意に識別する情報 (ネットワークカードの MAC アドレス、USB デバイスのシリアル番号など)) を収集します。
この情報を武器にして、udev は /etc/udev/rules.d/
と /lib/udev/rules.d/
に含まれるすべてのルールを調査します。この過程で udev はデバイスに割り当てる名前、作成するシンボリックリンクの名前 (シンボリックリンクはデバイスに別名を与えるために作成されます)、実行するコマンドを決定します。上記ディレクトリに含まれるすべてのファイルが調査され、すべてのルールが順番に評価されます (「GOTO」指示文を使う場合を除きます)。そのため、与えられたイベントに対応する複数のルールがあるかもしれません。
ルールの構文はとても単純です。つまり各行には、選択基準と変数代入命令が含まれます。選択基準は反応を必要とするイベントを選ぶのに使われ、変数代入命令はイベントに対して行う動作を定義します。選択基準と変数代入命令は単純にコンマで区切られており、演算子を使って選択基準 (==
または !=
などの比較演算子を付ける) と変数代入命令 (=
、+=
、:=
などの演算子を付ける) を暗黙のうちに区別します。
比較演算子は以下の変数に使われます。
KERNEL
。カーネルがデバイスに割り当てた名前を意味します。
ACTION
。イベントに対する動作を意味します (デバイスが追加されたら「add」で、デバイスが取り外されたら「remove」です)。
DEVPATH
。デバイスの /sys/
エントリのパスを意味します。
SUBSYSTEM
。要求を生成したカーネルサブシステムを意味します (たくさんの種類がありますが、「usb」、「ide」、「net」、「firmware」などがその例です)。
ATTR{attribute}
。デバイスの /sys/$devpath/
ディレクトリ内の attribute ファイルの内容を意味します。これで MAC アドレスやその他のバス固有識別子がわかります。
KERNELS
、SUBSYSTEMS
、ATTRS{attributes}
。対象のデバイスの親デバイスの 1 つに対するさまざまなオプションに対して一致を検査します。
PROGRAM
。指定されたプログラムを使ってテストを実行することを意味します (プログラムが 0 を返したら真、それ以外を返したら偽になります)。プログラムの標準出力の内容は保存され、RESULT
テストによって再利用されます。
RESULT
。PROGRAM
が最後に呼び出された時に保存された標準出力に対してテストを実行することを意味します。
演算子の右側引数には、複数の値に同時にマッチするようなパターン式を使うことが可能です。たとえば、*
は任意の文字列に (空文字列にも) マッチします。そして ?
は任意の文字にマッチし、[]
は角括弧の間にリストされた文字セットにマッチします (最初の文字が !
の場合は、その否定にマッチします。また、文字の連続範囲は a-z
のように表記します)。
代入演算子に関して、=
は値を代入します (そして現在の値を入れ替えます)。さらに、リストに代入する場合、リストを空にした後、割り当てた値だけを代入します。:=
は同じことをしますが、後から値を変更できなくなります。+=
はリストにアイテムを追加します。代入演算子を使って変更できる変数は以下です。
NAME
。/dev/
内に作成するデバイスファイル名を意味します。最初に代入された値だけが考慮され、他は無視されます。
SYMLINK
。同じデバイスを指すシンボリックリンクのリストです。
OWNER
、GROUP
、MODE
。デバイスを所有するユーザとグループおよびパーミッションを定義します。
RUN
。イベントに応答する際に実行するプログラムのリストを意味します。
これらの変数に割り当てる値の中で、以下の置換変数を使うことが可能な場合があります。
$kernel
または %k
。KERNEL
と同じ値です。
$number
または %n
。デバイスの割り当て番号を意味します。たとえば、sda3
の場合「3」です。
$devpath
または %p
。DEVPATH
と同じ値です。
$attr{attribute}
または %s{attribute}
。ATTRS{attribute}
と同じ値です。
$major
または %M
。デバイスのカーネルメジャー番号を意味します。
$minor
または %m
。デバイスのカーネルマイナー番号を意味します。
$result
または %c
。PROGRAM
によって起動された最後のプログラムの出力文字列を意味します。
そして最後に、%%
と $$
。それぞれパーセントとドル記号を意味します。
上のリストは完全なものではありません (最も重要なパラメータの抜粋です)。完全なリストは udev(7) マニュアルページをご覧ください。
単純な USB メモリに固定された名前を割り当てる場合を考えましょう。最初に、一意的な方法で USB メモリを識別するために必要な要素を見つけなければいけません。このために、USB メモリを取り付け、udevadm info -a -n /dev/sdc
を実行してください (ここで、/dev/sdc は USB メモリに割り当てられた実際のデバイス名で置き換えてください)。
#
udevadm info -a -n /dev/sdc
[...]
looking at device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0/block/sdc':
KERNEL=="sdc"
SUBSYSTEM=="block"
DRIVER==""
ATTR{range}=="16"
ATTR{ext_range}=="256"
ATTR{removable}=="1"
ATTR{ro}=="0"
ATTR{size}=="126976"
ATTR{alignment_offset}=="0"
ATTR{capability}=="53"
ATTR{stat}==" 51 100 1208 256 0 0 0 0 0 192 25 6"
ATTR{inflight}==" 0 0"
[...]
looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0':
KERNELS=="9:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{device_blocked}=="0"
ATTRS{type}=="0"
ATTRS{scsi_level}=="3"
ATTRS{vendor}=="I0MEGA "
ATTRS{model}=="UMni64MB*IOM2C4 "
ATTRS{rev}==" "
ATTRS{state}=="running"
[...]
ATTRS{max_sectors}=="240"
[...]
looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2':
KERNELS=="9:0:0:0"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}=="iCfg"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="80"
ATTRS{bMaxPower}=="100mA"
ATTRS{urbnum}=="398"
ATTRS{idVendor}=="4146"
ATTRS{idProduct}=="4146"
ATTRS{bcdDevice}=="0100"
[...]
ATTRS{manufacturer}=="USB Disk"
ATTRS{product}=="USB Mass Storage Device"
ATTRS{serial}=="M004021000001"
[...]
新しいルールを作るために、デバイスの変数および親デバイスの変数に対するテストを行います。上の例から、以下のような 2 つのルールを作成します。
KERNEL=="sd?", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/disk"
KERNEL=="sd?[0-9]", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/part%n"
これらのルールをたとえば /etc/udev/rules.d/010_local.rules
という 1 つのファイルに書き込んだら、USB メモリを取り外し、もう一度取り付けてください。この USB キーに関連付けられたディスクを表す /dev/usb_key/disk
と第 1 パーティションを表す /dev/usb_key/part1
が生成されたことと思います。