2017-05-20

udev

來源 :
1. udev-強大的device node管理系統
2. Howto--udev rules

一、devfs

linux下有專門的檔系統用來對設備進行管理,devfs和sysfs就是其中兩種。

在2.6內核以前一直使用的是devfs,devfs掛載於/dev目錄下,提供了一種類似於檔案的方法來管理位於/dev目錄下的所有設備,我們知道/dev目錄下的每一個檔案都對應的是一個設備,至於當前該設備存在與否先且不論,而且這些特殊檔是位於根檔系統上的,在製作檔案系統的時候我們就已經建立了這些設備檔,因此通過操作這些特殊檔,可以實現與內核進行交互。但是devfs檔案系統有一些缺點,例如:不確定的設備映射,有時一個設備映射的設備檔可能不同,例如我的U盤可能對應sda有可能對應sdb;沒有足夠的主/輔設備號,當設備過多的時候,顯然這會成為一個問題;/dev目錄下檔太多而且不能表示當前系統上的實際設備;命名不夠靈活,不能任意指定等等。

udev的出現, 解決了在devfs上碰到的3個問題
1.udev能夠根據規則建立device node(這可以解決probing時,device node可能會依probing的順序不同而改變)
2.udev動態建立device node,不會像以前在/dev資料夾下擺一堆多而無用的device node
3.提供user更方便的API存取目前device的資訊,在kernel 2.6以上已提供sysfs這個device管理機制

二、sysfs

正因為上述這些問題的存在,在linux2.6內核以後,引入了一個新的檔案系統sysfs,它掛載於/sys目錄下,跟devfs一樣它也是一個虛擬檔案系統,也是用來對系統的設備進行管理的,它把實際連接到系統上的設備和匯流排組織成一個分階層的檔案目錄,用戶空間的程式同樣可以利用這些資訊以實現和內核的交互,該檔案系統是當前系統上實際設備樹的一個直觀反應,它是通過kobject子系統來建立這個資訊的,當一個kobject被創建的時候,對應的檔案和目錄也就被創建了,位於/sys下的相關目錄下,既然每個設備在sysfs中都有唯一對應的目錄,那麼也就可以被用戶空間讀寫了。用戶空間的工具udev就是利用了sysfs提供的資訊來實現所有devfs的功能的,但不同的是,udev運行在用戶空間中,而devfs卻運行在內核空間,而且udev不存在devfs那些先天的缺陷。很顯然,sysfs將是未來發展的方向。

The top level sysfs directory looks like:
block/
bus/
class/
devices/
firmware/
net/
fs/

devices/ contains a filesystem representation of the device tree. It maps directly to the internal kernel device tree, which is a hierarchy of struct device.

bus/ contains flat directory layout of the various bus types in the kernel. Each bus's directory contains two subdirectories:
       devices/
       drivers/
devices/ contains symlinks for each device discovered in the system that point to the device's  directory under root/.

drivers/ contains a directory for each device driver that is loaded for devices on that particular bus (this assumes that drivers do not span multiple bus types).

fs/ contains a directory for some filesystems. Currently each filesystem wanting to export attributes must create its own hierarchy below fs/ (see ./fuse.txt for an example).

三、udev

udev是一種工具,它能夠根據系統中的硬體設備的狀況動態更新設備檔,包括設備檔的創建,刪除等。設備檔通常放在/dev目錄下,使用udev後,在/dev下面只包含系統中真實存在的設備。它於硬體平臺無關的,位於用戶空間,需要內核sysfs和tmpfs的支援,sysfs為udev提供設備入口和uevent通道,tmpfs為udev設備檔提供存放空間。

udev運作的原理很簡單,它透過netlink得知目前kernel有那些module新增了,在收到kernel module新增的netlink訊息之後,它會先掃描user是否有指派device node rule,如果沒有,會自動根據module的major和minor number建立device node.有一點比較特殊的地方是,udev用inotify監聽rule變化的event,所以可以即時改變device node的狀態,udev的架構圖可參考如下




如前述,udev的功能是動態的對/dev/下的device node做增減的動作。動態增減是為了解決以往/dev下有太多沒用到的device node,而devfs又有一些"先天的缺陷":

1. 屬於kernel space
2. 存在一些可解和解不了的bug
3. 作者消失,維護者也不見了
4....

在種種原因下,udev取代了devfs。當然 devfs也不是沒有優點,如比較小,比較快(速度),比較省(memory),但anyway,過去就讓它過去了。


來吧
基本上,rule是用來在某些條件成立後,執行指令,包括增減device node,並執行外部程式等。比如說,在插入usb disk時,建立/dev/sdb,並mount起來,然後彈出檔案總管。大致條列如下:
1. 將kernel預設的device node改個名字,如把/dev/sdb改成 /dev/apacer-8G (by NAME, no longer works after udev became a part of systemd ??? )
2. 為預設device node建立symbolic link (by SYMLINK)
3. 更彈性的用法,用外部程式來替device node命名 (by PROGRAM)
4. 改變device node的permission和ownership (by GROUP and MODE)
5. device node增減時,執行shell script (by RUN)

時機
通常改變rule是為了改變系統預設的行為,因此,會希望在預設動作前,先完成自己想要做的事。而udev是根據字母排序來parse rule。在rule裡,#開頭的是註解,且每條rule都要在同一行,每條非空行都被視為一條rule。同一個device可以match多條rule,udev會apply每條match的rule。

Syntax
每條rule是由一串(key, value)組成,由comma(“,”)區隔。兩種key:
1. match key(==): 用來識別device,並決定是否要執行該條rule
2. assignment key(=): 當所有的match key都成立,則執行assignment key指定的指令。
每條rule至少要有一個match key和assignment key。
比如說:
        KERNEL==”sdb”, NAME=”usb_disk”
意思是如果kernel report sdb這個device,則建立usb_disk這個device node (就不會有sdb這個node了)。不過這是比較沒鑑別的rule,因為sdb會隨著插入的順序而assign給不同的usb disk,因此最好是使用可uniquely identified的match key,見下述。
常用的match key如下:
KERNEL: kernel所給的預設名字 (如,sda, lp等)
SUBSYSTEM: device所屬的subsystem,如net, block, usb (可由sysfs得知)
DRIVER: 該device所使用的driver
常用的assignment key如下:
NAME: 該device node的名字
SYMLINK: device node的另一個名字,用symbolic link。這個link可以有很多個,所以可以用+=來做assignmet。
PROGRAM: 指定所需執行的外部程式,其stdout可用來指定device node的名字,用%c表示。若不止一個output,也可用%c{1},%c{2}或%c{2+}等表示式。
RUN: 在某些rule成立時,再多執行另一個外部程式。這和PROGRAM有點像,只是PROGRAM是用來產生device node的名字。

正規表示法
match key也有類似正規表示法的東西:
*:match任一個字母,0個或多個
?:match一個字母
[]: match 括號內任一個字母,可以是某個range,如[a-p],[0-9],或是[!1-3]
例如:
KERNEL=”sd*| hd[a-e]”, NAEM=”block/%k”

字串代換
類似printf的表示法:
%k: kernel給的預設名字,如sdb1
%n:kernel給的device number,若%k是sdb1,則%n是1
%c:外部程式的stdout
特殊字元%與$若要拿來當match key,需寫為:%%和$$

Sysfs登場
以上都是解釋,接下來就是真正的應用了。每個device插入系統時,會export一大堆訊息給sysfs,因此在sysfs裡有許多可用的訊息,而且是可以在rule裡直接剪下貼上的,因此udev和sysfs的關係是相當的密切。先從sysfs裡看看有什麼可以挖的吧!
使用udevinfo來query sysfs:
先插入一個usb disk,看看kernel給了什麼預設的名字:
user@user-ubuntu:/etc/udev/rules.d$ cat /proc/partitions
major minor #blocks name
8 0 244198584 sda
8 1 78180291 sda1
8 2 1 sda2
8 5 158200056 sda5
8 6 7815591 sda6
7 0 10240000 loop0
8 16 7823360 sdb
8 17 7819328 sdb1
這樣可知道,是在sdb,而真正要mount的是sdb1,來查一下:
udevadm info -a -p /sys/block/sdb/sdb1 會看到如下的output (只挑其中兩段做例子):
looking at device '/devices/pci0000:00/0000:00:1d.7/usb4/4-3/4-3:1.0/….':
KERNEL=="sdb1"
SUBSYSTEM=="block"
DRIVER==""
ATTR{start}=="8064"
ATTR{size}=="15638656"
ATTR{stat}==" 78 2219 2765 164 4 ……."

looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb4/4-3/4-3:1.0/…':
KERNELS=="52:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{device_blocked}=="0"
ATTRS{type}=="0"
ATTRS{scsi_level}=="0"
ATTRS{vendor}==" "
ATTRS{model}=="USB FLASH DRIVE "
ATTRS{rev}=="PMAP"
ATTRS{state}=="running"
ATTRS{timeout}=="30"
ATTRS{iocounterbits}=="32"
ATTRS{iorequest_cnt}=="0xe3"
ATTRS{iodone_cnt}=="0xe3"

其實這些資訊都是透過driver export給sysfs,而udevinfo只是透過sysfs裡所記錄的內容,整理過後程現出來有關這個device的屬性。而這些屬性都是可以用來做match key。還有一個原則是每段的屬性是不能交互寫在同一個rule。比如說:
KERNEL==”sdb1”, ATTR{size}=="15638656", NAME=”apacer-disk”
SUBSYSTEMS=="scsi", ATTRS{model}=="USB FLASH DRIVE ", NAME=”apacer-disk”
這兩條rule都是對的,但不能這樣用:
KERNEL==”sdb1”, ATTR{size}=="15638656", ATTRS{model}=="USB FLASH DRIVE ", NAME=”apacer-disk”

Permission and ownership
這個比較直覺,就是設定而已
KERNEL==”sdb1”, ATTR{size}=="15638656", NAME=”apacer-disk”, GROUP=”user”, MODE=”0755”
結果如下:
brwxr-xr-x 1 root user 8, 33 2009-07-03 21:52 apacer-8g

Naming by External Program
比較複雜的情況下,可以存在一支命名程式,使用其stdout來當作device node的名字。在rule中,甚至可以傳參數給這支外部程式。其stdout用%c來表;
示,若有多組output,可用%c{1},%c{2}或%c{2+}這樣的表示方式。舉例如下

SUBSYSTEM=="block", ATTR{size}=="15638656", PROGRAM=”/usr/bin/usb-naming %k”, NAME=”%c”
%k是kernel預設的名字,usb-naming接收此名字後,output結果至stdout。

Run external program upon events
當收到某個udev event後,可再執行某個外部指令。這個跟PROGRAM類似,但不是做naming。比如說,在mount完usb後,做個記錄,可以這麼寫:
SUBSYSTEM=="block", ATTR{size}=="15638656", NAME=”apacer-8g”, RUN+=”/usr/bin/mount-log %k”。

特殊選項
all_partition: 意思大概是為block device建立所有可能的partition,而不是只建一開始偵測到的,不知道這個有什麼應用。
ignore_device:忽略這個event。這個還蠻好用。有些usb有不止一個partition,比如說一個是開機,一個是data。如果每次udev都把這兩個 partition mount起來,但是開機 那個永遠用不到,那就很煩。可用這個選項來忽略開機partition的event。比如:
SUBSYSTEM=="block", ATTR{size}=="1440", OPTIONS+=”ignore_device”
last_rule:對該device不再match之後的rule

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.