轉自:https://blog.csdn.net/W1107101310/article/details/80211885
簡介:
本文主要介紹uevent機制是什么,並通過代碼分析使用uevent機制生成設備節點的過程。而本文將分為兩部分,第一部分我們介紹一些預備知識和uevent的原理,而第二部分——通過代碼介紹使用uevent機制創建設備節點。
Linux內核:linux-2.6.22.6
所用開發板:JZ2440 V3(S3C2440A)
聲明:
本文主要是看完韋東山老師視頻並結合一些博客內容所寫,因此文中可能會有其他文章中的內容,如果你覺得我的文章對你構成了侵犯,您可以告訴我,我會對文章進行改正,同時如果文中可能有不正確的地方,敬請指正。謝謝。
第一部分:預備知識和uevent的原理
下面我們開始講解預備知識。在講解之前,我們先來看一個介紹uevent機制的框架圖:
該圖片來自:Linux設備模型(3)_Uevent
我們很多人可能不清楚:uevent機制是什么,uevent機制到底做了什么工作?他的那些方面是值得我們研究的?
我們在剛學習驅動程序的時候並沒有使用uevent機制,也就是說我們在程序中沒有用到class_create和class_device_create函數,來自動的在用戶空間為設備驅動創建設備節點。那時候我們要手動的在用戶空間使用mknod命令來創建設備節點。而當我們使用class_create和class_device_create函數后,他們會為我們在用戶空間創建設備節點而不用我們再手動的去完成這項工作。而這就是我們對於uevent機制的宏觀認識。而我們知道我們的設備節點是為設備驅動所創建的,而設備device和驅動driver都是以鏈表的形式連接在總線bus上的,而設備——驅動——總線的更上一層就是sysfs層。因此這就引出了我們就要介紹一個組合了:sysfs+mdev。而這對組合將為我們解釋uevent機制的原理。我們先來了解sysfs。
sysfs是一個基於內存的虛擬文件系統,有kernel提供,掛載到/sys 目錄下(用mount查看得到 sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)),負責以設備樹的形式向user namespace提供直觀的設備和驅動信息。同時sysfs以不同的視角為我們展示當前系統接入的設備:
- /sys/block 歷史遺留問題,存放塊設備,提供以設備名(如sda)到/sys/devices的符號鏈接
- /sys/bus 按總線類型分類,在某個總線目錄之下可以找到連接該總線的設備的符號鏈接,指向/sys/devices。某個總線目錄之下的 drivers 目錄包含了該總線所需的所有驅動的符號鏈接對應kernel中的 struct bus_type
- /sys/class 按設備功能分類,如輸入設備在 /sys/class/input 之下,圖形設備在 /sys/class/graphics 之下,是指向 /sys/devices 目錄下對應設備的符號鏈接對應kernel中的 struct class
- /sys/dev 按設備驅動程序分層(字符設備/塊設備),提供以major:minor為名到 /sys/devices 的符號鏈接對應kernel中的 struct device_driver
- /sys/devices 包含所有被發現的注冊在各種總線上的各種物理設備。所有的物理設備都按其在總線上的拓撲結構來顯示,除了 platform devices 和 system devices 。platform devices一般是掛在芯片內部高速或者低速總線上的各種控制器和外設,能被CPU直接尋址。system devices不是外設,他是芯片內部的核心結構,比如CPU,timer等,他們一般沒有相關的driver,但是會有一些體系結構相關的代碼來配置他們對應kernel中的 struct device
上面展現了在sys目錄下總線,設備,驅動和類所對應的文件,而他們的關系為:
- device用於描述各種設備,其保存了所有的設備信息
- driver 用於驅動 device ,其保存了所有能夠被它所驅動的設備鏈表。
- bus 是連接 CPU 和 device 的橋梁,其保存了所有掛載在它上面的設備鏈表和驅動這些設備的驅動鏈表。
- class 用於描述一類 device ,其保存了所有該類 device 的設備鏈表。
下面我們介紹總線,設備,驅動和類更下一層的結構體。sysfs的功能基於Linux的統一設備模型,他有以下結構體構成:kobject,kset,ktype。同時我們從上面框圖中可以看出uevent是在kobject結構體的基礎上實現的。
kobject:統一設備模型中最基本的對象。
-
struct kobject {
-
const char *name; //name,該Kobject的名稱,同時也是sysfs中的目錄名稱。
-
//由於Kobject添加到Kernel時,需要根據名字注冊到sysfs中,之后就不能再直接修改該字段。
-
//如果需要修改Kobject的名字,需要調用kobject_rename接口,該接口會主動處理sysfs的相關事宜。
-
struct list_head entry; //entry,用於將Kobject加入到Kset中的list_head。
-
struct kobject *parent; //parent,指向parent kobject,以此形成層次結構(在sysfs就表現為目錄結構)。
-
struct kset *kset; //kset,該kobject屬於的Kset。可以為NULL。
-
//如果存在,且沒有指定parent,則會把Kset作為parent
-
//(別忘了Kset是一個特殊的Kobject)。
-
struct kobj_type *ktype; //ktype,該Kobject屬於的kobj_type。每個Kobject必須有一個ktype,或者Kernel會提示錯誤。
-
struct sysfs_dirent *sd; //sd,該Kobject在sysfs中的表示。
-
-
struct kref kref; //kref,"struct kref”類型(在include/linux/kref.h中定義)的變量,為一個可用於原子操作的引用計數。
-
unsigned int state_initialized:1; //state_initialized,指示該Kobject是否已經初始化,
-
//以在Kobject的Init,Put,Add等操作時進行異常校驗。
-
unsigned int state_in_sysfs:1; //state_in_sysfs,指示該Kobject是否已在sysfs中呈現,以便在自動注銷時從sysfs中移除。
-
unsigned int state_add_uevent_sent:1; // state_add_uevent_sent/state_remove_uevent_sent,記錄是否已經向用戶空間發送ADD uevent,
-
//如果有,且沒有發送remove uevent,則在自動注銷時,補發REMOVE uevent,
-
//以便讓用戶空間正確處理。
-
unsigned int state_remove_uevent_sent:1;
-
unsigned int uevent_suppress:1; //uevent_suppress,如果該字段為1,則表示忽略所有上報的uevent事件。
-
};
注:Uevent提供了“用戶空間通知”的功能實現,通過該功能,當內核中有Kobject的增加、刪除、修改等動作時,會通知用戶空間。
Ktype:代表Kobject(嚴格地講,是包含了Kobject的數據結構)的屬性操作集合(由於通用性,多個Kobject可能共用同一個屬性操作集,因此把Ktype獨立出來了)。
-
struct kobj_type {
-
void (*release)(struct kobject *kobj); //release,通過該回調函數,可以將包含該種類型kobject的數據結構的內存空間釋放掉。
-
const struct sysfs_ops *sysfs_ops; //sysfs_ops,該種類型的Kobject的sysfs文件系統接口。
-
struct attribute **default_attrs; //default_attrs,該種類型的Kobject的atrribute列表
-
//(所謂attribute,就是sysfs文件系統中的一個文件)。
-
//將會在Kobject添加到內核時,一並注冊到sysfs中。
-
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
-
//child_ns_type/namespace,和文件系統(sysfs)的命名空間有關
-
const void *(*namespace)(struct kobject *kobj);
-
};
實際上這里實現的類似於對 kobject 的派生,包含不同 kobj_type 的kobject 可以看做不同的子類。通過實現相同的函數來實現多態。在這樣的設計下,每一個內嵌Kobject的數據結構(如kset、device、device_driver等),都要實現自己的 kobj_type ,並定義其中的回調函數。
Kset:一個特殊的Kobject(因此它也會在"/sys/“文件系統中以目錄的形式出現),它用來集合相似的Kobject(這些Kobject可以是相同屬性的,也可以不同屬性的)。
-
struct kset {
-
struct list_head list; //list用於保存該kset下所有的kobject的鏈表。
-
spinlock_t list_lock; //list自旋鎖
-
struct kobject kobj; //kobj,該kset自己的kobject(kset是一個特殊的kobject,也會在sysfs中以目錄的形式體現)。
-
const struct kset_uevent_ops *uevent_ops; //uevent_ops,該kset的uevent操作函數集。
-
//當任何Kobject需要上報uevent時,都要調用它所從屬的kset的uevent_ops,添加環境變量,
-
//或者過濾event(kset可以決定哪些event可以上報)。
-
//因此,如果一個kobject不屬於任何kset時,是不允許發送uevent的。
-
};
而kset的uevent操作回調函數為:
-
struct kset_uevent_ops {
-
int (* const filter)(struct kset *kset, struct kobject *kobj);
-
//filter,當任何Kobject需要上報uevent時,它所屬的kset可以通過該接口過濾,
-
//阻止不希望上報的event,從而達到從整體上管理的目的。
-
const char *(* const name)(struct kset *kset, struct kobject *kobj);
-
//name,該接口可以返回kset的名稱。如果一個kset沒有合法的名稱,
-
//則其下的所有Kobject將不允許上報uvent
-
int (* const uevent)(struct kset *kset, struct kobject *kobj,
-
struct kobj_uevent_env *env);
-
//uevent,當任何Kobject需要上報uevent時,它所屬的kset可以通過該接口統一為這些event添加環境變量。
-
//因為很多時候上報uevent時的環境變量都是相同的,因此可以由kset統一處理,就不需要讓每個Kobject獨自添加了。
-
};
注意Kset和 ktype 的關聯,kobject 會利用成員 kset 找到自已所屬的kset,然后才設置自身的 ktype 為 kobj.ktype 。當沒有指定 kset 成員時,才會用 ktype 來建立關系。
由於 kobject 調用的是它所屬 kset 的uevent操作函數,所以 kset 可以對其行為進行控制。如果kobject不屬於任何 kset ,則無法發送uevent。
總結,Ktype以及整個Kobject機制的理解。
Kobject的核心功能是:保持一個引用計數,當該計數減為0時,自動釋放(由本文所講的kobject模塊負責) Kobject所占用的meomry空間。這就決定了Kobject必須是動態分配的(只有這樣才能動態釋放)。
而Kobject大多數的使用場景,是內嵌在大型的數據結構中(如Kset、device_driver等),因此這些大型的數據結構,也必須是動態分配、動態釋放的。那么釋放的時機是什么呢?是內嵌的Kobject釋放時。但是Kobject的釋放是由Kobject模塊自動完成的(在引用計數為0時),那么怎么一並釋放包含自己的大型數據結構呢?
這時Ktype就派上用場了。我們知道,Ktype中的release回調函數負責釋放Kobject(甚至是包含Kobject的數據結構)的內存空間,那么Ktype及其內部函數,是由誰實現呢?是由上層數據結構所在的模塊!因為只有它,才清楚Kobject嵌在哪個數據結構中,並通過Kobject指針以及自身的數據結構類型,找到需要釋放的上層數據結構的指針,然后釋放它。
講到這里,就清晰多了。所以,每一個內嵌Kobject的數據結構,例如kset、device、device_driver等等,都要實現一個Ktype,並定義其中的回調函數。同理,sysfs相關的操作也一樣,必須經過ktype的中轉,因為sysfs看到的是Kobject,而真正的文件操作的主體,是內嵌Kobject的上層數據結構!
順便提一下,Kobject是面向對象的思想在Linux kernel中的極致體現,但C語言的優勢卻不在這里,所以Linux kernel需要用比較巧妙(也很啰嗦)的手段去實現。
mdev原理
上面我們分析了sysfs,下面我們就開始分析mdev,我們通過分析mdev了解他與sysfs的關系。mdev在busybox的代碼包中能找到,位於busybox/util-linux/mdev.c文件中,他通過uevent_helper函數被調用。在mdev中主要完成兩件事情:
第一件事:
執行mdev -s命令時,mdev掃描/sys/block(塊設備保存在/sys/block目錄下,內核2.6.25版本以后,塊設備也保存在/sys /class/block目錄下。mdev掃描/sys/block是為了實現向后兼容)和/sys/class兩個目錄下的dev屬性文件,從該dev 屬性文件中獲取到設備編號(dev屬性文件以”major:minor\n”形式保存設備編號),並以包含該dev屬性文件的目錄名稱作為設備名 device_name(即包含dev屬性文件的目錄稱為device_name,而/sys/class和device_name之間的那部分目錄稱為 subsystem。也就是每個dev屬性文件所在的路徑都可表示為/sys/class/subsystem/device_name/dev),在 /dev目錄下創建相應的設備文件。例如,cat /sys/class/tty/tty0/dev會得到4:0,subsystem為tty,device_name為tty0。
第二件事:
當mdev因uevnet事件(以前叫hotplug事件)被調用時,mdev通過由uevent事件傳遞給它的環境變量獲取到:引起該uevent 事件的設備action及該設備所在的路徑device path。然后判斷引起該uevent事件的action是什么。並根據action的不同做相應操作。若該action是add,即有新設備加入到系統中,不管該設備是虛擬設備還是實際物理設備,mdev都會通過device path路徑下的dev屬性文件獲取到設備編號,然后以device path路徑最后一個目錄(即包含該dev屬性文件的目錄)作為設備名,在/dev目錄下創建相應的設備文件。若該action是remote,即設備已 從系統中移除,則刪除/dev目錄下以device path路徑最后一個目錄名稱作為文件名的設備文件。如果該action既不是add也不是remove,mdev則什么都不做。
由上面可知,如果我們想在設備加入到系統中或從系統中移除時,由mdev自動地創建和刪除設備文件,那么就必須做到以下三點:
1. 在/sys/class 的某一subsystem目錄下,
2. 創建一個以設備名device_name作為名稱的目錄,
3. 並且在該device_name目錄下還必須包含一個 dev屬性文件,該dev屬性文件以”major:minor\n”形式輸出設備編號。
而從上面這些內容我們可以知道,sysfs為uevent機制做前期的准備工作,即創建相應的目錄,而mdev則是在sysfs的基礎上通過調用sysfs創建的目錄或文件來實現設備節點的創建。
第二部分:結合代碼介紹使用uevent機制創建設備節點
現在我們就要結合代碼分析uevent機制了,而要分析這個機制我們就要從class_create和class_device_create這兩個函數來分析這個過程是怎么實現的。我們現在先分析class_create:
下面是class_create函數的層級關系:
-
class_create(THIS_MODULE, "buttonsdrv");
-
class_register(cls);
-
kobject_set_name(&cls->subsys.kobj, "%s", cls->name); //將類的名字led_class賦值給對應的kset
-
subsys_set_kset(cls, class_subsys);
-
subsystem_register(&cls->subsys);
-
kset_register(s); //創建class設備類目錄
-
kset_add(k);
-
kobject_add(&k->kobj);
-
kobject_shadow_add(kobj, NULL);
-
parent = kobject_get(kobj->parent); // parent即class_kset.kobj,即/sysfs/class對應的目錄
-
list_add_tail(&kobj->entry,&kobj->kset-> list);
-
create_dir(kobj, shadow_parent); //創建一個class設備類目錄
-
sysfs_create_dir(kobj, shadow_parent); //該接口是sysfs文件系統接口,代表創建一個目錄,不再展開。
從上面我們可以看出kobject在sysfs中對應的是目錄(dir),當我們注冊一個kobject時,會調用kobject_add(&k->kobj);然后在其后創建class設備目錄。而同時我們可以看出class_create函數是為class_device_create函數做了目錄的准工作。
而從文章開始介紹的圖片我們知道當kobject的狀態發生改變(如,add, remove等)時,會通知用戶空間,用戶空間接收到事件通知后可以做相應的處理。
而uevent把事件上報給用戶空間有兩種途徑:
1.通過kmod模塊,直接調用用戶空間的可執行程序或腳本。
2.通過netlink通信機制,將事件從內核空間傳遞到用戶空間。
而本文主要講解通過kmod模塊,直接調用用戶空間的可執行程序或腳本。而通過kobject.h,uevent模塊提供了如下的API(這些API的實現是在"lib/kobject_uevent.c”文件中):
-
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
-
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
-
char *envp[]);
-
-
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
-
-
int kobject_action_type(const char *buf, size_t count,enum kobject_action *type);
下面我們從class_device_create函數開始分析,看他是如何走到kobject_uevent函數的。我們看class_device_create函數的層級關系:
-
class_device_create(buttonsdrv_class, NULL,MKDEV(auto_major,0),NULL,"buttonsdrv");
-
class_device_register(class_dev);
-
class_device_add(class_dev);
-
class_dev = class_device_get(class_dev);
-
parent_class = class_get(class_dev->class);
-
parent_class_dev = class_device_get(class_dev->parent);
-
kobject_set_name(&class_dev->kobj, "%s", class_dev->class_id);
-
kobject_add(&class_dev->kobj);
-
class_device_create_file(class_dev, attr);
-
class_device_add_groups(class_dev);
-
make_deprecated_class_device_links(class_dev);
-
kobject_uevent(&class_dev->kobj, KOBJ_ADD);
從前面的代碼看class_create和class_device_create做了很多相似的工作——就是創建目錄,而當到kobject_uevent函數后,他們就不一樣了,下面我們分析kobject_uevent函數
-
/**
-
* 通過終端事件通知用戶層
-
*
-
* @action: 發生的事件 (通常是 KOBJ_ADD 和 KOBJ_REMOVE)
-
* @kobj: 事件發生的kobject 結構體
-
*
-
*/
-
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
-
{
-
return kobject_uevent_env(kobj, action, NULL);
-
}
他調用了kobject_uevent_env函數,而上面的介紹中我們知道了要發送事件,那么都有什么事件那?
我們看linux-3.5/include/linux/kobject.h
-
enum kobject_action {
-
KOBJ_ADD, //ADD/REMOVE,Kobject(或上層數據結構)的添加/移除事件。
-
KOBJ_REMOVE,
-
KOBJ_CHANGE, //CHANGE,Kobject(或上層數據結構)的狀態或者內容發生改變。
-
//CHANGE,如果設備驅動需要上報的事件不再上面事件的范圍內,
-
//或者是自定義的事件,可以使用該event,並攜帶相應的參數。
-
KOBJ_MOVE, //MOVE,Kobject(或上層數據結構)更改名稱或者更改Parent(意味着在sysfs中更改了目錄結構)。
-
KOBJ_ONLINE, //ONLINE/OFFLINE,Kobject(或上層數據結構)的上線/下線事件,其實是是否使能。
-
KOBJ_OFFLINE,
-
KOBJ_MAX
-
};
下面我們接着分析kobject_uevent_env函數:
-
/**
-
* 發送一個帶有環境變量的事件
-
*
-
* @action: 發生的事件(通常為KOBJ_MOVE)
-
* @kobj: 事件發生的kobject結構體
-
* @envp_ext: 環境變量數據指針
-
*
-
*/
-
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
-
char *envp_ext[])
-
{
-
action_string = action_to_string(action);
-
-
/* 查找當前kobject或其parent是否從屬於某個kset;如果都不從屬於某個kset,則返回錯誤。(說明一個kobject若沒有加入kset,是不會上報uevent的) */
-
top_kobj = kobj;
-
while (!top_kobj->kset && top_kobj->parent) {
-
top_kobj = top_kobj->parent;
-
}
-
if (!top_kobj->kset) {
-
pr_debug( "kobject attempted to send uevent without kset!\n");
-
return -EINVAL;
-
}
-
-
kset = top_kobj->kset;
-
uevent_ops = kset->uevent_ops;
-
-
/* 如果所屬的kset有uevent_ops->filter,則調用該函數,若該函數返回0,則過濾此次上報。(kset 可以通過filter接口過濾不希望上報的event) */
-
if (uevent_ops && uevent_ops->filter)
-
if (!uevent_ops->filter(kset, kobj)) {
-
pr_debug( "kobject filter function caused the event to drop!\n");
-
return 0;
-
}
-
-
/*判斷所屬的kset是否有合法的名稱,若uevent_ops->name存在就用其返回的名稱作為subsystem;若uevent_ops->name不存在就用kset本身的kobject的名稱作為subsystem;若沒有合法的名稱,則不上報uevent */
-
if (uevent_ops && uevent_ops->name)
-
subsystem = uevent_ops->name(kset, kobj);
-
else
-
subsystem = kobject_name(&kset->kobj);
-
if (!subsystem) {
-
pr_debug( "unset subsytem caused the event to drop!\n");
-
return 0;
-
}
-
-
/* 分配一個此次上報的環境變量 */
-
envp = kzalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL);
-
if (!envp)
-
return -ENOMEM;
-
-
/*分配一個此次上報的用於保存環境變量的buffer, */
-
buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
-
if (!buffer) {
-
retval = -ENOMEM;
-
goto exit;
-
}
-
-
/* 獲得該kobject在sysfs中路徑 */
-
devpath = kobject_get_path(kobj, GFP_KERNEL);
-
if (!devpath) {
-
retval = -ENOENT;
-
goto exit;
-
}
-
-
/* uevent_helper的環境變量*/
-
envp[i++] = "HOME=/";
-
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
-
-
/* 添加環境變量 */
-
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;
-
for (j = 0; envp_ext && envp_ext[j]; j++)
-
envp[i++] = envp_ext[j];
-
/* just reserve the space, overwrite it after kset call has returned */
-
envp[i++] = seq_buff = scratch;
-
scratch += strlen("SEQNUM=18446744073709551616") + 1;
-
-
/* 如果 uevent_ops->uevent 存在,調用該接口,添加kset統一的環境變量到env指針 */
-
if (uevent_ops && uevent_ops->uevent) {
-
retval = uevent_ops->uevent(kset, kobj,
-
&envp[i], NUM_ENVP - i, scratch,
-
BUFFER_SIZE - (scratch - buffer));
-
if (retval) {
-
pr_debug ( "%s - uevent() returned %d\n",
-
__FUNCTION__, retval);
-
goto exit;
-
}
-
}
-
-
/* 調用add_uevent_var接口,添加格式為"SEQNUM=%llu”的序列號 */
-
spin_lock(&sequence_lock);
-
seq = ++uevent_seqnum;
-
spin_unlock(&sequence_lock);
-
sprintf(seq_buff, "SEQNUM=%llu", (unsigned long long)seq);
-
-
-
/* 以uevent_helper、 subsystem 以及添加了標准環境變量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指針為參數,調用kmod模塊提供的call_usermodehelper函數,上報uevent。 */
-
if (uevent_helper[0]) {
-
char *argv [3];
-
-
argv [ 0] = uevent_helper;
-
argv [ 1] = (char *)subsystem;
-
argv [ 2] = NULL;
-
call_usermodehelper (argv[ 0], argv, envp, 0);
-
}
-
}
uevent模塊通過kmod上報uevent時,會通過call_usermodehelper函數,調用用戶空間的可執行文件(或者腳本,簡稱uevent helper)處理該event。而該uevent helper的路徑保存在uevent_helper數組中。可以在編譯內核時,通過CONFIG_UEVENT_HELPER_PATH配置項,靜態指定uevent helper。
但這種方式會為每個event fork一個進程,隨着內核支持的設備數量的增多,這種方式在系統啟動時將會是致命的(可以導致內存溢出等)。因此只有在早期的內核版本中會使用這種方式,現在內核不再推薦使用該方式。因此內核編譯時,需要把該配置項留空。在系統啟動后,大部分的設備已經ready,可以根據需要,重新指定一個uevent helper,以便檢測系統運行過程中的熱拔插事件。
這可以通過把helper的路徑寫入到"/sys/kernel/uevent_helper"文件中實現。實際上,內核通過sysfs文件系統的形式,將uevent_helper數組開放到用戶空間,供用戶空間程序修改訪問,具體可參考"./kernel/ksysfs.c”中相應的代碼。
在/etc/init.d/rcS腳本中添加 echo "/sbin/mdev" > /proc/sys/kernel/hotplug,會發現cat /sys/kernel/uevent_helper 即是/sbin/mdev。說明/proc/sys/kernel/hotplug中的可執行文件路徑最終還是會寫到/sys/kernel/uevent_helper中。自己手動echo "/kernel/main" > uevent_helper(之前的/sbin/mdev會被覆蓋),當lsmod、rmmod時,/sys/kernel/uevent_helper中的/kernel/main會執行,表明事件已經上報給用戶空間。
下面我們看在Busybox中是如何創建設備節點的。
輪到mdev出場了,前面的描述都是在sysfs文件系統中創建目錄或者文件,而應用程序訪問的設備文件則需要創建在/dev/目錄下。該項工作由mdev完成。
mdev的原理是解釋/etc/mdev.conf文件定義的命名設備文件的規則,並在該規則下根據環境變量的要求來創建設備文件。mdev.conf由用戶層指定,因此更具靈活性。本文無意展開對mdev配置腳本的分析。相關知識可以看我的翻譯:mdev.conf翻譯
mdev相應的程序在Busybox/util-linux/mdev.c
-
int mdev_main(int argc UNUSED_PARAM, char **argv)
-
xchdir("/dev");
-
if (argv[1] && strcmp(argv[1], "-s")//系統啟動時mdev –s才會執行這個分支
-
else
-
action = getenv( "ACTION");
-
env_path = getenv( "DEVPATH");
-
G.subsystem = getenv( "SUBSYSTEM");
-
snprintf(temp, PATH_MAX, "/sys%s", env_path);//到/sysfs/devices/led目錄
-
make_device(temp, /*delete:*/ 0);
-
strcpy(dev_maj_min, "/dev"); //讀出dev屬性文件,得到設備號
-
open_read_close(path, dev_maj_min + 1, 64);
-
….
-
mknod(node_name, rule->mode | type, makedev(major, minor)) //最終mknod創建節點
最終我們會跟蹤到mknod在/dev/目錄下創建了設備文件。
參考文獻:
sysfs、udev 和 它們背后的 Linux 統一設備模型
Linux設備模型(2)_Kobject
Linux 設備文件的創建和mdev
內核發送uevent的API,用戶空間解析uevent
Linux設備模型(3)_Uevent
設備模型的uevent機制