歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

linux下熱插拔事件的產生是怎樣通知到用戶空間,kobject_uevent_env之uevent_helper

熱插拔(hotplug,打這個詞的時候我常常想到熱干面)不一定非要指類似U盤那樣的插入拔出,此處的熱插拔廣義上講,是指一個設備加入系統,內核如何通知用戶空間。舉個簡單的例子,如果你的電腦中有塊PCI網卡,針對該網卡的驅動程序以內核模塊的形式被編譯(obj-m),那麼Linux系統在啟動過程中是如何自動加載該網卡的驅動模塊呢?大家都知道現在udev負責干這事,其實除了udev,還可以有其他的手法,你自己就可以這樣做。

我們先討論udev,udev最關鍵的東西是當系統發現一個設備時,它要能夠被通知該事件,一旦它知道了這件事,那麼余下的事情就都好說了,無非是個如何查找模塊並加載的過程。所以我們看到,這裡的關鍵是熱插拔事件的通知機制。Linux的設備模型為此提供了非常完美的支持,其原理其實發源於kset這一層,對此在《深入Linux設備驅動程序內核機制》一書中有詳細的描述,雖然這部分看起來蠻復雜,貌似挺能嚇唬住一些新手,其實說白了,要點就是通過sysfs建立關系,溝通內核與用戶空間,然後就是uevent,也就是下面要說的熱插拔事件。

當然設備驅動程序一般不會和這些太底層的kobject/kset家伙打交道,因為更高層次的device,bus和driver把kobject/kset那一層的細節實現都給封裝了起來。所以設備熱插拔的uevent事件最終的源頭來自於device_add,本帖這裡肯定不會討論device與driver如何綁定那一攤子事情。下面看看device_add的源碼,是如何實現uevent機制的:

<drivers/base/core.c>

int device_add(struct device *dev)

{

...

kobject_uevent(&dev->kobj, KOBJ_ADD);

...

}

復制代碼

熱插拔的核心實現就那一個函數調用,這裡device_add對應的是KOBJ_ADD,那麼移除設備自然對應KOBJ_REMOVE了。kobject_uevent函數最終調用的是kobject_uevent_env,後者才是真正干事的伙計。

下面給出kobject_uevent_env函數的核心框架:

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);

}

...

}

復制代碼

怎麼樣,夠簡潔吧,其實看實際的代碼比這要郁悶地多,不過骨架清晰就行了。代碼中的netlink message就不用多說了吧,給udev發通知用(有時間的話可以分析分析udev的代碼)。本帖重點討論後半段的if

(uevent_helper[0] && !kobj_usermode_filter(kobj))代碼,這裡的核心調用是call_usermodehelper,這個函數最有意思的地方就在於在內核空間調用用戶空間的程序,它的詳細實現機制在書中已經講得很多,這裡就不再贅述了。call_usermodehelper在kobject_uevent_env函數中要調用的用戶空間程序由uevent_helper[0]來指定,所以如果我們能控制這個uevent_helper[0],就能接收到設備加入系統移出系統等事件。那個if中的kobj_usermode_filter條件一般都會滿足(除非這是個特別注意個人隱私的設備,那就不好說了,人家偷偷加入系統就是不想讓你知道你也沒有辦法,但是udev還是能知道的)。

下面看看uevent_helper[0]來自何處:

<lib/kobject_uevent.c>

char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;

復制代碼

貌似要通過內核配置來指定,我看了一下我系統中Linux目錄下的.config文件,找到了下面這行:

<linux-3.1.6/.config>

#

# Generic Driver Options

#

CONFIG_UEVENT_HELPER_PATH=""

復制代碼

丫的,居然沒指定,那麼uevent_helper[0]="",這樣的話我們在kobject_uevent_env函數中的那個if語句就沒法滿足了,看來要重新配置再編譯內核了。不過想想sysfs這麼強大,內核開發的那幫人好歹給留個用戶空間的接口出來吧,一查看還真有:

<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;

}

復制代碼

尼瑪,爽得簡直是一塌糊塗,雖然俺那台馬力強勁的機器編個全新的內核不過幾分鐘的事情,但是哪裡有上面這個方法爽啊。馬上進入到/sys/kernel目錄下ls一把,截屏如下(點擊放大):

有個uevent_helper文件不是?那麼我們現在可以把我們用戶空間的程序給打進去了,我打算做個最簡單的腳本/sbin/myhotplug,這個腳本只干一件事,在/home/dennis目錄下生成一個hotplug文件:

</sbin/myhotplug>

#!/bin/sh

cd /home/dennis

touch hotplug

復制代碼

然後把這個腳本程序的文件名給打入到內核空間的uevent_helper[0]上:

root@build-server:/sys/kernel# echo "/sbin/myhotplug" > uevent_helper

root@build-server:/sys/kernel# cat uevent_helper

/sbin/myhotplug

復制代碼

好了,現在檢查一下你的/home/dennis目錄下面有沒有hotplug這個文件,有的話就刪掉,否則怎麼知道是新生成的呢。現在,找個U盤插到你的電腦裡,然後再看一下/home/dennis目錄,有個hotplug文件對吧?如果你現在刪除這個文件,再把U盤給拔了,你會再次發現這個文件。這意味著什麼,意味著你可以輕而易舉地捕捉到設備加入/移出系統等事件,如果你的腳本足夠智能,那麼你就會想到很多很有創意的玩法對吧?

最後,對於PCI設備而言,Linux系統在啟動過程中會掃描系統中所有PCI設備,對發現的每一個設備都會調用device_add函數,正如你前面看到的那樣,udev將會被通知,它負責找到對應的驅動模塊並加載。當然,如果你願意,你也可以去捕捉這些事件。

Copyright © Linux教程網 All Rights Reserved