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
が生成されたことと思います。