Product SiteDocumentation Site

9.11. ホットプラグ機能: hotplug

9.11.1. はじめに

hotplug カーネルサブシステムはデバイスの追加と削除を、適切なドライバを読み込んだり関連するデバイスファイルを作成する (udevd の助けを借りて) ことで、動的に取り扱います。現代的なハードウェアと仮想化を使えば、ほとんどすべてはホットプラグ対応です: USB/PCMCIA/IEEE 1394 周辺機器から SATA ハードドライブ、さらには CPU やメモリに至るまで。
カーネルは必要なドライバとデバイス ID を関連付けるデータベースを持っています。このデータベースは、起動中に様々なバスで検出された周辺機器用のすべてのドライバを読み込んだり、追加的なホットプラグデバイスが接続された時に、使われます。デバイスの使用準備が整ったら、メッセージが udevd に送信され、udevd は対応するエントリを /dev/ 内に作成します。

9.11.2. 命名問題

ホットプラグ接続の出現前、デバイスに固定された名前を割り当てることは簡単でした。名前は単純にデバイスが接続されたバスの位置を基にしていました。しかしこのやり方では、デバイスが接続されるバスの位置が決まっていない場合に、問題です。典型的な例は、コンピュータがディスクドライブとして認識するデジタルカメラや USB メモリを使う場合です。最初に接続されたデバイスは /dev/sdb 、2 番目に接続されたデバイスは /dev/sdc と名付けられるかもしれません (/dev/sda はコンピュータのハードドライブを表します)。デバイスに対するデバイス名は固定されていません; デバイス名はデバイスが接続された順番に依存します。
さらに、ますます多くのドライバがデバイスのメジャー/マイナー番号に動的な値を使うことで、あるデバイスに対する静的なエントリを持つ事が不可能になります。なぜなら、これらのエントリは再起動の後に変化するかもしれないからです。
udev はまさにこの問題を解決するために作られました。

9.11.3. 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{attribut}: デバイスの /sys/$devpath/ ディレクトリ内の attribute ファイルの内容。これで MAC アドレスやその他のバス固有識別子が判ります。
  • KERNELSSUBSYSTEMSATTRS{attributes} は対象のデバイスの親デバイスの 1 つに対する様々なオプションに対して一致を検査します。
  • PROGRAM: 指定されたプログラムを使ってテストを実行します (0 が返されたら真、それ以外は偽)。プログラムの標準出力の内容は保存され、RESULT テストによって再利用されます;
  • RESULT: PROGRAM が最後に呼び出された時に保存された標準出力に対してテストを実行。
演算子の右側引数には、複数の値に同時にマッチするようなパターン式を使う事が可能です。例えば、* は任意の文字列に (空文字列にも) マッチします; ? は任意の文字にマッチし、[] は角括弧の間にリストされた文字セットにマッチします (最初の文字が感嘆符の場合は、その否定にマッチします。また、文字の連続範囲は a-z のように表記します)。
代入演算子に関して、= は値を代入します (そして現在の値を入れ替えます); リストに代入する場合、リストを空にした後、割り当てた値だけを代入します。:= は同じ事をしますが、後から値を変更できなくなります。+= はリストにアイテムを追加します。代入演算子を使って変更できる変数は以下です:
  • NAME: /dev/ 内に作成するデバイスファイル名。最初に代入された値だけが考慮されます; 他は無視されます;
  • SYMLINK: 同じデバイスを指すシンボリックリンクのリスト;
  • OWNERGROUPMODE はデバイスを所有するユーザとグループおよびパーミッション定義します;
  • 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) マニュアルページをご覧ください。

9.11.4. 具体例

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