天天看點

嵌入式 使用udev高效、動态地管理Linux 裝置檔案

本文以通俗的方法闡述 udev 及相關術語的概念、udev 的配置檔案和規則檔案,然後以 Red Hat Enterprise Server 為平台示範一些管理裝置檔案和查詢裝置資訊的執行個體。本文會使那些需要高效地、友善地管理 Linux 裝置的使用者受益匪淺,這些使用者包括 Linux 最終使用者、裝置驅動開發人員、裝置測試人員和系統管理者等等。

概述:

Linux 使用者常常會很難鑒别同一類型的裝置名,比如 eth0, eth1, sda, sdb 等等。通過觀察這些裝置的核心裝置名稱,使用者通常能知道這些是什麼類型的裝置,但是不知道哪一個裝置是他們想要的。例如,在一個充斥着本地磁盤和光纖磁盤的裝置名清單 (<code>/dev/sd*</code>) 中,使用者無法找到一個序列号為“35000c50000a7ef67”的磁盤。在這種情況下,udev 就能動态地在 <code>/dev</code>目錄裡産生自己想要的、辨別性強的裝置檔案或者裝置連結,以此幫助使用者友善快捷地找到所需的裝置檔案。

udev 是 Linux2.6 核心裡的一個功能,它替代了原來的 devfs,成為目前 Linux 預設的裝置管理工具。udev 以守護程序的形式運作,通過偵聽核心發出來的 uevent 來管理 <code>/dev</code>目錄下的裝置檔案。不像之前的裝置管理工具,udev 在使用者空間 (user space) 運作,而不在核心空間 (kernel space) 運作。

我們都知道,所有的裝置在 Linux 裡都是以裝置檔案的形式存在。在早期的 Linux 版本中,<code>/dev</code>目錄包含了所有可能出現的裝置的裝置檔案。很難想象 Linux 使用者如何在這些大量的裝置檔案中找到比對條件的裝置檔案。現在 udev 隻為那些連接配接到 Linux 作業系統的裝置産生裝置檔案。并且 udev 能通過定義一個 udev 規則 (rule) 來産生比對裝置屬性的裝置檔案,這些裝置屬性可以是核心裝置名稱、總線路徑、廠商名稱、型号、序列号或者磁盤大小等等。

動态管理:當裝置添加 / 删除時,udev 的守護程序偵聽來自核心的 uevent,以此添加或者删除 <code>/dev</code>下的裝置檔案,是以 udev 隻為已經連接配接的裝置産生裝置檔案,而不會在 <code>/dev</code>下産生大量虛無的裝置檔案。

自定義命名規則:通過 Linux 預設的規則檔案,udev 在 /dev/ 裡為所有的裝置定義了核心裝置名稱,比如 <code>/dev/sda、/dev/hda、/dev/fd</code>等等。由于 udev 是在使用者空間 (user space) 運作,Linux 使用者可以通過自定義的規則檔案,靈活地産生辨別性強的裝置檔案名,比如<code>/dev/boot_disk、/dev/root_disk、/dev/color_printer</code>等等。

設定裝置的權限和所有者 / 組:udev 可以按一定的條件來設定裝置檔案的權限和裝置檔案所有者 / 組。在不同的 udev 版本中,實作的方法不同,在“如何配置和使用 udev”中會詳解。

下面的流程圖顯示 udev 添加 / 删除裝置檔案的過程。

嵌入式 使用udev高效、動态地管理Linux 裝置檔案

裝置檔案:由于本文以較通俗的方式講解 udev,是以裝置檔案是泛指在 <code>/dev/</code>下,可被應用程式用來和裝置驅動互動的檔案。而不會特别地區分裝置檔案、裝置節點或者裝置特殊檔案。

devfs:devfs是 Linux 早期的裝置管理工具,已經被 udev 取代。

sysfs:sysfs是 Linux 2.6 核心裡的一個虛拟檔案系統 <code>(/sys)</code>。它把裝置和驅動的資訊從核心的裝置子產品導出到使用者空間 (userspace)。從該檔案系統中,Linux 使用者可以擷取很多裝置的屬性。

devpath:本文的 devpath是指一個裝置在 sysfs檔案系統 <code>(/sys)</code>下的相對路徑,該路徑包含了該裝置的屬性檔案。udev 裡的多數指令都是針對 devpath操作的。例如:sda的 devpath是 <code>/block/sda</code>,sda2 的 devpath是 <code>/block/sda/sda2</code>。

核心裝置名稱:裝置在 sysfs裡的名稱,是 udev 預設使用的裝置檔案名。

下面會以 RHEL4.8 和 RHEL5.3 為平台,分别描述 udev 的配置和使用:

從 Fedora3 和 Red Hat Enterprise4 開始,udev 就是預設的裝置管理工具,無需另外下載下傳安裝。

Linux 使用者可以通過該檔案設定以下參數:

udev_root:udev 産生的裝置所存放的目錄,預設值是 <code>/dev/</code>。建議不要修改該參數,因為很多應用程式預設會從該目錄調用裝置檔案。

udev_db:udev 資訊存放的資料庫或者所在目錄,預設值是 <code>/dev/.udev.tdb</code>。

udev_rules:udev 規則檔案的名字或者所在目錄,預設值是 <code>/etc/udev/rules.d/</code>。

udev_permissions:udev 權限檔案的名字或者所在目錄,預設值是 <code>/etc/udev/permissions.d/</code>。

default_mode/ default_owner/ default_group:如果裝置檔案的權限沒有在權限檔案裡指定,就使用該參數作為預設權限,預設值分别是:<code>0600/root/root</code>。

udev_log:是否需要 syslog記錄 udev 日志的開關,預設值是 no。

udev_log:syslog記錄日志的級别,預設值是 err。如果改為 info 或者 debug 的話,會有冗長的 udev 日志被記錄下來。

實際上在 RHEL5.3 裡,除了配置檔案裡列出的參數 udev_log外,Linux 使用者還可以修改參數 udev_root和 udev_rules( 請參考上面的“RHEL4.8 的 udev 配置檔案”),隻不過這 2 個參數是不建議修改的,是以沒顯示在 udev.conf 裡。

可見該版本的 udev.conf 改動不小:syslog預設會記錄 udev 的日志,Linux 使用者隻能修改日志的級别 (err、info、degub 等 );裝置的權限不能在 udev.conf 裡設定,而是要在規則檔案 (*.rules) 裡設定。

在 RHEL4.8 的 udev,裝置的權限是通過權限檔案來設定。

RHEL4.8 裡 udev 的權限檔案會為所有常用的裝置設定權限和 ownership,如果有裝置沒有被權限檔案設定權限,udev 就按照 udev.conf 裡的預設權限值為這些裝置設定權限。由于篇幅的限制,上圖隻顯示了 udev 權限檔案的一部分,該部分設 <code>置了</code>所有可能連接配接上的磁盤裝置和錄音帶裝置的權限和 ownership。

而在 RHEL5.3 的 udev,已經沒有權限檔案,所有的權限都是通過規則檔案 <code>(*.rules)</code>來設定,在下面的規則檔案配置過程會介紹到。

規則檔案是 udev 裡最重要的部分,預設是存放在 <code>/etc/udev/rules.d/</code>下。所有的規則檔案必須以“<code>.rules</code>”為字尾名。RHEL 有預設的規則檔案,這些預設規則檔案不僅為裝置産生核心裝置名稱,還會産生辨別性強的符号連結。例如:

但這些連結名較長,不易調用,是以通常需要自定義規則檔案,以此産生易用且辨別性強的裝置檔案或符号連結。

此外,一些應用程式也會在 <code>/dev/</code>下産生一些友善調用的符号連結。例如規則 40-multipath.rules 為磁盤産生下面的符号連結:

udev 按照規則檔案名的字母順序來查詢全部規則檔案,然後為比對規則的裝置管理其裝置檔案或檔案連結。雖然 udev 不會因為一個裝置比對了一條規則而停止解析後面的規則檔案,但是解析的順序仍然很重要。通常情況下,建議讓自己想要的規則檔案最先被解析。比如,建立一個名為 <code>/etc/udev/rules.d/10-myrule.rules</code>的檔案,并把你的規則寫入該檔案,這樣 udev 就會在解析系統預設的規則檔案之前解析到你的檔案。

RHEL5.3 的 udev 規則檔案比 RHEL4.8 裡的更完善。受篇幅的限制,同時也為了不讓大家混淆,本文将不對 RHEL4.8 裡的規則檔案進行詳解,下面關于規則檔案的配置和執行個體都是在 RHEL5.3 上進行的。如果大家需要配置 RHEL4 的 udev 規則檔案,可以先參照下面 RHEL5.3 的配置過程,然後查詢 RHEL4 裡的使用者手冊 (man udev) 後進行配置。

在規則檔案裡,除了以“#”開頭的行(注釋),所有的非空行都被視為一條規則,但是一條規則不能擴充到多行。規則都是由多個 鍵值對(key-value pairs)組成,并由逗号隔開,鍵值對可以分為 條件比對鍵值對( 以下簡稱“比對鍵 ”) 和 指派鍵值對( 以下簡稱“指派鍵 ”),一條規則可以有多條比對鍵和多條指派鍵。比對鍵是比對一個裝置屬性的所有條件,當一個裝置的屬性比對了該規則裡所有的比對鍵,就認為這條規則生效,然後按照指派鍵的内容,執行該規則的指派。下面是一個簡單的規則:

KERNEL 是比對鍵,NAME 和 MODE 是指派鍵。這條規則的意思是:如果有一個裝置的核心裝置名稱為 sda,則該條件生效,執行後面的指派:在 <code>/dev</code>下産生一個名為<code>my_root_disk</code>的裝置檔案,并把裝置檔案的權限設為 0660。

通過這條簡單的規則,大家應該對 udev 規則有直覺的了解。但可能會産生疑惑,為什麼 KERNEL 是比對鍵,而 NAME 和 MODE 是指派鍵呢?這由中間的操作符 (operator) 決定。

僅當操作符是“==”或者“!=”時,其為比對鍵;若為其他操作符時,都是指派鍵。

RHEL5.3 裡 udev 規則的所有操作符:

“==”:比較鍵、值,若等于,則該條件滿足;

“!=”: 比較鍵、值,若不等于,則該條件滿足;

“=”: 對一個鍵指派;

“+=”:為一個表示多個條目的鍵指派。

“:=”:對一個鍵指派,并拒絕之後所有對該鍵的改動。目的是防止後面的規則檔案對該鍵指派。

RHEL5.3 裡 udev 規則的比對鍵

ACTION: 事件 (uevent) 的行為,例如:add( 添加裝置 )、remove( 删除裝置 )。

KERNEL: 核心裝置名稱,例如:sda, cdrom。

DEVPATH:裝置的 devpath 路徑。

SUBSYSTEM: 裝置的子系統名稱,例如:sda 的子系統為 block。

BUS: 裝置在 devpath 裡的總線名稱,例如:usb。

DRIVER: 裝置在 devpath 裡的裝置驅動名稱,例如:ide-cdrom。

ID: 裝置在 devpath 裡的識别号。

SYSFS{filename}: 裝置的 devpath 路徑下,裝置的屬性檔案“filename”裡的内容。

例如:SYSFS{model}==“ST936701SS”表示:如果裝置的型号為 ST936701SS,則該裝置比對該 比對鍵。

在一條規則中,可以設定最多五條 SYSFS 的 比對鍵。

ENV{key}:  環境變量。在一條規則中,可以設定最多五條環境變量的 比對鍵。

PROGRAM:調用外部指令。

RESULT:   外部指令 PROGRAM 的傳回結果。例如:

調用外部指令 <code>/lib/udev/scsi_id</code>查詢裝置的 SCSI ID,如果傳回結果為 35000c50000a7ef67,則該裝置比對該 比對鍵。

RHEL5.3 裡 udev 的重要指派鍵

NAME:在 <code>/dev</code>下産生的裝置檔案名。隻有第一次對某個裝置的 NAME 的指派行為生效,之後比對的規則再對該裝置的 NAME 指派行為将被忽略。如果沒有任何規則對裝置的 NAME 指派,udev 将使用核心裝置名稱來産生裝置檔案。

SYMLINK:為 <code>/dev/</code>下的裝置檔案産生符号連結。由于 udev 隻能為某個裝置産生一個裝置檔案,是以為了不覆寫系統預設的 udev 規則所産生的檔案,推薦使用符号連結。

OWNER, GROUP, MODE:為裝置設定權限。

ENV{key}:導入一個環境變量。

RHEL5.3 裡 udev 的值和可調用的替換操作符

在鍵值對中的鍵和操作符都介紹完了,最後是值 (value)。Linux 使用者可以随意地定制 udev 規則檔案的值。例如:<code>my_root_disk, my_printer</code>。同時也可以引用下面的替換操作符:

$kernel, %k:裝置的核心裝置名稱,例如:sda、cdrom。

$number, %n:裝置的核心号碼,例如:sda3 的核心号碼是 3。

$devpath, %p:裝置的 devpath路徑。

$id, %b:裝置在 devpath裡的 ID 号。

$sysfs{file}, %s{file}:裝置的 sysfs裡 file 的内容。其實就是裝置的屬性值。

例如:$sysfs{size} 表示該裝置 ( 磁盤 ) 的大小。

$env{key}, %E{key}:一個環境變量的值。

$major, %M:裝置的 major 号。

$minor %m:裝置的 minor 号。

$result, %c:PROGRAM 傳回的結果。

$parent, %P:父裝置的裝置檔案名。

$root, %r:udev_root的值,預設是 <code>/dev/</code>。

$tempnode, %N:臨時裝置名。

%%:符号 % 本身。

$$:符号 $ 本身。

該規則的執行:如果有一個核心裝置名稱以 sd 開頭,且 SCSI ID 為 <code>35000c50000a7ef67</code>,則為裝置檔案産生一個符号連結“sda_35000c50000a7ef67”.

如何查找裝置的資訊 ( 屬性 ) 來制定 udev 規則:

當我們為指定的裝置設定規則時,首先需要知道該裝置的屬性,比如裝置的序列号、磁盤大小、廠商 ID、裝置路徑等等。通常我們可以通過以下的方法獲得:

查詢sysfs檔案系統:

前面介紹過,sysfs 裡包含了很多裝置和驅動的資訊。

例如:裝置 sda 的 SYSFS{size} 可以通過 <code>cat /sys/block/sda/size</code>得到;SYSFS{model} 資訊可以通過 <code>cat /sys/block/sda/device/model</code>得到。

udevinfo指令:

udevinfo 可以查詢 udev 資料庫裡的裝置資訊。例如:用 udevinfo 查詢裝置 sda 的 model 和 size 資訊:

其他外部指令:

udev 的簡單規則:

該規則表示:如果存在裝置的子系統為 net,并且位址 (MAC address) 為“AA:BB:CC:DD:EE:FF”,為該裝置産生一個名為 public_NIC 的裝置檔案。

該規則表示:如果存在裝置的子系統為 block,并且大小為 71096640(block),則為該裝置的裝置檔案名産生一個名為 my_disk 的符号連結。

該規則表示:如果存在裝置的核心裝置名稱是以 sd 開頭 ( 磁盤裝置 ),以數字結尾 ( 磁盤分區 ),并且通過外部指令查詢該裝置的 SCSI_ID 号為“35000c50000a7ef67”,則産生一個以 root_disk 開頭,核心号碼結尾的裝置檔案,并替換原來的裝置檔案(如果存在的話)。例如:産生裝置名 <code>/dev/root_disk2</code>,替換原來的裝置名 <code>/dev/sda2</code>。

運用這條規則,可以在 <code>/etc/fstab</code>裡保持系統分區名稱的一緻性,而不會受驅動加載順序或者磁盤标簽被破壞的影響,導緻作業系統啟動時找不到系統分區。

其他常用的 udev 指令:

udevtest:

<code>udevtest</code>會針對一個裝置,在不需要 uevent 觸發的情況下模拟一次 <code>udev</code>的運作,并輸出查詢規則檔案的過程、所執行的行為、規則檔案的執行結果。通常使用<code>udevtest</code>來調試規則檔案。以下是一個針對裝置 sda 的 <code>udevtest</code>例子。由于 <code>udevtest</code>是掃描所有的規則檔案 ( 包括系統自帶的規則檔案 ),是以會産生冗長的輸出。為了讓讀者清楚地了解 <code>udevtest</code>,本例隻在規則目錄裡保留一條規則:

可以看出,<code>udevtest</code>對 sda 執行了外部指令 <code>scsi_id</code>, 得到的 stdout 和規則檔案裡的 RESULT 比對,是以該規則比對。然後 ( 模拟 ) 産生裝置檔案<code>/dev/root_disk</code>和符号連結 <code>/dev/symlink_root_disk</code>,并為其設定權限。

start_udev:

<code>start</code>_<code>dev</code>指令重新開機 <code>udev</code>守護程序,并對所有的裝置重新查詢規則目錄下所有的規則檔案,然後執行所比對的規則裡的行為。通常使用該指令讓新的規則檔案立即生效:

<code>start</code><code>_udev</code><code>一般</code>沒有标準輸出,所有的 udev 相關資訊都按照配置檔案 (<code>udev.conf)</code>的參數設定,由 syslog記錄。

udev 是高效的裝置管理工具,其最大的優勢是動态管理裝置和自定義裝置的命名規則,是以替代 devfs 成為 Linux 預設的裝置管理工具。通過閱讀本文,Linux 使用者能夠了解到 udev 的工作原理和流程,靈活地運用 udev 規則檔案,進而友善地管理 Linux 裝置檔案。

繼續閱讀