自2.6 核心開始,就可以使用udev 協助管理系統中各設備名稱。例如,磁盤設備排序、網卡設備排序等。udev能動態地在/dev 目錄里產生自定義的、標識性強的設備文件或設備鏈接。本文即以紅旗Asianux 3.0 平台,給新加載的U盤設備自定義一個鏈接為例進行簡要說明。
一、關於udev
2.4 內核使用devfs(設備文件系統)在設備初始化時創建設備文件,設備驅動程序可以指定設備號、所有者、用戶空間等信息,devfs 運行在內核環境中,並有不少缺點:可能出現主/輔設備號不夠,命名不靈活,不能指定設備名稱等問題。而自2.6 內核開始,引入了sysfs 文件系統。sysfs 把連接在系統上的設備和總線組織成一個分級的文件,並提供給用戶空間存取使用。udev 運行在用戶模式,而非內核中。udev 的初始化腳本在系統啟動時創建設備節點,並且當插入新設備——加入驅動模塊——在sysfs上注冊新的數據后,udev會創新新的設備節點。
udev 是一個工作在用戶空間的工具,它能根據系統中硬件設備的狀態動態的更新設備文件,包括設備文件的創建,刪除,權限等。這些文件通常都定義在/dev 目錄下,但也可以在配置文件中指定。udev 必須內核中的sysfs和tmpfs支持,sysfs 為udev 提供設備入口和uevent 通道,tmpfs 為udev 設備文件提供存放空間。
注意,udev 是通過對內核產生的設備文件修改,或增加別名的方式來達到自定義設備文件的目的。但是,udev 是用戶模式程序,其不會更改內核行為。也就是說,內核仍然會創建sda,sdb等設備文件,而udev可根據設備的唯一信息來區分不同的設備,並產生新的設備文件(或鏈接)。而在用戶的應用中,只要使用新產生的設備文件即可。
udev 的工作流程圖:
二、參考文檔
正如前面提到的,udev 依賴於2.6 核心上的sysfs 文件系統。因此,只有在紅旗DC 5.0以上版本中才能使用。隨着udev 的不斷發展,不同版本的udev 規則也有不少差別,編寫規則時必須注意。
因為不同版本的udev 規則定義方式不同,在編寫時需特別留意。我主要是參考以下資料來了解相關規則的:
官網:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
man udev
Writing udev rules
使用 udev 高效、動態地管理 Linux 設備文件
udev 服務的主要配置文件在/etc/udev/udev.conf,但通常不同修改。
規 則文件是存放在/etc/udev/rules.d 目錄下面,所有的規則文件都必須以.rules 作為后綴名。系統安裝完畢后,該目錄中就會有一些默認的規則文件,主要用於產生一些容易標識的設備符號鏈接。同時,一些應用程序,為了在/dev 下產生方便使用的標識符,也會放入一些規則,例如:40-multipath.rules、99-fuse.rules 等。
udev 是按照規則文件的字母順序來解析各規則文件的,並根據匹配上的規則創建對應的設備文件或鏈接。所以,解析的順序很重要,為了使自定義的規則生效,可以把規則寫入較前的規則文件中,例如20-names.rules。
三、規則說明
以下規則說明來自使用 udev 高效、動態地管理 Linux 設備文件一文,與Asianux 3.0 中man udev 中的說明一致,我就不一一翻譯了。
1、udev 規則的所有操作符
“!=”: 比較鍵、值,若不等於,則該條件滿足;
“=”: 對一個鍵賦值;
“+=”:為一個表示多個條目的鍵賦值。
“:=”:對一個鍵賦值,並拒絕之后所有對該鍵的改動。目的是防止后面的規則文件對該鍵賦值。
2、udev 規則的匹配鍵
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 的返回結果。
3、udev 的重要賦值鍵
SYMLINK:為 /dev/下的設備文件產生符號鏈接。由於 udev 只能為某個設備產生一個設備文件,所以為了不覆蓋系統默認的 udev 規則所產生的文件,推薦使用符號鏈接。
OWNER, GROUP, MODE:為設備設定權限。
ENV{key}:導入一個環境變量。
4、udev 的值和可調用的替換操作符
$kernel, %k:設備的內核設備名稱,例如:sda、cdrom。
$number, %n:設備的內核號碼,例如:sda3 的內核號碼是 3。
$devpath, %p:設備的 devpath路徑。
$id, %b:設備在 devpath里的 ID 號。
$sysfs{file}, %s{file}:設備的 sysfs里 file 的內容。其實就是設備的屬性值。
$env{key}, %E{key}:一個環境變量的值。
$major, %M:設備的 major 號。
$minor %m:設備的 minor 號。
$result, %c:PROGRAM 返回的結果。
$parent, %P:父設備的設備文件名。
$root, %r:udev_root的值,默認是 /dev/。
$tempnode, %N:臨時設備名。
%%:符號 % 本身。
$$:符號 $ 本身。
四、實例說明
下面以在Asianux 3.0 平台上為一塊U盤創建一個新的設備文件為例說明。
1、查詢設備屬性
規則中,需要給出匹配的設備屬性,例如設備的序列號、廠商ID、磁盤大小等,用於區分不同的設備。這有三種方法:
a、由udevinfo 命令得到
looking at device '/block/sda':
KERNEL=="sda"
SUBSYSTEM=="block"
SYSFS{stat}==" 42 25 536 1882 0 0 0 0 0 1882 1882"
SYSFS{size}=="128000"
......
looking at parent device '/devices/pci0000:00/0000:00:03.0/usb1':
ID=="usb1"
BUS=="usb"
DRIVER=="usb"
SYSFS{configuration}==""
SYSFS{serial}=="0000:00:03.0"
SYSFS{product}=="OHCI Host Controller"
......
b、由sysfs 文件系統獲得
128000
c、通過外部命令
通過scsi_id、path_id、usb_id 等命令獲取:
ID_VENDOR=0c76
ID_MODEL=0005
ID_REVISION=0100
ID_SERIAL=0c76_0005
ID_TYPE=disk
ID_BUS=usb
※ 注意,可供選擇的設備屬性有不少,但必須選擇固定不變的屬性,例如廠商ID、大小等。換句話說,不能選擇隨重啟或插入而改變的信息,例如pci_id等,否則,同樣的規則下次將不能匹配成功。
2、創建規則
SUBSYSTEM=="block",BUS=="usb",SYSFS{product}=="OHCI Host Controller",NAME="usbhd%n"
※ 注意,最后的%n,若沒有這變量符,將不會創建該設備下分區的映射;另外,雙等號“==”與等號“=”是不能混淆使用。
3、測試規則
main: looking at device '/block/sda' from subsystem 'block'
udev_rules_get_name: rule applied, 'sda' becomes 'usbhd'
run_program: '/lib/udev/usb_id -x'
run_program: '/lib/udev/usb_id' (stdout) 'ID_VENDOR=0c76'
run_program: '/lib/udev/usb_id' (stdout) 'ID_MODEL=0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_REVISION=0100'
run_program: '/lib/udev/usb_id' (stdout) 'ID_SERIAL=0c76_0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_TYPE=disk'
run_program: '/lib/udev/usb_id' (stdout) 'ID_BUS=usb'
run_program: '/lib/udev/usb_id' returned with status 0
udev_rules_get_name: 1 untrusted character(s) replaced
udev_rules_get_name: add symlink 'disk/by-id/usb-0c76_0_'
run_program: '/lib/udev/path_id /block/sda'
run_program: '/lib/udev/path_id' (stdout) 'ID_PATH=pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
run_program: '/lib/udev/path_id' returned with status 0
udev_rules_get_name: add symlink 'disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
udev_device_event: device '/block/sda' already in database, validate currently present symlinks
udev_node_add: creating device node '/dev/usbhd', major = '8', minor = '0', mode = '0640', uid = '0', gid = '6'
udev_node_add: creating symlink '/dev/disk/by-id/usb-0c76_0_' to '../../usbhd'
udev_node_add: creating symlink '/dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0' to '../../usbhd'
main: run: 'socket:/org/kernel/udev/monitor'
main: run: '/lib/udev/udev_run_devd'
main: run: 'socket:/org/freedesktop/hal/udev_event'
main: run: '/sbin/pam_console_apply /dev/usbhd /dev/disk/by-id/usb-0c76_0_ /dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
從紅色標記的地方可見,原來的sda設備被改為自定義的usbhd設備符(剩余信息是其他規則匹配的情況)。
4、啟動udev
啟動 udev: [確定]
5、測試
從/dev 目錄下,可查看到新創建的設備符:
brw-r----- 1 root disk 8, 0 08-10 17:49 /dev/usbhd
brw-r----- 1 root disk 8, 1 08-10 17:50 /dev/usbhd1
掛載測試:
# mount|grep usbhd1
/dev/usbhd1 on /mnt/disk type vfat (rw)
# df |grep usbhd1
/dev/usbhd1 62952 52172 10780 83% /mnt/disk
可見,我們新創建的設備符已經生效,並可以使用。
※ 注意,由於我們使用的是NAME方式,即新的usbhd會直接替換為sda,因此,在fdisk -l 命令下,可能會看不到原來的sda設備。這時,可通過dmesg命令獲取內核分配的設備符:
usbcore: registered new driver usb-storage
USB Mass Storage support registered.
Vendor: Model: Rev:
Type: Direct-Access ANSI SCSI revision: 02
SCSI device sda: 128000 512-byte hdwr sectors (66 MB)
sda: Write Protect is off
sda: Mode Sense: 0b 00 00 08
sda: assuming drive cache: write through
SCSI device sda: 128000 512-byte hdwr sectors (66 MB)
sda: Write Protect is off
sda: Mode Sense: 0b 00 00 08
sda: assuming drive cache: write through
sda: sda1
sd 0:0:0:0: Attached scsi removable disk sda
usb-storage: device scan complete
sd 0:0:0:0: Attached scsi generic sg0 type 0
另外,為避免與內核產生的設備文件沖突,新創建的設備文件應使用自定義的易標識名稱,而不要使用sdb/sdc 等,否則,可能會給其他應用(如ASM等)帶來麻煩。
6、使用其他屬性創建規則
上面,我們是直接使用udevinfo 得到的信息來編寫規則,下面借助外部程序來匹配設備屬性。
首先,編寫一個自定義規則:
#!/bin/bash
/lib/udev/usb_id -x $1|grep ID_VENDOR|awk -F '=' {'print $2'}
給予可執行權限:
測試其輸入的結果:
0c76
編寫規則:
KERNEL=="sd*",PROGRAM="/lib/udev/select_usb_disk.sh %p",RESULT=="0c76",SYMLINK+="usbhd%n"
測試規則:
main: looking at device '/block/sda' from subsystem 'block'
run_program: '/lib/udev/select_usb_disk.sh /block/sda'
run_program: '/lib/udev/select_usb_disk.sh' (stdout) '0c76'
run_program: '/lib/udev/select_usb_disk.sh' returned with status 0
udev_rules_get_name: add symlink 'usbhd'
run_program: '/lib/udev/usb_id -x'
run_program: '/lib/udev/usb_id' (stdout) 'ID_VENDOR=0c76'
run_program: '/lib/udev/usb_id' (stdout) 'ID_MODEL=0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_REVISION=0100'
run_program: '/lib/udev/usb_id' (stdout) 'ID_SERIAL=0c76_0^'
run_program: '/lib/udev/usb_id' (stdout) 'ID_TYPE=disk'
run_program: '/lib/udev/usb_id' (stdout) 'ID_BUS=usb'
run_program: '/lib/udev/usb_id' returned with status 0
udev_rules_get_name: 1 untrusted character(s) replaced
udev_rules_get_name: add symlink 'disk/by-id/usb-0c76_0_'
run_program: '/lib/udev/path_id /block/sda'
run_program: '/lib/udev/path_id' (stdout) 'ID_PATH=pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
run_program: '/lib/udev/path_id' returned with status 0
udev_rules_get_name: add symlink 'disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
udev_rules_get_name: no node name set, will use kernel name 'sda'
udev_device_event: device '/block/sda' already in database, validate currently present symlinks
udev_node_add: creating device node '/dev/sda', major = '8', minor = '0', mode = '0640', uid = '0', gid = '6'
udev_node_add: creating symlink '/dev/usbhd' to 'sda'
udev_node_add: creating symlink '/dev/disk/by-id/usb-0c76_0_' to '../../sda'
udev_node_add: creating symlink '/dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0' to '../../sda'
main: run: 'socket:/org/kernel/udev/monitor'
main: run: '/lib/udev/udev_run_devd'
main: run: 'socket:/org/freedesktop/hal/udev_event'
main: run:'/sbin/pam_console_apply /dev/sda /dev/usbhd/dev/disk/by-id/usb-0c76_0_ /dev/disk/by-path/pci-0000:00:03.2-usb-0:1:1.0-scsi-0:0:0:0'
刪除舊設備符:
※ 注意,使用start_udev並不會刪除原來由NAME在/dev 目錄下創建的設備文件,並且可能與新規則產生沖突,導致start_udev服務啟動超時或出錯,所以,在測試規則時,應預先刪除舊規則創建的設備符。拔 去對應的物理設備,udev 會自動刪除規則創建的設備文件或鏈接,但期間若修改了規則,導致規則所對應的設備文件或鏈接不相符,則這些舊的設備文件或鏈接可能會被遺留下來。
啟動udev,創建新設備符:
# ll /dev/usbhd*
lrwxrwxrwx 1 root root 3 08-10 18:18 /dev/usbhd -> sda
lrwxrwxrwx 1 root root 4 08-10 18:18 /dev/usbhd1 -> sda1
掛載測試:
# mount |grep disk
/dev/sda1on /mnt/disk type vfat (rw)
可見,新設備符可用。
※ 請特別留意,規則中使用的是SYMLINK,所以,創建的僅是一個指向內核產生的設備的鏈接,而非取代原來的設備符。所以,在掛載成功后,mount命令顯示的也是實際的內核設備。這很容易引起誤會,請特別小心。
至此,我要講解的udev 自定義規則方式已完畢。除了用於存儲設備外,其他如Network interface等也能用udev 來處理。例如最常見的就是Asianux 3.0 下網絡設備名自動變更的問題,可通過創建自定義規則:
綁定固定的MAC地址,即可輕松解決。這比修改/etc/sysconfig/network-scripts/ifcfg-ethx 要可靠和穩定得多。
但是,udev 也不是萬能的。至少,在我的實踐中,可能因為udev 版本太低等原因,DC 5.0 平台下(2.6.9-89 核心),udev 為039,配置RDAC后,udev 的規則無法匹配內核產生的sdb等設備,至今沒找到解決辦法。
不過,udev 畢竟提供了一條很好的處理設備名沖突、變更等問題引起故障的解決辦法,應熟悉和充分利用。
五、附錄
使用 udev 高效、動態地管理 Linux 設備文件的pdf 版本:

http://www.linuxfly.org/attachment/1281437205_4694a742.pdf
http://www.cnblogs.com/sopost/archive/2013/01/09/2853200.html
如果你使用Linux比較長時間了,那你就知道,在對待設備文件這塊,Linux改變了幾次策略。在Linux早期,設備文件僅僅是是一些帶有適當的屬性集的普通文件,它由mknod命令創建,文件存放在/dev目錄下。后來,采用了devfs, 一個基於內核的動態設備文件系統,他首次出現在2.3.46內核中。Mandrake,Gentoo等Linux分發版本采用了這種方式。devfs創建的設備文件是動態的。但是devfs有一些嚴重的限制,從2.6.13版本后移走了。目前取代他的便是文本要提到的udev--一個用戶空間程序。
目前很多的Linux分發版本采納了udev的方式,因為它在Linux設備訪問,特別是那些對設備有極端需求的站點(比如需要控制上千個硬盤)和熱插拔設備(比如USB攝像頭和MP3播放器)上解決了幾個問題。下面我我們來看看如何管理udev設備。
實際上,對於那些為磁盤,終端設備等准備的標准配置文件而言,你不需要修改什么。但是,你需要了解udev配置來使用新的或者外來設備,如果不修改配置,這些設備可能無法訪問,或者說Linux可能會采用不恰當的名字,屬組或權限來創建這些設備文件。你可能也想知道如何修改RS-232串口,音頻設備等文件的屬組或者權限。這點在實際的Linux實施中是會遇到的。
為什么使用udev
在此之前的設備文件管理方法(靜態文件和devfs)有幾個缺點:
*不確定的設備映射。特別是那些動態設備,比如USB設備,設備文件到實際設備的映射並不可靠和確定。舉一個例子:如果你有兩個USB打印機。一個可能稱為/dev/usb/lp0,另外一個便是/dev/usb/lp1。但是到底哪個是哪個並不清楚,lp0,lp1和實際的設備沒有一一對應的關系,因為他可能因為發現設備的順序,打印機本身關閉等原因而導致這種映射並不確定。理想的方式應該是:兩個打印機應該采用基於他們的序列號或者其他標識信息的唯一設備文件來映射。但是靜態文件和devfs都無法做到這點。
*沒有足夠的主/輔設備號。我們知道,每一個設備文件是有兩個8位的數字:一個是主設備號,另外一個是輔設備號來分配的。這兩個8位的數字加上設備類型(塊設備或者字符設備)來唯一標識一個設備。不幸的是,關聯這些身邊的的數字並不足夠。
*/dev目錄下文件太多。一個系統采用靜態設備文件關聯的方式,那么這個目錄下的文件必然是足夠多。而同時你又不知道在你的系統上到底有那些設備文件是激活的。
*命名不夠靈活。盡管devfs解決了以前的一些問題,但是它自身又帶來了一些問題。其中一個就是命名不夠靈活;你別想非常簡單的就能修改設備文件的名字。缺省的devfs命令機制本身也很奇怪,他需要修改大量的配置文件和程序。
*內核內存使用,devfs特有的另外一個問題是,作為內核驅動模塊,devfs需要消耗大量的內存,特別當系統上有大量的設備時(比如上面我們提到的系統一個上有好幾千磁盤時)
udev的目標是想解決上面提到的這些問題,他通采用用戶空間(user-space)工具來管理/dev/目錄樹,他和文件系統分開。知道如何改變缺省配置能讓你之大如何定制自己的系統,比如創建設備字符連接,改變設備文件屬組,權限等。
udev配置文件
主要的udev配置文件是/etc/udev/udev.conf。這個文件通常很短,他可能只是包含幾行#開頭的注釋,然后有幾行選項:
udev_root=“/dev/”
udev_rules=“/etc/udev/rules.d/”
udev_log=“err“
上面的第二行非常重要,因為他表示udev規則存儲的目錄,這個目錄存儲的是以.rules結束的文件。每一個文件處理一系列規則來幫助udev分配名字給設備文件以保證能被內核識別。
你的/etc/udev/rules.d下面可能有好幾個udev規則文件,這些文件一部分是udev包安裝的,另外一部分則是可能是別的硬件或者軟件包生成的。比如在Fedora Core 5系統上,sane-backends包就會安裝60-libsane.rules文件,另外initscripts包會安裝60-net.rules文件。這些規則文件的文件名通常是兩個數字開頭,它表示系統應用該規則的順序。
規則文件里的規則有一系列的鍵/值對組成,鍵/值對之間用逗號(,)分割。每一個鍵或者是用戶匹配鍵,或者是一個賦值鍵。匹配鍵確定規則是否被應用,而賦值鍵表示分配某值給該鍵。這些值將影響udev創建的設備文件。賦值鍵可以處理一個多值列表。匹配鍵和賦值鍵操作符解釋見下表:
udev 鍵/值對操作符
操作符 匹配或賦值 解釋
----------------------------------------
== 匹配 相等比較
!= 匹配 不等比較
= 賦值 分配一個特定的值給該鍵,他可以覆蓋之前的賦值。
+= 賦值 追加特定的值給已經存在的鍵
:= 賦值 分配一個特定的值給該鍵,后面的規則不可能覆蓋它。
這有點類似我們常見的編程語言,比如C語言。只是這里的鍵一次可以處理多個值。有一些鍵在udev規則文件里經常出現,這些鍵的值可以使用通配符(*,?,甚至范圍,比如[0-9]),這些常用鍵列舉如下:
常用udev鍵
鍵 含義
ACTION 一個時間活動的名字,比如add,當設備增加的時候
KERNEL 在內核里看到的設備名字,比如sd*表示任意SCSI磁盤設備
DEVPATH 內核設備錄進,比如/devices
error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev_name(dev));
if (error)
goto Error;
.......
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev);
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);
.......
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
goto done;
}
上面的代碼段為我們常見的添加注冊設備時的會調用到的接口,device_add()函數,刪除了一些無關代碼,可以看出,是先調用了kobject_add()創建添加該內核對象,然后調用kobject_uevent()來通知系統uevent的變化,這里的action是KOBJ_ADD,相對應還有
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};
在kobject_uevent()里面采用的就是Linux中比較經典的內核空間和用戶空間的一種通信機制netlink socket,這個不是udev的重點,我也不做過多的解釋,總之相信它能讓內核空間和用戶空間進行通信就行了。在udev也會有相應的socket來接受底層的消息。如下為參照udev源碼寫的一個簡單的uevent消息偵聽程序:
#define UEVENT_BUFFER_SIZE 2048
static int init_hotplug_sock(void)
{
struct sockaddr_nl snl;
const int buffersize = 16 * 1024 * 1024;
int retval;
memset(&snl, 0x00, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;
int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (hotplug_sock == -1) {
printf("error getting socket: %s", strerror(errno));
return -1;
}
setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
if (retval < 0) {
printf("bind failed: %s", strerror(errno));
close(hotplug_sock);
hotplug_sock = -1;
return -1;
}
return hotplug_sock;
}
int main(int argc, char* argv[])
{
int hotplug_sock = init_hotplug_sock();
while(1)
{
//printf("sunqidong debug\n");
char buf[UEVENT_BUFFER_SIZE*2] = {0};
recv(hotplug_sock, &buf, sizeof(buf), 0);
printf("%s\n", buf);
}
return 0;
}
這也是一個后台服務程序,循環的執行接受底層的消息過來,當發生U盤的插拔時,會產生如下的log:
[root@localhost test]# ./hotplug
add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1
add@/class/usb_endpoint/usbdev1.5_ep00
add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0
add@/class/scsi_host/host6
add@/class/usb_endpoint/usbdev1.5_ep81
add@/class/usb_endpoint/usbdev1.5_ep02
add@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0
add@/class/scsi_disk/6:0:0:0
add@/block/sdb
add@/block/sdb/sdb1
add@/block/sdb/sdb2
add@/block/sdb/sdb5
add@/block/sdb/sdb6
add@/block/sdb/sdb7
add@/block/sdb/sdb8
add@/class/scsi_device/6:0:0:0
add@/class/scsi_generic/sg2
add@/class/bsg/6:0:0:0
remove@/class/usb_endpoint/usbdev1.5_ep81
remove@/class/usb_endpoint/usbdev1.5_ep02
remove@/class/bsg/6:0:0:0
remove@/class/scsi_generic/sg2
remove@/class/scsi_device/6:0:0:0
remove@/class/scsi_disk/6:0:0:0
remove@/block/sdb/sdb8
remove@/block/sdb/sdb7
remove@/block/sdb/sdb6
remove@/block/sdb/sdb5
remove@/block/sdb/sdb2
remove@/block/sdb/sdb1
remove@/block/sdb
remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0
remove@/class/scsi_host/host6
remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0
remove@/class/usb_endpoint/usbdev1.5_ep00
remove@/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1
三.udev的規則文件 規則文件是udev中最重要的部分,默認是存放在/etc/udev/rules.d/下。所有的規則文件都必須以".rules"為后綴名。下面是一個簡單的規則文件例子說明
KERNEL=="sdb8" , NAME="mydisk",MODE="0660"
KERNEL是匹配鍵,NAME和MODE是賦值鍵。"=="這是判斷語句,"="是賦值語句,這條規則的意思是,如果有一個設備的內核設備名稱為sdb8(我移動硬盤內的一個分區),則該條件生效,執行后面的賦值:在/dev/下產生名為mydisk的設備文件,並把該設備文件的權限設為0660.
通過這條簡單的規則,應該就可以對規則文件有了個基本的了解。每個規則文件被分成一個或多個匹配和賦值部分。匹配部分用匹配專用的關鍵字來表示,相應的賦值部分用賦值專用的字來表示。
1.常見的匹配關鍵字: ACTION(用於匹配行為add/remove),KERNEL(內核中定義的設備名),BUS(用於匹配總線類型),SYSFS(用於匹配從sysfs得到的信息,比如lable,vendor,USB序列號等),SUBSYSTEM(匹配子系統名)等
2.常見的賦值關鍵字:
NAME(創建的設備文件名),SYMLINK(符號創建鏈接名),OWNER(設置設備的所有者),GROUP(設置設備的組),IMPORT(調用外部程序),MODE(權限位)
四.xx項目上自動掛載usb存儲設備的應用
在xx項目上,我們需要自動掛載usb存儲設備,並且要支持常見的幾種文件系統,fat32,ntfs,exfat等,其中fat32等fat系列,Linux下早就有支持,ntfs和exfat目前的內核自身還沒支持,我們有關於這兩個文件系統的內核模塊文件tntfs,ko和texfat.ko,加載進去過后就能讓我們的內核識別這兩種文件系統,實現手動加載這兩種格式的存儲設備。但如果要支持自動加載還有問題,需要去修改相應的規則文件。
在加載的時候,不同的格式的文件系統,加載的參數是不一樣的,如exfat為
mount -t texfat /dev/sda /mnt/udisk
而ntfs為
mount -t tntfs /dev/sda /mnt/udisk
並且針對不同的格式,還有些其他掛載選項參數不一樣,所以對不同的格式需要區別對待。
在規則文件里面是通過blkid -o udev命令來獲取文件系統的信息的,判斷出該盤是哪種格式,再去執行不同的掛載命令。
如下是blkid -o udev讀出來的文件系統格式信息
ID_FS_UUID=2EE054B8E054884B
ID_FS_UUID_ENC=2EE054B8E054884B
ID_FS_LABEL=disk3
ID_FS_LABEL_ENC=disk3
ID_FS_TYPE=ntfs
ID_FS_LABEL=DISK4
ID_FS_LABEL_ENC=DISK4
ID_FS_UUID=B8CF-FF22
ID_FS_UUID_ENC=B8CF-FF22
ID_FS_TYPE=vfat
ID_FS_UUID=3606-1B2C
ID_FS_UUID_ENC=3606-1B2C
ID_FS_TYPE=exfat
ID_FS_LABEL=Disk5
ID_FS_LABEL_ENC=Disk5
可以看到上面的幾個賦值項,在規則文件里面就會去讀這些值。做相應的判斷,實現不同文件系統的區別對待掛載
如下為規則文件的一部分
KERNEL!="sd[a-z][0-9]", GOTO="media_by_label_auto_mount_end"
# Import FS infos
IMPORT{program}="/sbin/blkid -o udev -p %N"
ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"
ENV{ID_FS_LABEL}=="", ENV{dir_name}="usb-%k"
#vfat,fat
ACTION=="add",ENV{ID_FS_TYPE}=="vfat|fat",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",ENV{mount_options}="$env{mount_options},gid=100,umask=000",RUN+="/bin/mount -o $env{mount_options},iocharset=utf8 /dev/%k /mnt/udisk/%E{dir_name}"
#ntfs
ACTION=="add",ENV{ID_FS_TYPE}=="ntfs",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",ENV{mount_options}="$env{mount_options},gid=100,umask=000",RUN+="/bin/mount -t tntfs -o $env{mount_options},iostreaming /dev/%k /mnt/udisk/%E{dir_name}"
#exfat
ACTION=="add",ENV{ID_FS_TYPE}=="exfat",RUN+="/bin/mkdir -p /mnt/udisk/%E{dir_name}",RUN+="/bin/mount -t texfat -o rw /dev/%k /mnt/udisk/%E{dir_name}"
關於blkid,在我們目前的文件系統里面,blkid是不支持exfat格式的,通過命令查看磁盤的信息,根本找不到exfat格式的磁盤。所以之前在做自動掛載的時候沒法實現掛exfat,后來在網上找了個util-linux-ng2.18源碼包,里面包含了blkid的源碼。修改編譯,編譯出一個新的blkid文件,使其可以在我們的系統上運行,能夠識別出exfat文件。