熱插拔是內(nèi)核和用戶空間之間波附,通過(guò)調(diào)用用戶空間程序(如hotplug柏靶、udev 和 mdev)的交互。 當(dāng)需要通知用戶內(nèi)核發(fā)生了某種熱插拔事件時(shí)想罕,內(nèi)核才調(diào)用這個(gè)用戶空間程序。
1. 3種熱插拔機(jī)制
Linux內(nèi)核支持熱插拔的部件有USB設(shè)備霉涨、PCI設(shè)備甚至CPU按价。Linux的熱插拔支持是一個(gè)連接底層硬件、內(nèi)核空間和用戶空間程序的機(jī)制笙瑟,且一直在變化楼镐。而設(shè)備文件系統(tǒng)有devfs,mdev,udev這三種。
在對(duì)待設(shè)備文件這塊往枷,Linux改變了幾次策略框产。在Linux早期,設(shè)備文件僅僅是是一些帶有適當(dāng)?shù)膶傩约钠胀ㄎ募斫啵蒻knod命令創(chuàng)建秉宿,文件存放在/dev目錄下。后來(lái)墓臭,采用了devfs, 一個(gè)基于內(nèi)核的動(dòng)態(tài)設(shè)備文件系統(tǒng)蘸鲸,他首次出現(xiàn)在2.3.46內(nèi)核中。Mandrake窿锉,Gentoo等Linux分發(fā)版本采用了這種方式。devfs創(chuàng)建 的設(shè)備文件是動(dòng)態(tài)的膝舅。但是devfs有一些嚴(yán)重的限制嗡载,從2.6.13版本后移走了。目前取代他的是udev(PC機(jī)上的linux中)和mdev(嵌入式linux系統(tǒng))仍稀。
在需要控制上千個(gè)硬盤(pán)或熱插拔設(shè)備(比如USB攝像頭和MP3播放器)時(shí)一般建議采用udev機(jī)制洼滚。實(shí)際上,你不需要修改這些為磁盤(pán)或熱插拔終端設(shè)備的標(biāo)準(zhǔn)配置文件技潘,而僅需要了解udev的配置方法來(lái)使用這些新的外設(shè)遥巴,如果不修改配置, Linux可能會(huì)采用不恰當(dāng)?shù)拿窒碛模瑢俳M或權(quán)限來(lái)創(chuàng)建這些設(shè)備文件铲掐。
udev和devfs的一個(gè)顯著的區(qū)別在于:采用devfs,當(dāng)一個(gè)并不存在的/dev節(jié)點(diǎn)被打開(kāi)的時(shí)候值桩,devfs能自動(dòng)加載對(duì)應(yīng)的驅(qū)動(dòng)摆霉,而udev是在設(shè)備被發(fā)現(xiàn)的時(shí)候才加載驅(qū)動(dòng)模塊,而不是當(dāng)它被訪問(wèn)的時(shí)候。
1.1hotplug
這個(gè)程序是一個(gè)典型的 bash 腳本携栋, 當(dāng)用戶向系統(tǒng)添加或刪除設(shè)備時(shí)搭盾,內(nèi)核會(huì)產(chǎn)生一個(gè)熱插拔事件,并在 /proc/sys/kernel/hotplug 文件里查找處理設(shè)備連接的用戶空間程序hotplug婉支。它只傳遞執(zhí)行權(quán)給一系列位于 /etc/hot-plug.d/ 目錄樹(shù)的程序鸯隅。hotplug 腳本搜索所有的有 .hotplug 后綴的可能對(duì)這個(gè)事件進(jìn)行處理的程序并調(diào)用它們, 并傳遞給它們?cè)S多不同的已經(jīng)被內(nèi)核設(shè)置的環(huán)境變量。(目前基本已被淘汰)
其工作前提是硬件本身會(huì)告訴計(jì)算機(jī)自己是做什么的(就算沒(méi)有向挖,它也會(huì)告訴內(nèi)核自己的生廠商代碼和獨(dú)一無(wú)二的產(chǎn)品代碼)蝌以;驅(qū)動(dòng)程序清楚自己是驅(qū)動(dòng)哪一類設(shè)備的;內(nèi)核通過(guò)總線底層代碼清楚什么時(shí)間什么樣的設(shè)備被接入或移出計(jì)算機(jī)户誓。
/sbin/hotplug的本質(zhì)是一個(gè)腳本饼灿。腳本中解析相關(guān)參數(shù)并調(diào)用modprobe和rmmod完成加載和卸載操作。但是帝美,/sbin/hotplug本身是被誰(shuí)調(diào)用的呢碍彭?設(shè)備驅(qū)動(dòng)程序一般不會(huì)和這些太底層的kobject/kset家伙打交道,因?yàn)楦邔哟蔚膁evice,bus和driver把kobject/kset那一層的細(xì)節(jié)實(shí)現(xiàn)都給封裝了起來(lái)悼潭。
以device_add為起點(diǎn)庇忌,uevent事件被這樣產(chǎn)生和傳遞:
device_add
=> kobject_uevent(&dev->kobj, KOBJ_ADD)
=> /* send netlink message */
...
/* 準(zhǔn)備參數(shù) */
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
...
/* 內(nèi)核空間調(diào)用用戶空間的程序 */
call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);
…
下面看看uevent_helper[0]來(lái)自何處:
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
CONFIG_UEVENT_HELPER_PATH其實(shí)是空值〗⑼剩可以通過(guò)向sysfs接口/sys/kernel/uevent_helper寫(xiě)入應(yīng)用空間程序路徑皆疹。
1.2 udev
用于linux2.6.13或更高版本的內(nèi)核上,為用戶空間提供使用固定設(shè)備名的動(dòng)態(tài)/dev目錄的解決方案占拍。它通過(guò)在 sysfs 的 /class/ 和/block/ 目錄樹(shù)中查找一個(gè)稱為 dev 的文件略就,以確定所創(chuàng)建的設(shè)備節(jié)點(diǎn)文件的主次設(shè)備號(hào)。所以要使用udev晃酒,驅(qū)動(dòng)必須為設(shè)備在sysfs中創(chuàng)建類接口及其dev屬性文件表牢,方法和sculld模塊中創(chuàng)建dev屬性相同。
udev的工作原理: 當(dāng)系統(tǒng)內(nèi)核發(fā)現(xiàn)系統(tǒng)中添加或者刪除了某個(gè)新的設(shè)備時(shí)贝次,內(nèi)核檢測(cè)到后會(huì)產(chǎn)生一個(gè)hotplug event并查找/proc/sys/kernel/hotplug去找出管理設(shè)備連接的用戶空間程序崔兴。若udev已經(jīng)啟動(dòng),內(nèi)核會(huì)通知udev去檢測(cè)sysfs中關(guān)于這個(gè)新設(shè)備的信息并創(chuàng)建設(shè)備節(jié)點(diǎn)蛔翅。udev就會(huì)去執(zhí)行udevd敲茄,以便讓udevd可以產(chǎn)生或者刪除硬件的設(shè)備文件。 接著udevd會(huì)通過(guò)libsysfs讀取sys文件系統(tǒng)山析,以便取得該硬件設(shè)備的信息(如/dev/vcs堰燎,在/sys/class/tty/vcs/dev存放的是”7:0”,既/dev/vcs的主次設(shè)備號(hào))盖腿;然后再向namedev查詢?cè)撏獠吭O(shè)備的設(shè)備文件信息爽待,例如文件的名稱损同、權(quán)限等。最后鸟款,udevd就依據(jù)上述的結(jié)果膏燃,在/dev/目錄中自動(dòng)建立該外部設(shè)備的設(shè)備文件,同時(shí)在/etc/udev/rules.d下檢查有無(wú)針對(duì)該設(shè)備的使用權(quán)限何什。
當(dāng)設(shè)備插入或移除時(shí)组哩,hotplug機(jī)制會(huì)讓內(nèi)核會(huì)通過(guò)netlink socket通訊(內(nèi)核調(diào)用kobject_uevent函數(shù)發(fā)送netlink message給用戶空間,該功能由內(nèi)核的統(tǒng)一設(shè)備模型里的子系統(tǒng)這一層實(shí)現(xiàn))向用戶傳遞一個(gè)事件的發(fā)生处渣,Udevd通過(guò)標(biāo)準(zhǔn)的socket機(jī)制伶贰,創(chuàng)建socket連接來(lái)獲取內(nèi)核廣播的uevent事件 并解析這些uevent事件。
運(yùn)行udevd以后罐栈,使用udevtrigger的時(shí)候黍衙,會(huì)把內(nèi)核中已存在的設(shè)備的節(jié)點(diǎn)創(chuàng)建出來(lái),其具體過(guò)程為:udevtrigger通過(guò)向/sysfs 文件系統(tǒng)下現(xiàn)有設(shè)備的uevent節(jié)點(diǎn)寫(xiě)"add"字符串荠诬,從而觸發(fā)uevent事件琅翻,使得udevd能夠接收到這些事件,并創(chuàng)建buildin的設(shè)備驅(qū)動(dòng)的設(shè)備節(jié)點(diǎn)連同任何已insmod的模塊的設(shè)備節(jié)點(diǎn)柑贞。
所以方椎,我們也能夠手工用命令行來(lái)模擬這一過(guò)程:
# echo "add" > /sys/block/mtdblock2/uevent
實(shí)際上,不管您往uevent里面寫(xiě)什么钧嘶,都會(huì)觸發(fā)add事件棠众,這個(gè)從kernel內(nèi)部對(duì)uevent屬性的實(shí)現(xiàn)函數(shù)能夠看出來(lái).
而udevstart的實(shí)現(xiàn)方式和udevtrigger就不同了,他基本上是重復(fù)實(shí)現(xiàn)了udevd里面的機(jī)制有决,通過(guò)遍歷sysfs闸拿,自己完成設(shè)備節(jié)點(diǎn)的創(chuàng)建,不通過(guò)udevd來(lái)完成书幕。
Uevent_seqnum 用來(lái)標(biāo)識(shí)當(dāng)前的uevent事件的序號(hào)(已產(chǎn)生了多少uevent事件)胸墙,您能夠通過(guò)如下操作來(lái)查看:
$ cat /sys/kernel/uevent_seqnum
2673
/sbin/hotplug接收到內(nèi)核的熱插拔事件后會(huì)執(zhí)行一系列腳本,其中一個(gè)腳本執(zhí)行了/sbin/udevsend按咒,從而讓udev的守護(hù)進(jìn)程知悉這一事件[3]。不過(guò)現(xiàn)在但骨,有些發(fā)行版中/sbin/目錄已經(jīng)不存在hotplug和udevsend了励七。熱插拔事件通過(guò)netlink由udevd直接接收并全權(quán)負(fù)責(zé)。通過(guò)下面這條命令可以查看系統(tǒng)中傳遞給udevd的熱插拔事件:
udevadm monitor
- udev實(shí)現(xiàn)自動(dòng)加載:
注意:請(qǐng)區(qū)別于開(kāi)機(jī)自動(dòng)加載:
開(kāi)機(jī)自動(dòng)加載:
將模塊.ko文件復(fù)制到/lib/modules/uname -r/kernel/modulename.ko 目錄并更新 /etc/modules文件即可實(shí)現(xiàn)booting階段自動(dòng)加載模塊奔缠。(這里沒(méi)有用到熱插拔)
下面介紹如何實(shí)現(xiàn)設(shè)備節(jié)點(diǎn)的自動(dòng)創(chuàng)建及u盤(pán)或sd卡的自動(dòng)掛載:
-
若文件系統(tǒng)中默認(rèn)是沒(méi)有對(duì)udev進(jìn)行支持的掠抬,我們還需要移植一個(gè)udev:
下載udev源碼udev-100.tar.bz2,并解壓(網(wǎng)址:http://www.us.kernel.org/pub/linux/utils/kernel/hotplug)
設(shè)置交叉編譯選項(xiàng):修改makefile中的交叉編譯工具:
cross = arm-linux-
校哎,保存后執(zhí)行make進(jìn)行編譯两波;執(zhí)行命令make進(jìn)行編譯瞳步,然后執(zhí)行
arm-linux-strip udev udevd udevstart udevinfo udevtest
,并拷貝這些文件到目標(biāo)板根文件/bin目錄下面腰奋。-
添加udev的支持(以下3種方法任選其一即可):
-
修改etc/init.d/rcs腳本单起,然后添加如下命令:
/bin/mount -t sysfs sysfs /sys /bin/mount -t tmpfs tmpfs /dev /bin/udevd --daemon /bin/udevstart
-
修改linuxrc文件(如果linuxrc是二進(jìn)制文件的話 ,則刪除后再創(chuàng)建文本文件)
/bin/mount -t sysfs sysfs /sys /bin/mount -t tmpfs tmpfs /dev /bin/udevd --daemon /bin/udevstart exec /sbin/init
-
修改/etc/fstab為:
#device mount-point type options dump fsck order proc /proc proc defaults 0 0 tmpfs /tmp tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 tmpfs /dev tmpfs defaults 0 0
-
修改/etc/init.d/rcs劣坊,添加如下內(nèi)容:
/bin/udevd --daemon
/bin/udevstart
-
-
編譯內(nèi)核時(shí)確保配置了如下選項(xiàng):
CONFIG_HOTPLUG=y CONFIG_UEVENT_HELPER_PATH="" CONFIG_NET=y CONFIG_UNIX=y CONFIG_SYSFS=y CONFIG_SYSFS_DEPRECATED*=n CONFIG_PROC_FS=y CONFIG_TMPFS=y CONFIG_INOTIFY_USER=y CONFIG_SIGNALFD=y CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes) CONFIG_BLK_DEV_BSG=y (SCSI devices)
在/etc下創(chuàng)建udev目錄
在/etc/udev下創(chuàng)建目錄rules.d和文件udev.conf
-
在udev.conf中添加如下內(nèi)容
# udev.conf # the initial syslog(3) priority: "err", "info", "debug" or its # numerical equivalent. for runtime debugging, the daemons internal # state can be changed with: "udevcontrol log_priority=<value>". udev_log="err"
udev_root:udev 產(chǎn)生的設(shè)備所存放的目錄嘀倒,默認(rèn)值是 /dev/。建議不要修改該參數(shù)局冰,因?yàn)楹芏鄳?yīng)用程序默認(rèn)會(huì)從該目錄調(diào)用設(shè)備文件测蘑。
udev_db:udev 信息存放的數(shù)據(jù)庫(kù)或者所在目錄,默認(rèn)值是 /dev/.udev.tdb康二。
udev_rules:udev 規(guī)則文件的名字或者所在目錄碳胳,默認(rèn)值是 /etc/udev/rules.d/。
udev_permissions:udev 權(quán)限文件的名字或者所在目錄沫勿,默認(rèn)值是 /etc/udev/permissions.d/挨约。
default_mode/ default_owner/ default_group:如果設(shè)備文件的權(quán)限沒(méi)有在權(quán)限文件里指定,就使用該參數(shù)作為默認(rèn)權(quán)限藕帜,默認(rèn)值分別是:0600/root/root烫罩。
udev_log:是否需要 syslog記錄 udev 日志的開(kāi)關(guān),默認(rèn)值是 no洽故。
-
在目錄rules.d/下創(chuàng)建.config規(guī)則文件
-
如實(shí)現(xiàn)u盤(pán)自動(dòng)掛載贝攒,則創(chuàng)建11-add-usb.rules文件,內(nèi)容為:
action!="add",goto="farsight" kernel=="sd[a-z][0-9]",run+="/sbin/mount-usb.sh %k" label="farsight"
action后是說(shuō)明是什么事件时甚,kernel后是說(shuō)明是什么設(shè)備比如sda1隘弊,mmcblk0p1等,run這個(gè)設(shè)備插入后去執(zhí)行哪個(gè)程序%k是傳入這個(gè)程序的參數(shù)荒适,這里%k=kernel的值也就是sda1等
-
在/sbin/下創(chuàng)建mount-usb.sh文件梨熙,添加如下內(nèi)容:
#!/bin/sh /bin/mount -t vfat /dev/$1 /tmp sync
修改文件權(quán)限為其添加可執(zhí)行的權(quán)限:
chmod u+x mount-usb.sh
至此,就實(shí)現(xiàn)了u盤(pán)的自動(dòng)掛載刀诬。
-
如實(shí)現(xiàn)u盤(pán)自動(dòng)卸載咽扇,則創(chuàng)建11-remove-usb.rules文件,內(nèi)容為:
action !="remove",goto="farsight" subsystem!="block",goto="farsight" kernel=="sd[a-z][0-9]",run+="/sbin/umount-usb.sh" label="farsight"
-
在/sbin/下創(chuàng)建umount-usb.sh文件陕壹,添加如下內(nèi)容:
#!/bin/sh sync umount /tmp/
-
如實(shí)現(xiàn)SD卡自動(dòng)掛載质欲,則創(chuàng)建12-add-sd.rules文件,內(nèi)容為:
action!="add",goto="farsight" kernel=="mmcblk[0-9]p[0-9]",run+="/sbin/mount-sd.sh %k" label="farsight"
-
在/sbin/下創(chuàng)建mount-sd.sh文件糠馆,添加如下內(nèi)容:
#!/bin/sh /bin/mount -t vfat /dev/$1 /tmp sync
-
如實(shí)現(xiàn)sd卡自動(dòng)卸載嘶伟,則創(chuàng)建12-remove-sd.rules文件,內(nèi)容為:
action !="remove",goto="farsight" subsystem!="block",goto="farsight" kernel=="mmcblk",run+="/sbin/umount-sd.sh" label="farsight"
-
在/sbin/下創(chuàng)建umount-sd.sh文件又碌,添加如下內(nèi)容:
#!/bin/sh sync /bin/umount /tmp/
-
- 下面對(duì)規(guī)則文件的規(guī)則進(jìn)行簡(jiǎn)要的價(jià)紹:
udev的規(guī)則文件放在/lib/udev/rules.d
和/etc/udev/rules.d
兩個(gè)目錄中九昧,后者的優(yōu)先權(quán)較高:后者目錄中的規(guī)則文件會(huì)覆蓋前者中同名文件绊袋。這個(gè)文件通常很短,他可能只是包含幾行#開(kāi)頭的注釋铸鹰,然后有幾行選項(xiàng):
udev_root = "/dev/"
udev_rules = "/etc/udev/rules.d/"
udev_log = "err"
上面的第二行非常重要癌别,因?yàn)樗硎緐dev規(guī)則存儲(chǔ)的目錄,這個(gè)目錄存儲(chǔ)的是以.rules
結(jié)束的文件掉奄。每一個(gè)文件處理一系列規(guī)則來(lái)幫助udev分配名字給設(shè)備文件以保證能被內(nèi)核識(shí)別规个。在/etc/udev/rules.d下面會(huì)有好幾個(gè)udev規(guī)則文件,這些文件一部分是udev包安裝的姓建,另外一部分則是可能是別的硬件或者軟件包 生成的诞仓。這些規(guī)則文件的文件名通常是兩個(gè)數(shù)字開(kāi)頭,它表示系統(tǒng)應(yīng)用該規(guī)則的順序速兔。
規(guī)則文件里的規(guī)則有一系列的鍵/值對(duì)組成墅拭,鍵/值對(duì)之間用逗號(hào)(,)分割。每一個(gè)鍵或者是用戶匹配鍵涣狗,或者是一個(gè)賦值鍵谍婉。匹配鍵確定規(guī)則是否被應(yīng)用,而賦 值鍵表示分配某值給該鍵镀钓。這些值將影響udev創(chuàng)建的設(shè)備文件穗熬。賦值鍵可以處理一個(gè)多值列表。匹配鍵和賦值鍵操作符解釋見(jiàn)下表:
操作符 | 匹配/賦值 | 涵義 |
---|---|---|
== | 匹配 | 相等 |
!= | 匹配 | 不相等 |
= | 賦值 | 覆蓋賦值 |
+= | 賦值 | 追加賦值 |
:= | 賦值 | 賦值后丁溅,后面的規(guī)則不能覆蓋它 |
常用鍵列舉如下:
ACTION 一個(gè)事件的名字唤蔗,比如add,當(dāng)設(shè)備增加的時(shí)候
KERNEL 在內(nèi)核里看到的設(shè)備名字窟赏,比如sd*表示任意SCSI磁盤(pán)設(shè)備
DEVPATH 內(nèi)核設(shè)備目錄妓柜,比如/devices
SUBSYSTEM 子系統(tǒng)名字,比如sound,net
BUS 總線的名字涯穷,比如IDE,USB
DRIVER 設(shè)備驅(qū)動(dòng)的名字棍掐,比如ide-cdrom
ID 獨(dú)立于內(nèi)核名字的設(shè)備名字
SYSFS{ value} sysfs屬性值,他可以表示任意
ENV{ key} 環(huán)境變量拷况,可以表示任意
PROGRAM 可執(zhí)行的外部程序作煌,如果程序返回0值,該鍵則認(rèn)為為真(true)
RESULT 上一個(gè)PROGRAM調(diào)用返回的標(biāo)準(zhǔn)輸出赚瘦。
NAME 根據(jù)這個(gè)規(guī)則創(chuàng)建的設(shè)備文件的文件名最疆。注意:僅僅第一行的NAME描述是有效的,
后面的均忽略蚤告。 想使用兩個(gè)以上的名字訪問(wèn)一個(gè)設(shè)備的話,可以考慮SYMLINK鍵服爷。
SYMLINK 根據(jù)規(guī)則創(chuàng)建的字符連接名
OWNER 設(shè)備文件的屬主杜恰。
GROUP 設(shè)備文件所在的組获诈。
MODE 設(shè)備文件的權(quán)限,采用8進(jìn)制
RUN 為設(shè)備而執(zhí)行的程序列表
LABEL 在配置文件里為內(nèi)部控制而采用的名字標(biāo)簽(同下面的GOTO服務(wù))
GOTO 跳到匹配的規(guī)則(通過(guò)LABEL來(lái)標(biāo)識(shí))心褐,有點(diǎn)類似程序語(yǔ)言中的GOTO
IMPORT{ type} 導(dǎo)入一個(gè)文件或者一個(gè)程序執(zhí)行后而生成的規(guī)則集到當(dāng)前文件
WAIT_FOR_SYSFS 等待一個(gè)特定的設(shè)備文件的創(chuàng)建舔涎。主要是用作時(shí)序和依賴問(wèn)題。
PTIONS 特定的選項(xiàng): last_rule 對(duì)這類設(shè)備終端規(guī)則執(zhí)行逗爹; ignore_device 忽略當(dāng)前規(guī)則亡嫌; ignore_remove 忽略接下來(lái)的并移走請(qǐng)求。all_partitions 為所有的磁盤(pán)分區(qū)創(chuàng)建設(shè)備文件
一些規(guī)則的例子:
/*匹配任意被內(nèi)核識(shí)別到的設(shè)備掘而,然后設(shè)定這些設(shè)備的屬組是root挟冠,組是root,
*訪問(wèn)權(quán)限模式是(-rw——-)袍睡。這也是一個(gè)安全的缺省設(shè)置,保證所有的設(shè)備在默
*認(rèn)情況下只有root可以讀寫(xiě)
*/
KERNEL=="*", OWNER="root" GROUP="root", MODE="0600"
/*匹配終端設(shè)備(tty)知染,然后設(shè)置新的權(quán)限為0666,所在的組是tty斑胜。它也設(shè)置了
*一個(gè)特別的設(shè)備文件名:%K,代表設(shè)備的內(nèi)核名字控淡。意味著內(nèi)核識(shí)別出這些設(shè)備是
*什么名字,就創(chuàng)建什么樣的設(shè)備文件名止潘。
*/
KERNEL=="tty", NAME="%k", GROUP="tty", MODE="0666", OPTIONS="last_rule"
/*scd[0-9]表示 SCSI CD-ROM 驅(qū)動(dòng). 它創(chuàng)建一對(duì)設(shè)備符號(hào)連接:cdrom和cdrom-%k掺炭。*/
KERNEL=="scd[0-9]*", SYMLINK+="cdrom cdrom-%k"
/*hd[a-z]表示ATA CDROM驅(qū)動(dòng)器。這個(gè)規(guī)則創(chuàng)建和上面的規(guī)則相同的符號(hào)連接凭戴。
*ATA CDROM驅(qū)動(dòng)器需要sysfs值來(lái)區(qū)別別的ATA設(shè)備涧狮,而SCSI CDROM可以被內(nèi)核唯一識(shí)別。
*/
KERNEL=="hd[a-z]", BUS=="ide", SYSFS{removable}=="1", SYSFS{device/media}=="cdrom", SYMLINK+="cdrom cdrom-%k"
/*告訴udev增加/sbin/modprobe sg 到命令列表簇宽,當(dāng)任意SCSI設(shè)備增加到系統(tǒng)后勋篓,這些命令將執(zhí)行。
*其效果就是當(dāng)有新的SCSI設(shè)備插到計(jì)算機(jī)中時(shí)會(huì)自動(dòng)加載sg模塊魏割。
*/
ACTION=="add", SUBSYSTEM=="scsi_device", RUN+="/sbin/modprobe sg"
在修改udev配置之前譬嚣,我們通常的考慮是:不要修改系統(tǒng)預(yù)置的規(guī)則,特別是那些影響非常廣泛的配置钞它,比比如上面例子中的第一行拜银。我們正確的做法應(yīng)該是在/etc/udev/rules.d/下創(chuàng)建一個(gè)新的規(guī)則文件,并確定該文件的文件名包含的數(shù)字序列應(yīng)該比標(biāo)準(zhǔn)配置文件高遭垛。
比如尼桶,你要修改floppy設(shè)備的所在組,并創(chuàng)建一個(gè)新的符號(hào)連接/dev/floppy锯仪,那你可以這么寫(xiě):
KERNEL=="fd[0-9]*", GROUP="users", SYMLINK+="floppy"
再如泵督,讓USB設(shè)備不用root權(quán)限訪問(wèn),則需在/etc/udev/rules.d/
目錄下新建一個(gè)文件庶喜,取名可以是90-tofu.rules小腊, 內(nèi)容如下:
SUBSYSTEM=="usb", ATTRS{idProduct}=="f408", ATTRS{idVendor}=="040e", GROUP="tofu", MODE="0666"
然后重新插拔設(shè)備救鲤,即可。
再如秩冈,想自動(dòng)加載鍵盤(pán):
為udev鍵盤(pán)規(guī)則文件60-keyboard.rules開(kāi)頭增加:
ACTION=="add",RUN+="/lib/udev/hello.sh" //當(dāng)鍵盤(pán)接入時(shí)本缠,自動(dòng)運(yùn)行hello.sh腳本
ACTION=="remove",RUN+="/lib/udev/bye.sh" //當(dāng)鍵盤(pán)拔出時(shí),自動(dòng)運(yùn)行bye.sh腳
在腳本文件中添加裝卸載命令:
/*
* /lib/udev/hello.sh:
*/
#!/bin/bash
sudo -H insmod /hello.ko
/*
* /lib/udev/bye.sh:
*/
#!/bin/bash
sudo -H rmmod hello
使用udev規(guī)則修改rules.d目錄下的特定規(guī)則文件并創(chuàng)建相應(yīng)的腳本文件后入问,插拔USB丹锹、PCI等設(shè)備時(shí)時(shí)就能自動(dòng)加載模塊了。
但這只能針對(duì)特定的設(shè)備芬失,而且過(guò)程中需要修改和創(chuàng)建各種文件楣黍,太繁瑣了。如果我想針對(duì)某一類設(shè)備時(shí)又該怎么辦呢麸折?
- MODULE_DEVICE_TABLE實(shí)現(xiàn)自動(dòng)加載
這是一種單純地僅需在模塊的源代碼級(jí)實(shí)現(xiàn)自動(dòng)加載:
首先锡凝,使用MODULE_DEVICE_TABLE宏注冊(cè)模塊。
接著垢啼,編譯模塊并將編譯后產(chǎn)生的.ko文件拷貝至/lib/modules/ `uname -r`/目錄下窜锯。
然后,使用sudo depmod -a
命令將新的模塊信息加入/lib/modules/ `uname -r`/目錄下的modules.alias和modules.dep文件中芭析。
#define USB_KEYBOARD_VENDOR_ID 0x093a
#define USB_KEYBOARD_PRODUCT_ID 0x2510
static struct usb_device_id usb_kbd_id_table[] =
{
{USB_DEVICE(USB_KEYBOARD_VENDOR_ID, USB_KEYBOARD_PRODUCT_ID) },
{},
};
MODULE_DEVICE_TABLE(usb, usb_kbd_id_table);
上面的代碼是實(shí)現(xiàn)一塊鍵盤(pán)連接上計(jì)算機(jī)后自動(dòng)加載模塊這個(gè)功能所需在模塊中添加的部分锚扎。VENDOR_ID和PRODUCT_ID每個(gè)鍵盤(pán)是不一樣的,可以把鍵盤(pán)連接在計(jì)算機(jī)后馁启,使用lsusb命令確定鍵盤(pán)的這兩個(gè)值驾孔。如果你需要對(duì)每個(gè) USB 設(shè)備都響應(yīng)而不是特定的VENDOR_ID和PRODUCT_ID值, 那么需要?jiǎng)?chuàng)建一個(gè)只設(shè)置這個(gè) driver_info 成員的入口項(xiàng):
static struct usb_device_id usb_ids[] =
{
{.driver_info = 42},
{},
};
MODULE_DEVICE_TABLE(usb, usb_ids);
如果只想對(duì)所有的USB鍵盤(pán)做響應(yīng),那么是這樣的:
static struct usb_device_id usb_kbd_id_table[] =
{
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,
USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_KEYBOARD) },
{},
};
MODULE_DEVICE_TABLE(usb, usb_kbd_id_table);
如果惯疙,想在一個(gè)設(shè)備上使用sysfs信息來(lái)唯一標(biāo)識(shí)一個(gè)設(shè)備翠勉。這些信息最好通過(guò)udevinfo命令來(lái)獲取:
你把設(shè)備插入系統(tǒng)后霉颠,系統(tǒng)為設(shè)備產(chǎn)生了設(shè)備名(如/dev/sda)对碌。那樣的
話,你先用udevinfo -q path -n/dev/sda蒿偎,命令會(huì)產(chǎn)生一個(gè)該設(shè)備名對(duì)應(yīng)的在sysfs下的路徑朽们,如/block/sda。然后诉位,你再用udevinfo -a -p/sys/block/sda骑脱,這個(gè)命令會(huì)顯示一堆信息,信息分成很多塊苍糠。這些信息實(shí)際來(lái)自于操作系統(tǒng)維護(hù)的sysfs鏈表叁丧,不同的塊對(duì)應(yīng)不同的路徑。你就可以用這些信息來(lái)作為udev規(guī)則文件中的匹配項(xiàng)。但需要注意的是歹袁,同一個(gè)規(guī)則只能使用同一塊中顯示的信息坷衍,不能跨塊書(shū)寫(xiě)規(guī)則。
[root@localhost rules.d]# udevinfo -a -p $(udevinfo -q path -n /dev/hda1)
Udevinfo starts with the device specified by the devpath and then walks up the chain of
parent devices. It prints for every device found,all possible attributes in the udev rules
key format. A rule to match, can be composed by the attributes of the device and the
attributes from one single parent device.
looking at device '/block/hda/hda1':
KERNEL=="hda1" SUBSYSTEM=="block" DRIVER==""
ATTR{stat}==" 1133 2268 2 4" ATTR{size}=="208782"
ATTR{start}=="63" ATTR{dev}=="3:1" looking at parent device '/block/hda':
KERNELS=="hda" SUBSYSTEMS=="block" DRIVERS==""
ATTRS{stat}=="28905 18814 1234781 302540 34087 133247 849708 981336 0 218340 1283968"
ATTRS{size}=="117210240" ATTRS{removable}=="0"
ATTRS{range}=="64" ATTRS{dev}=="3:0"
looking at parent device '/devices/pci0000:00/0000:00:1f.1/ide0/0.0':
KERNELS=="0.0" SUBSYSTEMS=="ide" DRIVERS=="ide-disk"
ATTRS{modalias}=="ide:m-disk" ATTRS{drivename}=="hda"
ATTRS{media}=="disk"
looking at parent device '/devices/pci0000:00/0000:00:1f.1/ide0':
KERNELS=="ide0" SUBSYSTEMS=="" DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:1f.1':
KERNELS=="0000:00:1f.1" SUBSYSTEMS=="pci" DRIVERS=="PIIX_IDE"
ATTRS{broken_parity_status}=="0" ATTRS{enable}=="1"
ATTRS{modalias}=="pci:v00008086d000024CAsv0000144Dsd0000C009bc01sc01i8a"
ATTRS{local_cpus}=="1" ATTRS{irq}=="11" ATTRS{class}=="0x01018a"
ATTRS{subsystem_device}=="0xc009" ATTRS{subsystem_vendor}=="0x144d"
ATTRS{device}=="0x24ca" ATTRS{vendor}=="0x8086"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00" SUBSYSTEMS=="" DRIVERS==""
1.3 mdev
udev是linux2.6內(nèi)核引入的一種新的設(shè)備文件管理機(jī)制条舔,用于取代老的devfs.udev最大的有點(diǎn)就是可以動(dòng)態(tài)的管理/dev目錄下的設(shè)備文件,而不用再系統(tǒng)初始化時(shí)就將可能要用到的設(shè)備都創(chuàng)建起來(lái)乏矾,還可以根據(jù)設(shè)備具體信息命名設(shè)備節(jié)點(diǎn)孟抗,而不是有內(nèi)核統(tǒng)一分配。但因?yàn)閡dev較mdev復(fù)雜钻心,不太適合嵌入式使用凄硼,所以在嵌入式鄰域一般更多的使用mdev。它是一個(gè)簡(jiǎn)化版的udev捷沸,是busybox所帶的程序摊沉,十分適合嵌入式系統(tǒng)。
udev 和mdev 是兩個(gè)使用uevent 機(jī)制處理熱插拔問(wèn)題的用戶空間程序痒给,兩者的實(shí)現(xiàn)機(jī)理不同说墨。udev 是基于netlink 機(jī)制的,它在系統(tǒng)啟動(dòng)時(shí)運(yùn)行了一個(gè)deamon 程序udevd苍柏,通過(guò)監(jiān)聽(tīng)內(nèi)核發(fā)送的uevent 來(lái)執(zhí)行相應(yīng)的熱拔插動(dòng)作尼斧,包括創(chuàng)建/刪除設(shè)備節(jié)點(diǎn),加載/卸載驅(qū)動(dòng)模塊等等试吁。mdev 是基于uevent_helper 機(jī)制的棺棵,它在系統(tǒng)啟動(dòng)時(shí)修改了內(nèi)核中的uevnet_helper 變量(通過(guò)寫(xiě)/proc/sys/kernel/hotplug),值為“/sbin/mdev”熄捍。這樣內(nèi)核產(chǎn)生uevent 時(shí)會(huì)調(diào)用uevent_helper 所指的用戶級(jí)程序烛恤,也就是mdev,來(lái)執(zhí)行相應(yīng)的熱拔插動(dòng)作余耽。
udev 使用的netlink 機(jī)制在有大量uevent 的場(chǎng)合效率高缚柏,適合用在PC 機(jī)上;而mdev 使用的uevent_helper 機(jī)制實(shí)現(xiàn)簡(jiǎn)單宾添,適合用在嵌入式系統(tǒng)中船惨。另外要說(shuō)明的一點(diǎn)是,uevent_helper 的初始值在內(nèi)核編譯時(shí)是可配置的缕陕,默認(rèn)值為/sbin/hotplug粱锐。如果想修改它的值,寫(xiě)/proc/sys/kernel/hotplug 文件就可以了扛邑,例如:
echo “/sbin/mdev” > /proc/sys/kernel/hotplug
而如果使用的是udevd,那么uevent_helper變量應(yīng)為空怜浅,即
echo "" > /proc/sys/kernel/hotplug
接下來(lái)我們可以分析下熱拔插事件:首先,當(dāng)在驅(qū)動(dòng)程序中創(chuàng)建設(shè)備節(jié)點(diǎn)的時(shí)候,流程都是”先創(chuàng)建一個(gè)類” 然后在”在類下創(chuàng)建設(shè)備”恶座。比如創(chuàng)建一個(gè)/dev/xxx 代碼如下:
static struct class *class;
static struct class_device *class_dev;
class = class_create(THIS_MODULE, "firstdrv");
class_dev = class_device_create(class , NULL, MKDEV(major, 0), NULL, "xxx");
深入class_device_create函數(shù)內(nèi)部搀暑,分析函數(shù)調(diào)用情況:
class_device_create
class_device_register
class_device_add
kobject_uevent(&class_dev->kobj, KOBJ_ADD);
kobject_uevent_env
/*action_string = "add"*/
action_to_string(KOBJ_ADD); /*add*/
/* environment values */
/* 分配保存環(huán)境變量的內(nèi)存 */
buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
/*設(shè)置環(huán)境變量*/
scratch = buffer;
envp [i++] = scratch;
scratch += sprintf(scratch, "ACTION=%s", action_string) + 1;
envp [i++] = scratch;
scratch += sprintf (scratch, "DEVPATH=%s", devpath) + 1;
envp [i++] = scratch;
scratch += sprintf(scratch, "SUBSYSTEM=%s", subsystem) + 1;
call_usermodehelper (argv[0], argv, envp, 0);
/*調(diào)用用戶模式下的輔助程序*/
再分析Busybox的mdev.c
mdev_main
temp = /sys/class/xxx_drv/xxx
action = getenv("ACTION");
env_path = getenv("DEVPATH")
make_device(temp, 0);
fd = open("/etc/mdev.conf", O_RDONLY);/*根據(jù)這個(gè)配置文件*/
/*如果沒(méi)有這個(gè)配置文件的話 就會(huì)創(chuàng)建設(shè)備節(jié)點(diǎn)*/
/* 確定設(shè)備文件名,類型,主次設(shè)備號(hào) */
device_name = bb_basename(path); /* = "xxx" */
/* 'c' == > 字符設(shè)備節(jié)點(diǎn);根據(jù)"/sys/class/xxx_drv/xxx/dev"的內(nèi)容確定主次設(shè)備號(hào)*/
mknod(device_name, mode | type, makedev(major, minor));
其實(shí)熱拔插事件就是利用設(shè)備的加入(add)或者移除(remove),然后把設(shè)備的詳細(xì)信息輸出到sys/下 跨琳,調(diào)用/sbin/mdev根據(jù)環(huán)境變量中的 ACTION 和 DEVPATH自点,來(lái)確定此次熱插拔事件的動(dòng)作以及影響了/sys中的那個(gè)目錄。接著會(huì)看看這個(gè)目錄中是否有“dev”的屬性文件脉让,如果有就利用這些信息為這個(gè)設(shè)備在/dev 下創(chuàng)建設(shè)備節(jié)點(diǎn)文件桂敛。
-
實(shí)現(xiàn)u盤(pán)的自動(dòng)掛載步驟:
-
①用busybox制作根文件系統(tǒng)的時(shí)候,要選擇支持mdev機(jī)制
Linux System Utilities ---> [*] mdev [*] Support /etc/mdev.conf [*] Support command execution at device addition/removal
-
②在/etc/init.d/rsC文件中添加如下內(nèi)容
mount -t tmpfs mdev /dev mkdir /dev/pts mount -t devpts devpts /dev/pts #它是mdev運(yùn)行的基礎(chǔ)條件之一 mount -t sysfs sysfs /sys #設(shè)置系統(tǒng)的hotplug程序?yàn)閙dev溅潜,當(dāng)有熱插拔事件產(chǎn)生時(shí)术唬,內(nèi)核就會(huì)調(diào)用位于 /sbin目錄的mdev。這時(shí)mdev通過(guò)環(huán)境變量中的 ACTION 和 DEVPATH滚澜,來(lái)確定此次熱插拔事件的動(dòng)作以及影響了/sys中的哪個(gè)目錄粗仓。接著會(huì)看看這個(gè)目錄中是否有“dev”屬性文件,如果有就利用這些信息為這個(gè)設(shè)備在/dev 下創(chuàng)建設(shè)備節(jié)點(diǎn)文件设捐。 echo "/sbin/mdev" > /proc/sys/kernel/hotplug #調(diào)用/sbin/mdev程序(其實(shí)是個(gè)鏈接借浊,作用是傳遞參數(shù)給/bin目錄下的busybox程序并調(diào)用它),檢索/sys/class挡育、/sys/block和/proc中所有類設(shè)備目錄巴碗,如果在目錄中含有名為“dev”的文件且文件中包含的是設(shè)備號(hào),則mdev就利用這些信息為這個(gè)設(shè)備在/dev 下創(chuàng)建設(shè)備節(jié)點(diǎn)文件即寒。 mdev –s
-
確保編譯內(nèi)核時(shí)編譯如下選項(xiàng):
CONFIG_PROC_FS=y CONFIG_PROC_SYSCTL=y CONFIG_HOTPLUG=y CONFIG_NET=y
-
-
③在/etc/mdev.conf文件中添加對(duì)熱插拔事件的響應(yīng)橡淆,實(shí)現(xiàn)U盤(pán)和SD卡的自動(dòng)掛載。
sd[a-z][0-9] 0:0 666 @/etc/mdev/udisk_insert.sh sd[a-z] 0:0 666 $/etc/mdev/udisk_remove.sh
@:表示在插入(創(chuàng)建設(shè)備結(jié)點(diǎn))后執(zhí)行后面的腳本母赵;$:表示在拔出(刪除設(shè)備結(jié)點(diǎn))前執(zhí)行后面的腳本逸爵。*:表示在創(chuàng)建設(shè)備結(jié)點(diǎn)后,刪除節(jié)點(diǎn)設(shè)備前執(zhí)行后面的腳本凹嘲。
-
/etc/mdev/udisk_insert .sh
#!/bin/sh if [ -d /sys/block/*/$MDEV ] ;then mkdir -p /media/$MDEV mount /dev/$MDEV /media/$MDEV fi
-
etc/mdev/udisk_remove.sh
#!/bin/sh umount -l /media/sd* rm -rf /media/sd*
-
以上兩個(gè)腳本需要可執(zhí)行權(quán)限:
chmod +x /etc/mdev/udisk_insert.sh chmod +x /etc/mdev/udisk_remove.sh
最后附上mdev官方文檔供大家參考:
-------------
MDEV Primer
-------------
For those of us who know how to use mdev, a primer might seem lame. For
everyone else, mdev is a weird black box that they hear is awesome, but can't
seem to get their head around how it works. Thus, a primer.
-----------
Basic Use
-----------
Mdev has two primary uses: initial population and dynamic updates. Both
require sysfs support in the kernel and have it mounted at /sys. For dynamic
updates, you also need to have hotplugging enabled in your kernel.
Here's a typical code snippet from the init script:
[0] mount -t proc proc /proc
[1] mount -t sysfs sysfs /sys
[2] echo /bin/mdev > /proc/sys/kernel/hotplug
[3] mdev -s
Alternatively, without procfs the above becomes:
[1] mount -t sysfs sysfs /sys
[2] sysctl -w kernel.hotplug=/bin/mdev
[3] mdev -s
Of course, a more "full" setup would entail executing this before the previous
code snippet:
[4] mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
[5] mkdir /dev/pts
[6] mount -t devpts devpts /dev/pts
The simple explanation here is that [1] you need to have /sys mounted before
executing mdev. Then you [2] instruct the kernel to execute /bin/mdev whenever
a device is added or removed so that the device node can be created or
destroyed. Then you [3] seed /dev with all the device nodes that were created
while the system was booting.
For the "full" setup, you want to [4] make sure /dev is a tmpfs filesystem
(assuming you're running out of flash). Then you want to [5] create the
/dev/pts mount point and finally [6] mount the devpts filesystem on it.
-------------
MDEV Config (/etc/mdev.conf)
-------------
Mdev has an optional config file for controlling ownership/permissions of
device nodes if your system needs something more than the default root/root
660 permissions.
The file has the format:
<device regex> <uid>:<gid> <octal permissions>
or @<maj[,min1[-min2]]> <uid>:<gid> <octal permissions>
For example:
hd[a-z][0-9]* 0:3 660
The config file parsing stops at the first matching line. If no line is
matched, then the default of 0:0 660 is used. To set your own default, simply
create your own total match like so:
.* 1:1 777
You can rename/move device nodes by using the next optional field.
<device regex> <uid>:<gid> <octal permissions> [=path]
So if you want to place the device node into a subdirectory, make sure the path
has a trailing /. If you want to rename the device node, just place the name.
hda 0:3 660 =drives/
This will move "hda" into the drives/ subdirectory.
hdb 0:3 660 =cdrom
This will rename "hdb" to "cdrom".
Similarly, ">path" renames/moves the device but it also creates
a direct symlink /dev/DEVNAME to the renamed/moved device.
If you also enable support for executing your own commands, then the file has
the format:
<device regex> <uid>:<gid> <octal permissions> [=path] [@|$|*<command>]
or
<device regex> <uid>:<gid> <octal permissions> [>path] [@|$|*<command>]
The special characters have the meaning:
@ Run after creating the device.
$ Run before removing the device.
* Run both after creating and before removing the device.
The command is executed via the system() function (which means you're giving a
command to the shell), so make sure you have a shell installed at /bin/sh. You
should also keep in mind that the kernel executes hotplug helpers with stdin,
stdout, and stderr connected to /dev/null.
For your convenience, the shell env var $MDEV is set to the device name. So if
the device "hdc" was matched, MDEV would be set to "hdc".
----------
FIRMWARE
----------
Some kernel device drivers need to request firmware at runtime in order to
properly initialize a device. Place all such firmware files into the
/lib/firmware/ directory. At runtime, the kernel will invoke mdev with the
filename of the firmware which mdev will load out of /lib/firmware/ and into
the kernel via the sysfs interface. The exact filename is hardcoded in the
kernel, so look there if you need to know how to name the file in userspace.
------------
SEQUENCING
------------
Kernel does not serialize hotplug events. It increments SEQNUM environmental
variable for each successive hotplug invocation. Normally, mdev doesn't care.
This may reorder hotplug and hot-unplug events, with typical symptoms of
device nodes sometimes not created as expected.
However, if /dev/mdev.seq file is found, mdev will compare its
contents with SEQNUM. It will retry up to two seconds, waiting for them
to match. If they match exactly (not even trailing '\n' is allowed),
or if two seconds pass, mdev runs as usual, then it rewrites /dev/mdev.seq
with SEQNUM+1.
IOW: this will serialize concurrent mdev invocations.
If you want to activate this feature, execute "echo >/dev/mdev.seq" prior to
setting mdev to be the hotplug handler. This writes single '\n' to the file.
NB: mdev recognizes /dev/mdev.seq consisting of single '\n' characher
as a special case. IOW: this will not make your first hotplug event
to stall for two seconds.