1. 程式人生 > >跟我一起寫udev規則(譯)

跟我一起寫udev規則(譯)

目錄
介紹 
      關於本文件
      更新歷史
概念
     術語: devfs, sysfs, nodes, etc.
     為什麼?
     內建固定命名設計
編寫規則
     規則檔案和語義
     規則語法
     基本規則
     sysfs匹配屬性
     裝置級聯結構
     字串替換
     字串匹配
從sysfs中查詢合適資訊
     sysfs樹
     udevinfo
     其他方法
高階話題
     許可權和所有權控制
     使用外部程式命名裝置
     發生特定事件時執行外部程式
     環境互動
     另外選項
例子
     USB印表機
     USB相機
     USB硬碟
     USB讀卡器
     USB Palm導航儀
     CD/DVD驅動
     網絡卡
測試和除錯
     讓你的規則跑起來
     udevtest
作者及聯絡方式

介紹
關於本文件

udev面向2.6以上的linux核心在使用者空間提供動態的/dev下固定裝置命名方案. 之前的/dev實現: devfs現在已被廢棄, udev成為繼任者. udev vs devfs是一個敏感的談話內容,在進行比較之前你應該讀一下這個文件(http://kernel.org/pub/linux/utils /kernel/hotplug/udev_vs_devfs).

幾年間你為之使用udev規則的裝置發生改變了,如同規則自身的彈性一樣. 在現代系統中udev為系統外的型別裝置提供了固定的命名方法, 避免了為這些裝置提供定製規則. 但是一些使用者仍然需要額外的定製級別.

本文件假設你已經安裝了udev並使用預設配置執行ok. 這通常通過你的linux發行版做到的.

本文件不會覆蓋規則書寫的方方面面, 只集中介紹所有主要概念. 更多細節資訊可以在udev的man頁中找到.

本文件使用各種例子(一些完全是虛構的)來闡述觀點和概念. 不是所有語法都會顯式的在附帶文字中描述,請確信通過檢視例子規則來獲取完整的理解.

更新歷史
(略)

概念
語義: devfs, sysfs, nodes等
僅僅是基本介紹,可能並不完全準確.
在典型的基於linux的系統中,/dev目錄用來儲存檔案一樣的裝置節點, 它們指向系統中特定的裝置. 每一個節點指向系統的一部分(一個裝置), 可能存在也可能不存在. 使用者空間應用程式可以使用這些裝置節點跟系統硬體打交道,例如, X伺服器"監聽"/dev/input/mice來根據使用者的滑鼠移動來移動可視滑鼠指標.
原來的/dev目錄僅僅在裝置可能在系統中出現時產生,因此/dev目錄一般非常大. 隨之而來的devfs提供了一種易於管理的途徑(注意它僅僅在硬體插入到系統中時產生/dev)以及其他功能,但系統會出現無法容易修復的問題.

udev是一種新的管理/dev目錄的方法,它的設計清除了以前的/dev實現的一些問題並提供了魯棒的路徑向後相容. 為了建立並命名系統中相應的/dev裝置結點,udev需要依賴於根據使用者提供的規則從sysfs中得到的匹配資訊. 本文著重規則書寫的過程,udev相關的任務由使用者自己完成.

sysfs是2.6核心中一個新的檔案系統, 它由核心管理,並匯出當前系統中插入的裝置基本資訊. udev可使用這些資訊建立對應的硬體裝置結點. sysfs掛載在/sys下而且是可瀏覽的. 你可能很希望在使用udev之前刺探下儲存在那兒的有關檔案. 本文中我將交替使用/sys和sysfs術語.

為什麼?
udev規則具有彈性非常強大,這裡是一些你使用規則可以達到的結果:
1. 重新命名裝置節點的預設名字為其他名字
2. 通過建立符號連結到預設裝置節點來提供一個可選的固定的裝置節點名字
3. 基於程式的輸出命名裝置節點
4. 改變裝置節點的許可權和所有權
5. 但裝置節點被建立或刪除時(通常是新增裝置或拔出裝置時)執行一個指令碼
6. 重新命名網路介面

當存在的特定裝置沒有裝置節點時,這不是書寫規則的工作範圍. 即使沒有匹配的規則,udev也會利用核心提供的預設名字來建立裝置節點.

擁有固定命名裝置節點有很多好處. 假設你有兩個USB儲存裝置:一個數碼相機,一個是USB快閃記憶體盤. 這些裝置通過被賦予/dev/sda和/dev/sdb裝置節點,準確的賦值取決於它們連線到系統的順序. 這可能為一些使用者造成麻煩,如果每個裝置每次都可以固定命名,比如/dev/camera和/dev/flashdisk,使用者就會獲益.

內建固定命名方法
udev為系統外的一些裝置型別提供了固定命名,這是一個很有用的特徵,在某些情況下意味著你不用書寫任何規則.

udev為儲存裝置在/dev/disk目錄下提供了系統外命名方法. 要檢視它為你的儲存硬體建立的固定命名,你可以使用下列命名:
#ls -lR /dev/disk
所有儲存型別都可以這麼用. 例如udev為我的根分割槽建立了固定命名連結:/dev/disk/by-id/scsi-SATA_ST3120827AS_4MS1NDXZ- part3. 但我插入我的USB快閃記憶體盤udev就會建立另外一個固定命名節點:/dev/disk/by-id/usb- Prolific_Technology_Inc._USB_Mass_Storage_Device-part1.

規則書寫
規則檔案和語義
為決定如何命名裝置以及執行什麼另外動作,udev會讀取一系列規則檔案. 這些檔案儲存在/etc/udev/rules.d目錄下並且都必須有.rules字尾名.

預設udev規則儲存在/etc/udev/rules.d/50-udev.rules裡面. 你可能發現整個檔案很有意思, 它包含了少量例子,一些預設規則提供了devfs風格的/dev佈局, 但是你不應該直接在這個檔案裡面書寫規則.

/etc/udev/rules.d/下面的檔案通過lexical順序解析,在某些情況下規則的解析順序很重要. 通常來說你希望你的規則可以在預設規則之前解析, 所以我建議你建立一個檔案/etc/udev/rules.d/10-local.rules並把自己的所有規則寫到這裡面去.

在一個規則檔案中, 以"#"開頭的行被認為是註釋. 每一個非空的行都是一條規則. 規則不能跨越多行.

一個裝置可以被多條規則匹配到, 這有著很實用的優點, 例如, 我們可以寫兩個匹配同一個裝置的規則, 每一個規則為裝置提供了它自己的可選命名. 即使分開在不同的檔案種, 兩個可選命名也都會被建立, 要明白udev在找到一個匹配規則後不會停止處理其他規則, 它仍然會繼續查詢並嘗試應用已知的每條規則, 這很重要.

規則語法
每條規則通過一系列鍵值對建立,這些鍵值對通過逗號分隔. 匹配鍵是用來識別要應用規則的裝置的條件, 但規則中對應裝置的所有匹配鍵被處理後,就會應用規則並且賦值鍵的行為也會觸發. 每條規則應該包含至少一個匹配鍵和至少一個賦值鍵.

這是用來闡述上面內容的一個例子規則:
  KERNEL=="hdb",NAME="my_spare_disk"
上述規則包含一個匹配鍵(KERNEL)以及一個賦值鍵(NAME). 這些鍵和它們的屬性的語義將在稍後具體說明. 注意到匹配鍵通過連等號(==)與它的值聯絡起來, 賦值鍵通過等號(=)與它的值關聯.

注意udev不支援任何形式的行連線符, 不要在你的規則種插入任何斷行符,這將會導致udev把你的一條規則看做是多條規則但不會按預料工作.

基本規則
udev提供一些用來書寫精確匹配規則的匹配鍵, 其中一些常用鍵將在下面介紹, 其他將在文件的後面說明. 要得到完整列表可以檢視udev的手冊頁.
KERNEL - 為裝置匹配的核心名字
SUBSYSTEM - 匹配裝置的子系統
DRIVER - 匹配支援裝置的驅動名稱
在你使用一系列匹配鍵來準確匹配裝置後,udev通過賦值鍵為接下來發生的事給你提供更好的控制. 你可以檢視udev的手冊頁檢視完整的賦值鍵列表. 最基本的賦值鍵在下面說明, 其他的將在文件結束時說明.
NAME - 裝置節點應該使用的名字
SYMLINK - 一個裝置節點可選名字的符號連結列表
正如之前所說,udev會為裝置建立一個真正的裝置節點. 如果你希望為裝置節點提供可選名字,你得通過SYMLINK使用符號連結功能, 實際上是維護一個符號連結列表,這些符號連結都會指向真實的裝置節點. 為了維護這些連結我們介紹一個新的附加操作符: +=. 你可以在一個規則中附加多個符號連結到列表中,每個連結通過空格分開.
 KERNEL=="hdb", NAME="my_spare_disk"
上面規則意思是:匹配一個裝置命名為hdb的裝置,把它重新命名為my_spare_disk. 裝置節點出現在/dev/my_spare_disk.
 KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"
上面規則意思是:匹配一個核心命名為hdb以及驅動為ide-disk的裝置,命名裝置節點為預設名字並建立一個指向它的sparedisk符號連結。注 意到我們沒有指明裝置節點名字,於是udev使用預設名字。 為了保留標準/dev佈局,你自己的規則通常沒有NAME但會建立一些SYMLINK並/或執行其他賦值操作.
 KERNEL=="hdc", SYMLINK+="cdrom cdrom0"
上面規則很可能就是你要寫的典型規則。 它在/dev/cdrom和/dev/cdrom0建立了兩個符號連結,都指向/dev/hdc. 再一次地,沒有NAME賦值鍵,所以使用預設的核心名字(hdc).

匹配sysfs屬性
到目前為止介紹的匹配鍵僅僅提供了有限的匹配能力. 實際上我們需要更加優良的控制:我們想基於裝置的高階屬性來識別裝置, 如供應商編碼, 產品編號, 序列號, 儲存能力, 分割槽數等等.

一些驅動匯出這些資訊到sysfs, udev允許我們使用ATTR鍵通過稍捎不同的語法來合併sysfs匹配到自己的規則中.

這裡有一個匹配sysfs中單個屬性例子. 更多細節將在稍後的幫助你基於sysfs屬性書寫規則的文件中提供. 
 SUBSYSTEM=="block", ATTR{size}=="234441648", SYMLINK+="my_disk"
 
裝置級聯
linux核心實際上以樹狀結構展示裝置, 這個資訊通過sysfs顯露出來,在書寫規則時這非常有用. 例如我的硬碟裝置的展示是一個SCSI磁碟裝置的孩子, 這個SCSI磁碟裝置又是一個ATA控制器裝置的孩子, 該控制器又是PCI匯流排裝置的孩子. 你很有可能發現你需要從一個討論中的裝置的雙親那裡引用資訊, 比如我的硬碟裝置的序列號在裝置級別並不暴露出來, 而是在SCSI磁碟級別通過它的直接雙親展現。

目前介紹的四個主要匹配鍵(KERNEL/SUBSYSTEM/DRIVER/ATTR)僅僅跟對應裝置的值匹配, 並不跟雙親裝置的值匹配. udev提供了在樹中向上查詢的匹配鍵變數:
KERNELS - 為裝置匹配的核心名字,或任何雙親裝置中的核心名
SUBSYSTEMS - 匹配裝置的子系統名,或任何雙親裝置中的子系統名
DRIVERS - 匹配支援裝置的驅動名,或任何支援雙親裝置的驅動名
ATTRS - 匹配裝置的sysfs屬性,或任何雙親裝置的sysfs屬性
由於在心裡要考慮到級聯結構,你可能感覺到規則書寫變的有點複雜了. 歇一會吧,有工具可以幫助我們的,稍微獻上.

字串替換
但寫的規則潛在的要處理多個相似的裝置時, udev的printf-like string substitution operators就非常有用了. 你可以在你的規則裡面的任何賦值裡面包含這些操作符, udev在它們執行時會計算.

最常用的操作符是%k和%n. %k計算裝置的核心名, 例如裝置的"sda3"將(預設)出現在/dev/sda3. %n計算裝置(儲存裝置的分割槽號)的核心號碼, 例如"3"將被換成"/dev/sda3".

udev也提供了一些高階功能替換操作符. 在讀完本文剩下內容後可以查詢udev的手冊頁. 以上例子中的操作符也有一個可選的語法 - $kernel和$number. 因此如果你希望在規則中匹配字元%, 你必需寫成%%, 如果你希望匹配字元$, 你必須寫成$$.

為闡述下字串替換的概念,我們來展示幾個例子:
 KERNEL=="mice", NAME="input/%k"
 KERNEL=="loop0", NAME="loop/%n", SYMLINK+="%k"

第一條規則確保滑鼠裝置節點一定出現在/dev/input目錄下(預設是在/dev/mice下面). 第二條規則確保名字為loop0的裝置節點在/dev/loop/0建立,也會照常建立一個符號連結/dev/loop0.

上面規則的使用都比較可疑,因為他們都可以通過不使用任何替換操作符來重寫. 這些替換的真正威力將會在下一節顯現.

字串匹配
不僅有字串精確匹配, udev也允許你使用shell風格的模式匹配. 支援的3種模式為:
* - 匹配任何字元, 匹配0次或多次
? - 匹配任何字元,但只匹配一次.
[] - 匹配任何單個字元, 這些字元在方括號裡面指定, 範圍是受限的.
這裡有一些例子, 注意字串替換符的使用:
 KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
 KERNEL=="hiddev*", NAME="usb/%k"

第一條規則匹配所有軟盤驅動並確保裝置節點放置在/dev/floppy目錄下, 也建立一個預設名字的符號連結. 第二條規則確保hiddev裝置節點放在/dev/usb目錄下面.

從sysfs中查詢資訊
sysfs樹
從sysfs中獲取有意思資訊的概念在之前的例子中已經觸控到了. 為了基於這些資訊書寫規則,你首先需要知道屬性名和他們的當前值.

sysfs實際上是一個非常簡單的結構,邏輯上以目錄形式區分. 每一個目錄包含一定量的檔案(屬性),這些檔案往往都僅僅包含一個值. 也會有一些符號連結,它們把裝置鏈到雙親那裡, 級聯結構已在上面說明了. 

一些目錄被引向頂層裝置路徑,這些目錄展示了擁有對應裝置節點的實際裝置. 頂層裝置路徑可以被分類為包含dev檔案的sysfs目錄, 下列命令可以列舉出這些檔案:
#find /sys -name dev
例如, 在我的系統中, /sys/block/sda目錄就是我的硬碟裝置路徑, 它通過/sys/block/sda/device符號連結鏈向它的雙親SCSI磁碟裝置.

但你書寫基於sysfs資訊的規則時, 只是簡單的匹配鏈條上一部分檔案的屬性內容. 例如, 我可以這樣讀我的硬碟的大小:
#cat /sys/block/sda/size
234441648

在一個udev規則裡面, 我可以使用ATTR{size}=="234441648"來識別這個磁碟. 因為udev反覆遍歷裝置鏈的入口,我可以通過ATTRS匹配另外鏈中的屬性(如:/sys/class/block/sda/device屬性), 但是在處理鏈中不同部分時會有一些告誡, 稍後描述.

雖然這對於關於sysfs的結構和udev如何匹配值的介紹很有用, 但對sysfs的全面梳理是個既耗時也沒必要的事.

udevinfo
敲入udevinfo大概就是你用來建立規則的最直接的工具了。你需要知道的全部就是裝置的sysfs裝置路徑. 下面是一個精簡的例子:
# udevinfo -a -p /sys/block/sda

  looking at device '/block/sda':
  KERNEL=="sda"
  SUBSYSTEM=="block"
  ATTR{stat}==" 128535 2246 2788977 766188 73998 317300 3132216 5735004 0 516516 6503316"
  ATTR{size}=="234441648"
  ATTR{removable}=="0"
  ATTR{range}=="16"
  ATTR{dev}=="8:0"

  looking at parent device '/devices/pci0000:00/0000:00:07.0/host0/target0:0:0/0:0:0:0':
  KERNELS=="0:0:0:0"
  SUBSYSTEMS=="scsi"
  DRIVERS=="sd"
  ATTRS{ioerr_cnt}=="0×0"
  ATTRS{iodone_cnt}=="0×31737"
  ATTRS{iorequest_cnt}=="0×31737"
  ATTRS{iocounterbits}=="32"
  ATTRS{timeout}=="30"
  ATTRS{state}=="running"
  ATTRS{rev}=="3.42"
  ATTRS{model}=="ST3120827AS "
  ATTRS{vendor}=="ATA "
  ATTRS{scsi_level}=="6"
  ATTRS{type}=="0"
  ATTRS{queue_type}=="none"
  ATTRS{queue_depth}=="1"
  ATTRS{device_blocked}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:07.0':
  KERNELS=="0000:00:07.0"
  SUBSYSTEMS=="pci"
  DRIVERS=="sata_nv"
  ATTRS{vendor}=="0×10de"
  ATTRS{device}=="0×037f"

正如你看到的, udevinfo簡單的產生一個你可以在udev規則中作為匹配鍵的屬性列表. 從上面例子中我可以為該裝置產生下面兩條規則:
SUBSYSTEM=="block", ATTR{size}=="234441648", NAME="my_hard_disk"
SUBSYSTEM=="block", SUBSYSTEMS=="scsi", ATTRS{model}=="ST3120827AS", NAME="my_hard_disk"

你可能發現到例子中使用了顏色, 這是為了闡述把裝置屬性和單個雙親裝置屬性並在一起是合法的, 你不能混合匹配多個雙親裝置屬性,這樣你的規則不能工作. 例如,下列規則是不合理的,因為它試圖匹配兩個雙親裝置屬性:
SUBSYSTEM=="block", ATTRS{model}=="ST3120827AS", DRIVERS=="sata_nv", NAME="my_hard_disk"
通常有大量的屬性提供給你,你必需挑選其中一些來建立你的規則. 一般來講,你希望挑選那些能夠固定標誌你的裝置並便於理解的屬性. 上例中我選取的是我的磁碟大小和它的模型號,而沒有使用沒有意義的諸如ATTRS{iodone_cnt}=="0×31737"數字.

仔細觀察下udevinfo的輸出級聯結構效果, 裝置的綠色部分使用了標準匹配鍵如KERNEL和ATTR, 藍色部分和栗色部分使用了雙親遍歷變數如SUBSYSTEMS和ATTRS, 這就是為什麼級聯結構的複雜性實際上是很容易處理的原因,只要確保使用udevinfo建議的準確值就行了.

另外一點需要指出的是udevinfo輸出的文字屬性之間用空格填充(例如上面的ST3120827AS)是很常見的. 在你的規則中你可以新增額外的空格,也可以像我那樣去掉.

使用udevinfo的唯一複雜之處在於要求你知道頂級裝置路徑(例如上面例子中的/sys/block/sda), 這通常並不明顯. 但是, 因為你通常是為已經存在的裝置節點寫規則, 你可以自己使用udevinfo查詢裝置路徑:
#udevinfo -a -p $(udevinfo -q path -n /dev/sda)

可選方法
雖然udevinfo差不多是列舉你要構建的規則的準確屬性的最直接方式, 但一些使用者仍然樂於使用其他工具, 諸如usbview的工具可以顯示類似的資訊集, 這些資訊也可以用在規則中.

高階話題
控制權限和所有權
udev允許你在規則中使用另外的賦值來控制每個裝置的所有權和許可權屬性.

GROUP賦值允許你定義哪個Unix組應該擁有裝置節點. 這裡有一個例子規則, 它定義video組擁有framebuffer裝置:
KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k", GROUP="video"
OWNER鍵可能用處不大, 它允許你定義哪個Unix使用者應該具有裝置節點的擁有許可權. 假設有個臨時情況要你讓join擁有軟盤裝置,你可以使用:
KERNEL="fd[0-9]*", OWNER="join"
udev預設用Unix的0660許可權(擁有者和組員擁有讀寫功能)建立裝置節點. 需要的話你可以在特定裝置的規則中使用包含MODE賦值鍵覆蓋這些預設值. 作為例子,下面的規則定義了inotify節點可以被每個人讀寫:
KERNEL=="inotify", NAME="misc/%k", SYMLINK+="%k", MODE="0666"

使用外部程式來命名裝置
某些情況下你可能要求比udev標準規則提供的更多彈性, 這種情況下你可以請求udev執行一個程式並運用程式的標準輸出來提供裝置命名.

要使用這個功能,你只需簡單的在PROGRAM賦值中指定要執行程式(以及任何闡述)的完整路徑, 然後在NAME/SYMLINK賦值中使用一些%c替換.

下列例子引用一個位於/bin/device_namer的虛構程式. device_namer帶一個表示核心名字的命令列引數, 基於核心名device_namer做一些魔幻變換接著產生一些輸出到普通stdout管道,這些輸出被分割為很多小塊, 每一小塊是一個單詞,塊之間用一個空格分開.

在我們的第一個例子中, 我們假設device_namer輸出塊的數目, 每一個形成當前裝置的一個符號連結(名字可選).
KERNEL=="hda", PROGRAM="/bin/device_namer %k", SYMLINK+="%c"
下一個例子假設device_namer輸出兩塊,第一塊是裝置名字, 第二塊是另外的符號連結名字. 我們現在介紹%c[N]替換, 它引向輸出的第N塊:
KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2}"
再下個例子假設device_namer輸出裝置名的一部分, 後面跟著的是快數, 它將形成另外的符號連結. 我們現在介紹%c{N+}替換, 它將被計算為塊N, N+1, N+2…直到輸出結束.
KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2+}"
輸出塊也可在賦值鍵中使用,而不僅僅是NAME和SYMLINK中. 下面例子使用一個虛構程式來決定哪個Unix組擁有裝置:
KERNEL=="hda", PROGRAM="/bin/who_owns_device %k", GROUP="%c"

特定事件發生時執行外部程式
另外一個書寫udev規則的原因是為了在裝置連線或者斷開時執行一個特定程式. 例如, 你可能想在你的數碼相機連到系統時執行一個指令碼來自動下載相機裡面的所有照片.

不要把這個跟上述的PROGRAM功能弄混淆了, PROGRAM是用來執行產生裝置名字(除此之外不應該做其他事情)的程式. 但這些程式執行時, 裝置節點裝置節點還沒有被建立, 所以對裝置的任何形式的操作都是不可能的.

這裡介紹的功能允許你在裝置節點到位後執行一個程式. 該程式可以作用在裝置上, 但是它不準在時間週期外執行,因為當程式執行時udev會正常中止. 這個限制的一個權宜之計是確保你的程式立即分離自身.

這裡有個展示RUN賦值的例子:
KERNEL=="sdb", RUN+="/usr/bin/my_program"
當/usr/bin/my_program執行時, udev環境的各部分可作為環境變數可用,包括諸如SUBSYSTEM的鍵值. 你也可以使用ACTION環境變數來檢測裝置是否連線或斷開, 它的值是"add"和"remove"其中的一個.

udev並不在任何啟用終端中執行這些程式,也不再shell上下文中執行. 確信你的程式是被標記為可執行的, 如果它是個shell指令碼請確保它以適當的shabang開頭(比如: #!/bin/sh)也不要期望任何標準輸出出現在你的終端上.

環境互動
udev為環境變數提供了一個ENV鍵, 即可用來匹配也可用來賦值.

在賦值情況下,你可以設定稍後匹配的環境變數. 你也可以設定環境變數,供通過上面提供的技巧觸發的任何外部程式使用. 一個虛構的設定環境變數的例子規則如下:
KERNEL=="fd0", SYMLINK+="floppy", ENV{some_var}="value"
在匹配情況下你可以確保規則僅僅依賴一個環境變數的值而執行. 注意udev看到的環境跟你在控制檯上得到的使用者環境不一樣. 一個虛構的涉及環境匹配的規則如下:
KERNEL=="fd0", ENV{an_env_var}=="yes", SYMLINK+="floppy"
上面規則僅僅在udev環境中的$an_env_var的值設為"yes"時才建立/dev/floppy連結.

另外選項
另外一個有用的賦值是OPTIONS列表. 不多的可用的選項有:
all_partitions - 為一個塊裝置建立所有可能的分割槽, 而不是初始檢測到的那些分割槽.
ignore_device - 完全忽略事件.
last_rule - 確保後面的所有規則不會有效.
例如, 下面規則設定我的硬碟節點的組所有權,並且後面的規則對它沒有任何效果:
KERNEL=="sda", GROUP="disk", OPTIONS+="last_rule"

例子
USB印表機
我啟動我的印表機, 它就被賦予了一個裝置節點/dev/lp0. 我對這樣的單調的名字不滿意並打算使用udevinfo幫我寫一個規則來提供一個可選名字:
# udevinfo -a -p $(udevinfo -q path -n /dev/lp0)
  looking at device '/class/usb/lp0':
  KERNEL=="lp0"
  SUBSYSTEM=="usb"
  DRIVER==""
  ATTR{dev}=="180:0"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb1/1-1':
  SUBSYSTEMS=="usb"
  ATTRS{manufacturer}=="EPSON"
  ATTRS{product}=="USB Printer"
  ATTRS{serial}=="L72010011070626380"

我的規則變成了這樣:
SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", SYMLINK+="epson_680"
   
USB相機
跟大多數相機一樣, 我的相機標識自己為一個通過USB匯流排來連線通過SCSI傳輸的外部硬碟. 為了訪問我的相片我先掛載驅動然後拷貝圖片檔案到我的硬碟中.

不是所有相機都這樣工作, 其中一些使用非儲存協議,如gphoto2支援的相機. 在gphoto情況下,你不用為你的裝置寫任何規則,因為純粹由使用者空間控制(而不是指定的核心驅動).

USB相機裝置的一個通常的複雜性在於它們通常標識自己是一個單分割槽磁碟,即帶有/dev/sdb1的/dev/sdb. sdb節點對我毫無用處,但對sdb1有興趣 - 這才是我要掛載的. 這裡有個麻煩因為sysfs是鏈式的, udevinfo為/dev/sdb1產生的有用屬性跟為/dev/sdb產生的一樣, 這導致你的規則潛在的匹配到原始磁碟和分割槽, 這不是你想要的, 你的規則應該明確下.

為解決這個問題,你需要簡單的考慮下sdb和sdb1之間有什麼區別. 令人驚奇的簡單: 只是名字上的區別, 所以我們可在NAME域上使用一個簡單的模式匹配.
# udevinfo -a -p $(udevinfo -q path -n /dev/sdb1)
  looking at device '/block/sdb/sdb1':
  KERNEL=="sdb1"
  SUBSYSTEM=="block"

  looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0':
  KERNELS=="6:0:0:0"
  SUBSYSTEMS=="scsi"
  DRIVERS=="sd"
  ATTRS{rev}=="1.00"
  ATTRS{model}=="X250,D560Z,C350Z"
  ATTRS{vendor}=="OLYMPUS "
  ATTRS{scsi_level}=="3"
  ATTRS{type}=="0"

我的規則: 
KERNEL=="sd?1", SUBSYSTEMS=="scsi", ATTRS{model}=="X250,D560Z,C350Z", SYMLINK+="camera"

USB硬碟
USB硬碟跟USB相機差不多,但典型的使用方式不同. 在相機例子中, 我有講到我對sdb節點沒有興趣, 它僅僅在分割槽(比如使用fdisk)時才有用, 但我為什麼要為我的相機分割槽呢?

當然如果你有一個100GB的USB硬碟, 你希望為它分割槽是很好理解的, 這種情況下我們可以使用udev的字串替換長處:
KERNEL=="sd*", SUBSYSTEM=="scsi", ATTRS{model}=="USB 2.0 Storage Device", SYMLINK+="usbhd%n"
這個規則建立諸如下面的符號連結:
/dev/usbhd - 可被fdisk使用的node
/dev/usbhd1 - 第一塊分割槽(可掛載)
/dev/usbhd2 - 第二塊分割槽(可掛載)

USB讀卡器
USB讀卡器(CompactFlash, SmartMedia等)屬於USB儲存裝置的另外一種範圍, 它有不同的使用需求.

這些裝置特別的在媒介改變時不用通知主機. 所以如果你插入沒有媒介的裝置,接著插入一張卡, 計算機不會意識到這點, 你也不會有媒介的可掛載的sdb1分割槽節點.

一個可能的解決辦法是使用all_partttions選項優點, 它將為每個規則匹配的塊裝置建立16個分割槽節點:
KERNEL="sd*", SUBSYSTEM=="scsi", ATTRS{model}=="USB 2.0 CompactFlash Reader", SYMLINK+="cfrdr%n", OPTIONS+="all_partitions"
你將得到這些命名節點:cfrdr, cfrdr1, cfrdr2, cfrdr3, …, cfrdr15.

USB Palm導航儀
這些裝置作為USB序列裝置工作, 所以預設的你僅僅得到ttyUSB1裝置節點. palm工具依賴於/dev/pilot, 一些使用者希望使用一條規則提供這個.

Carsten Clasohm的部落格上有完整的源. Carsten的規則如下:
SUBSYSTEM=="usb", ATTRS{product}=="Palm Handheld", KERNEL=="ttyUSB*", SYMLINK+="pilot"
注意到產品字串因產品不同而不同, 所以確保你檢查(使用udevinfo)了哪一個可以應用到你自己的產品.

CD/VCD驅動
我的電腦有兩個光碟機: 一個DVD只讀驅動(hdc)和一個DVD燒錄機(hdd). 我不希望這些裝置節點改變除非我物理性的重新接線, 但是一些使用者喜歡擁有諸如/dev/dvd這麼方便的裝置節點.

我們都知道KERNEL是這些裝置的名字, 規則書寫就很簡單了. 這是我的系統上一些規則:
SUBSYSTEM=="block", KERNEL=="hdc", SYMLINK+="dvd", GROUP="cdrom"
SUBSYSTEM=="block", KERNEL=="hdd", SYMLINK+="dvdrw", GROUP="cdrom"

網絡卡
儘管它們都是通過名字引用,網絡卡往往沒有與之關聯的裝置節點. 儘管這樣,規則書寫過程還是相同的.

在規則中簡單的匹配網絡卡MAC地址是有意義的,因為它們是唯一的. 但是, 確信你使用的是udevinfo顯示的準確MAC地址, 因為如果你沒有精確匹配,你的規則不會工作.
# udevinfo -a -p /sys/class/net/eth0
  looking at class device '/sys/class/net/eth0':
  KERNEL=="eth0"
  ATTR{address}=="00:52:8b:d5:04:48"

這是我的規則:
KERNEL=="eth*", ATTR{address}=="00:52:8b:d5:04:48", NAME="lan"
為了讓這個規則生效你得重啟網路驅動, 你可以解除安裝並重新載入模組或簡單的重啟系統即可. 你還得需要重新配置你的系統使用"lan"取代"eth0". 我以前遇到過這樣的麻煩(網絡卡不能重新命名)直到我去除了所有對eth0的引用. 在此之後你應該可以在任何ifconfig或類似的工具的呼叫中使用"lan"而不是"eth0"了.

測試和除錯
讓你的規則跑起來
假定你在一個有inotify支援的最近核心上工作, udev將自動監視你的規則目錄並且自動挑取你對規則檔案的任何修改.

儘管這樣, udev也不會自動重新處理所有裝置並試圖應用新規則. 例如, 如果你寫了個規則來為你的已連線相機新增一個額外符號連結, 你不能指望這個符號連結可以馬上顯現出來.

為使符號連結顯示, 你可以斷開並重連你的相機或者在非可移除裝置情況下執行udevtrigger.

如果你的核心沒有inotify支援, 新規則不會自動被檢測到. 這種情況下你必需在做出更改後執行udevcontrol reload_rules使之生效.

udevtest
如果你知道sysfs中的頂級裝置路徑, 你可以使用udevtest來顯示udev將要執行的動作, 這可能會幫你除錯你的規則. 例如, 假設你想除錯作用在/sys/class/sound/dsp上的規則:
# udevtest /class/sound/dsp
main: looking at device '/class/sound/dsp' from subsystem 'sound'
udev_rules_get_name: add symlink 'dsp'
udev_rules_get_name: rule applied, 'dsp' becomes 'sound/dsp'
udev_device_event: device '/class/sound/dsp' already known, remove possible symlinks
udev_node_add: creating device node '/dev/sound/dsp', major = '14', minor = '3', mode = '0660', uid = '0', gid = '18'
udev_node_add: creating symlink '/dev/dsp' to 'sound/dsp'

注意/sys字首在udevtest命令列中被刪除了, 這是因為udevtest在裝置路徑上操作. 還要留意的是udevtest是一個純粹的測試/除錯工具, 它不建立任何裝置節點無論輸出怎麼建議.

作者以及聯絡方式
本文由Daniel Drake<[email protected]>寫就. 歡迎反饋資訊.

Copyright (C) 2003-2006 Daniel Drake.
This document is licensed under the GNU General Public License, Version 2.