- 論壇徽章:
- 0
|
本帖最后由 MagicBoy2010 于 2012-03-03 18:13 編輯
本人CU博客中的文章,本來是希望CU給推薦到首頁的,結(jié)果周五不知道CU博客出了啥問題,居然沒什么更新。所以我干脆把再把它放到這里,因為話題涉及設(shè)備驅(qū)動模塊自動加載,本版已經(jīng)有同學問過這方面的問題。
熱插拔(hotplug,打這個詞的時候我常常想到熱干面)不一定非要指類似U盤那樣的插入拔出,此處的熱插拔廣義上講,是指一個設(shè)備加入系統(tǒng),內(nèi)核如何通知用戶空間。舉個簡單的例子,如果你的電腦中有塊PCI網(wǎng)卡,針對該網(wǎng)卡的驅(qū)動程序以內(nèi)核模塊的形式被編譯(obj-m),那么Linux系統(tǒng)在啟動過程中是如何自動加載該網(wǎng)卡的驅(qū)動模塊呢?大家都知道現(xiàn)在udev負責干這事,其實除了udev,還可以有其他的手法,你自己就可以這樣做。
我們先討論udev,udev最關(guān)鍵的東西是當系統(tǒng)發(fā)現(xiàn)一個設(shè)備時,它要能夠被通知該事件,一旦它知道了這件事,那么余下的事情就都好說了,無非是個如何查找模塊并加載的過程。所以我們看到,這里的關(guān)鍵是熱插拔事件的通知機制。Linux的設(shè)備模型為此提供了非常完美的支持,其原理其實發(fā)源于kset這一層,對此在《深入Linux設(shè)備驅(qū)動程序內(nèi)核機制》一書中有詳細的描述,雖然這部分看起來蠻復(fù)雜,貌似挺能嚇唬住一些新手,其實說白了,要點就是通過sysfs建立關(guān)系,溝通內(nèi)核與用戶空間,然后就是uevent,也就是下面要說的熱插拔事件。
當然設(shè)備驅(qū)動程序一般不會和這些太底層的kobject/kset家伙打交道,因為更高層次的device,bus和driver把kobject/kset那一層的細節(jié)實現(xiàn)都給封裝了起來。所以設(shè)備熱插拔的uevent事件最終的源頭來自于device_add,本帖這里肯定不會討論device與driver如何綁定那一攤子事情。下面看看device_add的源碼,是如何實現(xiàn)uevent機制的:- <drivers/base/core.c>
- int device_add(struct device *dev)
- {
- ...
- kobject_uevent(&dev->kobj, KOBJ_ADD);
- ...
- }
復(fù)制代碼 熱插拔的核心實現(xiàn)就那一個函數(shù)調(diào)用,這里device_add對應(yīng)的是KOBJ_ADD,那么移除設(shè)備自然對應(yīng)KOBJ_REMOVE了。kobject_uevent函數(shù)最終調(diào)用的是kobject_uevent_env,后者才是真正干事的伙計。
下面給出kobject_uevent_env函數(shù)的核心框架:- int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
- char *envp_ext[])
- {
- ...
- #if defined(CONFIG_NET)
- /* send netlink message */
- ...
- #endif
- /* call uevent_helper, usually only enabled during early boot */
- if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
- char *argv [3];
- argv [0] = uevent_helper;
- argv [1] = (char *)subsystem;
- argv [2] = NULL;
- retval = add_uevent_var(env, "HOME=/");
- if (retval)
- goto exit;
- retval = add_uevent_var(env,
- "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
- if (retval)
- goto exit;
- retval = call_usermodehelper(argv[0], argv,
- env->envp, UMH_WAIT_EXEC);
- }
- ...
- }
復(fù)制代碼 怎么樣,夠簡潔吧,其實看實際的代碼比這要郁悶地多,不過骨架清晰就行了。代碼中的netlink message就不用多說了吧,給udev發(fā)通知用(有時間的話可以分析分析udev的代碼)。本帖重點討論后半段的if (uevent_helper[0] && !kobj_usermode_filter(kobj))代碼,這里的核心調(diào)用是call_usermodehelper,這個函數(shù)最有意思的地方就在于在內(nèi)核空間調(diào)用用戶空間的程序,它的詳細實現(xiàn)機制在書中已經(jīng)講得很多,這里就不再贅述了。call_usermodehelper在kobject_uevent_env函數(shù)中要調(diào)用的用戶空間程序由uevent_helper[0]來指定,所以如果我們能控制這個uevent_helper[0],就能接收到設(shè)備加入系統(tǒng)移出系統(tǒng)等事件。那個if中的kobj_usermode_filter條件一般都會滿足(除非這是個特別注意個人隱私的設(shè)備,那就不好說了,人家偷偷加入系統(tǒng)就是不想讓你知道你也沒有辦法,但是udev還是能知道的)。
下面看看uevent_helper[0]來自何處:- <lib/kobject_uevent.c>
- char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
復(fù)制代碼 貌似要通過內(nèi)核配置來指定,我看了一下我系統(tǒng)中Linux目錄下的.config文件,找到了下面這行:- <linux-3.1.6/.config>
- #
- # Generic Driver Options
- #
- CONFIG_UEVENT_HELPER_PATH=""
復(fù)制代碼 丫的,居然沒指定,那么uevent_helper[0]="",這樣的話我們在kobject_uevent_env函數(shù)中的那個if語句就沒法滿足了,看來要重新配置再編譯內(nèi)核了。不過想想sysfs這么強大,內(nèi)核開發(fā)的那幫人好歹給留個用戶空間的接口出來吧,一查看還真有:
<kernel/ksysfs.c>- static ssize_t uevent_helper_store(struct kobject *kobj,
- struct kobj_attribute *attr,
- const char *buf, size_t count)
- {
- if (count+1 > UEVENT_HELPER_PATH_LEN)
- return -ENOENT;
- memcpy(uevent_helper, buf, count);
- uevent_helper[count] = '\0';
- if (count && uevent_helper[count-1] == '\n')
- uevent_helper[count-1] = '\0';
- return count;
- }
復(fù)制代碼 尼瑪,爽得簡直是一塌糊涂,雖然俺那臺馬力強勁的機器編個全新的內(nèi)核不過幾分鐘的事情,但是哪里有上面這個方法爽啊。馬上進入到/sys/kernel目錄下ls一把,截屏如下(點擊放大):
uevent1.png (8 KB, 下載次數(shù): 677)
下載附件
2012-03-03 18:09 上傳
有個uevent_helper文件不是?那么我們現(xiàn)在可以把我們用戶空間的程序給打進去了,我打算做個最簡單的腳本/sbin/myhotplug,這個腳本只干一件事,在/home/dennis目錄下生成一個hotplug文件:
</sbin/myhotplug>- #!/bin/sh
- cd /home/dennis
- touch hotplug
復(fù)制代碼 然后把這個腳本程序的文件名給打入到內(nèi)核空間的uevent_helper[0]上:- root@build-server:/sys/kernel# echo "/sbin/myhotplug" > uevent_helper
- root@build-server:/sys/kernel# cat uevent_helper
- /sbin/myhotplug
復(fù)制代碼 好了,現(xiàn)在檢查一下你的/home/dennis目錄下面有沒有hotplug這個文件,有的話就刪掉,否則怎么知道是新生成的呢,F(xiàn)在,找個U盤插到你的電腦里,然后再看一下/home/dennis目錄,有個hotplug文件對吧?如果你現(xiàn)在刪除這個文件,再把U盤給拔了,你會再次發(fā)現(xiàn)這個文件。這意味著什么,意味著你可以輕而易舉地捕捉到設(shè)備加入/移出系統(tǒng)等事件,如果你的腳本足夠智能,那么你就會想到很多很有創(chuàng)意的玩法對吧?
最后,對于PCI設(shè)備而言,Linux系統(tǒng)在啟動過程中會掃描系統(tǒng)中所有PCI設(shè)備,對發(fā)現(xiàn)的每一個設(shè)備都會調(diào)用device_add函數(shù),正如你前面看到的那樣,udev將會被通知,它負責找到對應(yīng)的驅(qū)動模塊并加載。當然,如果你愿意,你也可以去捕捉這些事件。
(原文首發(fā):http://www.embexperts.com/forum.php?mod=viewthread&tid=551&page=1&extra=#pid4649,略有改動) |
|