- 一、概述
- (1)udev是構建在linux的sysfs之上的是一個一個用戶程序,它能夠根據系統中的硬件設備的狀態動態更新設備文件,包括設備文件的創建,刪除等,設備文件通常放在/dev目錄下。使用udev後,在/dev目錄下就只包含系統中真正存在的設備。udev的的工作過程大致是這樣的:
- 1. 當內核檢測到在系統中出現了新設備後,內核會在sysfs文件系統中爲該新設備生成一項新的記錄,新記錄是以文件或目錄的方式來表示,每個文件都包含有特定的信息。
- 2. udev在系統中是以守護進程的方式運行,它通過某種途徑檢測到新設備的出現,通過查找設備對應的sysfs中的記錄得到設備的信息。
- 3. udev會根據/etc/udev/udev.conf文件中的udev_rules指定的目錄,逐個檢查該目錄下的文件,這個目錄下的文件都是針對某類或某個設備應該施行什麼措施的規則文件。udev讀取文件是按照文件名的ASCII字母順序來讀取的,如果udev一旦找到了與新加入的設備匹配的規則,udev就會根據規則定義的措施對新設備進行配置。同時不再讀後續的規則文件。
- (2)udev的工作可以簡單的概括爲:監控系統中設備狀態的變化,當有設備的狀態發生變化時,根據用戶的配置對設備文件執行相應的操作。
- (3)udev的配置規則
- udev的全局的配置文件是/etc/udev/udev.conf,該文件一般缺省有這樣幾項:
- udev_root="/dev" #udev產生的設備文件的根目錄是/dev
- udev_rules="/etc/udev/rules.d" #用於指導udev工作的規則所在目錄。
- udev_log="err" #當出現錯誤時,用syslog記錄錯誤信息。
- 其中最關鍵的就是規則文件,即/etc/udev/rules.d/目錄下的文件,udev是按照文件名的ASCII字母順序來讀取的,一旦找到與設備匹配的規則,就運用該規則,並不再讀後續的規則文件了。下面簡單看下udev定義了那些規則,以及它是如何進行配置的,怎樣去編寫這種規則文件。
- udev的規則文件以行爲單位,以"#"開頭的行代表註釋行。其餘的每一行代表一個規則。每個規則分成一個或多個“匹配”和“賦值”部分。“匹配”部分用“匹配“專用的關鍵字來表示,相應的“賦值”部分用“賦值”專用的關鍵字來表示。“匹配”關鍵字包括:ACTION,KERNEL,BUS, SYSFS等等,“賦值”關鍵字包括:NAME,SYMLINK,OWNER等等。具體詳細的描述可以閱讀udev的man文檔。下面通過一個具體的例子來說明,看如下的規則:
- # PCI device 0x8086:0x1096 (e1000e)
- SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:19:21:ff:cc:7c", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
- 這個規則中的“匹配”部分有四項,分別是SUBSYSTEM,ACTION,DRIVERS,KERNEL。而"賦值"部分有一項,是NAME。這個規則就是說,當系統中出現的新硬件屬於net子系統範疇,系統對該硬件採取的動作是加入這個硬件,且這個硬件在DRIVERS信息中的address="00:19:21:ff:cc:7c",dev_id="0x0",內核的命名爲eth*時,udev會在建立新的網絡設備,並命名爲eth0。udev規則文件的編寫可以參考man手冊。
- 二、udev自動生成設備節點
- 生成設備文件節點的方法有三個:1.手動mknod 2.利用devfs 3.利用udev
- udev是硬件平臺無關的,屬於user space的進程,它脫離驅動層的關聯而建立在操作系統之上,基於這種設計實現,我們可以隨時修改及刪除/dev下的設備文件名稱和指向,隨心所欲地按照我們的願望安排和管理設備文件系統,而完成如此靈活的功能只需要簡單地修改udev的配置文件即可,無需重新啓動操作系統。udev已經使得我們對設備的管理如探囊取物般輕鬆自如。
- 內核中定義了struct class 結構體,顧名思義,一個struct class 結構體類型變量對應一個類,內核同時提供了class_create() 函數,可以用它來創建一個類,這個類存放於sysfs 下面,一旦創建好了這個類,再調用device_create() 函數來在/dev目錄下創建相應的設備節點。這樣,加載模塊的時候,用戶空間中的udev會自動響應device_create() 函數,去/sysfs 下尋找對應的類從而創建設備節點。
- struct class {
- const char * name;
- struct module * owner;
- nbsp; struct kset subsys;
- struct list_head devices;
- struct list_head interfaces;
- struct kset class_dirs;
- struct semaphore sem; /* locks children, devices, interfaces */
- struct class_attribute * class_attrs;
- struct device_attribute * dev_attrs;
- int ( * dev_uevent) ( struct device * dev, struct kobj_uevent_env * env) ;
- void ( * class_release) ( struct class * class ) ;
- void ( * dev_release) ( struct device * dev) ;
- int ( * suspend) ( struct device * dev, pm_message_t state) ;
- int ( * resume) ( struct device * dev) ;
- } ;
- //第一個參數指定類的所有者是哪個模塊,第二個參數指定類名。
- struct class * class_create( struct module * owner, const char * name)
- {
- struct class * cls;
- int retval;
- cls = kzalloc( sizeof ( * cls) , GFP_KERNEL) ;//分配一個類結構
- if ( ! cls) {
- retval = - ENOMEM;
- goto error ;
- }
- cls- > name = name;
- cls- > owner = owner;
- cls- > class_release = class_create_release;
- retval = class_register( cls) ;//註冊這個類,即在/sys/class目錄下添加目錄
- if ( retval)
- goto error ;
- return cls;
- error :
- kfree( cls) ;
- return ERR_PTR( retval) ;
- }
- //第一個參數指定所要創建的設備所從屬的類,第二個參數是這個設備的父設備,如果沒有就指定爲NULL,第三個參數是設備號,第四個參數是設備名稱,第五個參數是從設備號。
- struct device * device_create( struct class * class , struct device * parent,dev_t devt, const char * fmt, . . . )
- {
- va_list vargs;
- struct device * dev;
- va_start ( vargs, fmt);
- //device_create_vargs函數流程:
- //device_create-->device_create_vargs-->device_register-->device_add-->kobject_uevent(&dev->kobj, KOBJ_ADD);-->
- //kobject_uevent_env(kobj, action, NULL);-->call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);}
- dev = device_create_vargs( class , parent, devt, NULL , fmt, vargs) ;
- va_end ( vargs) ;
- return dev;
- }
- 三、事件通知
- 在device_add()例程,其用於將一個device註冊到device model,其中調用了kobject_uevent(&dev->kobj, KOBJ_ADD)例程向用戶空間發出KOBJ_ADD 事件並輸出環境變量,以表明一個device被添加了。在《Linux設備模型淺析之設備篇》中介紹過rtc_device_register()例程 ,其最終調用device_add()例程添加了一個rtc0的device,我們就以它爲例子來完成uevent的分析。讓我們看看kobject_uevent()這個例程的代碼,如下:
- int kobject_uevent(struct kobject *kobj, enum kobject_action action)
- {
- return kobject_uevent_env(kobj, action, NULL);
- }
- 它又調用了kobject_uevent_env()例程,部分代碼如下:
- int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])
- {
- struct kobj_uevent_env *env;
- const char *action_string = kobject_actions[action]; // 本例是“add”命令
- const char *devpath = NULL;
- const char *subsystem;
- struct kobject *top_kobj;
- struct kset *kset;
- struct kset_uevent_ops *uevent_ops;
- u64 seq;
- int i = 0;
- int retval = 0;
- pr_debug("kobject: '%s' (%p): %s\n",kobject_name(kobj), kobj, __func__);
- top_kobj = kobj;
- /* 找到其所屬的 kset容器,如果沒找到就從其父kobj找,一直持續下去,直到父kobj不存在 */
- while (!top_kobj->kset && top_kobj->parent)
- top_kobj = top_kobj->parent;
- if (!top_kobj->kset) {
- pr_debug("kobject: '%s' (%p): %s: attempted to send uevent ""without kset!\n", kobject_name(kobj), kobj,__func__);
- return -EINVAL;
- }
- /* 在本例中是devices_kset容器 */
- kset = top_kobj->kset;
- uevent_ops = kset->uevent_ops; // 本例中uevent_ops = &device_uevent_ops
- /* 回調 uevent_ops->filter ()例程,本例中是dev_uevent_filter()例程,主要是檢查是否uevent suppress*/
- /* skip the event, if the filter returns zero. */
- if (uevent_ops && uevent_ops->filter)
- if (!uevent_ops->filter(kset, kobj)) { // 如果不成功,即uevent suppress,則直接返回
- pr_debug("kobject: '%s' (%p): %s: filter function ""caused the event to drop!\n",kobject_name(kobj), kobj, __func__);
- return 0;
- }
- /* 回調 uevent_ops-> name (),本例中是dev_uevent_name()例程,獲取bus或class的名字,本例中rtc0不存在bus,所以是class的名字“rtc”,後面分析 */
- if (uevent_ops && uevent_ops->name)
- subsystem = uevent_ops->name(kset, kobj);
- else
- subsystem = kobject_name(&kset->kobj);
- if (!subsystem) {
- pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the ""event to drop!\n", kobject_name(kobj), kobj,__func__);
- return 0;
- }
- // 獲得用於存放環境變量的buffer
- env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
- if (!env)
- return -ENOMEM;
- /* 獲取該kobj在sysfs的路徑,通過遍歷其父kobj來獲得,本例是/sys/devices/platform/s3c2410-rtc/rtc/rtc0 */
- devpath = kobject_get_path(kobj, GFP_KERNEL);
- if (!devpath) {
- retval = -ENOENT;
- goto exit;
- }
- // 添加 ACTION環境變量,本例是“add”命令
- retval = add_uevent_var(env, "ACTION=%s", action_string);
- if (retval)
- goto exit;
- // 添加 DEVPATH環境變量,本例是/sys/devices/platform/s3c2410-rtc/rtc/rtc0
- retval = add_uevent_var(env, "DEVPATH=%s", devpath);
- if (retval)
- goto exit;
- // 添加 SUBSYSTEM 環境變量,本例中是“rtc”
- retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
- if (retval)
- goto exit;
- /* keys passed in from the caller */
- if (envp_ext) { // 爲NULL,不執行
- for (i = 0; envp_ext[i]; i++) {
- retval = add_uevent_var(env, "%s", envp_ext[i]);
- if (retval)
- goto exit;
- }
- }
- // 回調 uevent_ops->uevent(),本例中是dev_uevent()例程,輸出一些環境變量,後面分析
- if (uevent_ops && uevent_ops->uevent) {
- retval = uevent_ops->uevent(kset, kobj, env);
- if (retval) {
- pr_debug("kobject: '%s' (%p): %s: uevent() returned ""%d\n", kobject_name(kobj), kobj,__func__, retval);
- goto exit;
- }
- }
- if (action == KOBJ_ADD)
- kobj->state_add_uevent_sent = 1;
- else if (action == KOBJ_REMOVE)
- kobj->state_remove_uevent_sent = 1;
- /* 增加event序列號的值,並輸出到環境變量的buffer。該系列號可以從/sys/kernel/uevent_seqnum屬性文件讀取,至於uevent_seqnum屬性文件及/sys/kernel/目錄是怎樣產生的,後面會分析 */
- spin_lock(&sequence_lock);
- seq = ++uevent_seqnum;
- spin_unlock(&sequence_lock);
- retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
- if (retval)
- goto exit;
- /* 如果配置了網絡,那麼就會通過netlink socket 向用戶空間發送環境標量,而用戶空間則通過netlink socket 接收,然後採取一些列的動作。這種機制目前用在udev中,也就是pc機系統中,後面會分析*/
- #if defined(CONFIG_NET)
- /* 如果配置了net,則會在kobject_uevent_init()例程中將全局比昂倆uevent_sock 初試化爲NETLINK_KOBJECT_UEVENT 類型的socket。*/
- if (uevent_sock) {
- struct sk_buff *skb;
- size_t len;
- /* allocate message with the maximum possible size */
- len = strlen(action_string) + strlen(devpath) + 2;
- skb = alloc_skb(len + env->buflen, GFP_KERNEL);
- if (skb) {
- char *scratch;
- /* add header */
- scratch = skb_put(skb, len);
- sprintf(scratch, "%s@%s", action_string, devpath);
- /* copy keys to our continuous event payload buffer */
- for (i = 0; i < env->envp_idx; i++) {
- len = strlen(env->envp[i]) + 1;
- scratch = skb_put(skb, len);
- strcpy(scratch, env->envp[i]);
- }
- NETLINK_CB(skb).dst_group = 1;
- retval = netlink_broadcast(uevent_sock, skb, 0, 1,GFP_KERNEL); // 廣播
- } else
- retval = -ENOMEM;
- }
- #endif
- /* 對於嵌入式系統來說,busybox採用的是mdev,在系統啓動腳本rcS 中會使用echo /sbin/mdev > /proc/sys/kernel/hotplug命令,而這個 hotplug文件通過一定的方法映射到了uevent_helper[]數組,所以uevent_helper[] = “/sbin/mdev” 。所以對於採用busybox的嵌入式系統來說會執行裏面的代碼,而pc機不會。也就是說內核會call用戶空間的/sbin/mdev這個應用程序來做動作,後面分析 */
- if (uevent_helper[0]) {
- char *argv [3];
- // 加入到環境變量buffer
- argv [0] = uevent_helper;
- argv [1] = (char *)subsystem;
- argv [2] = NULL;
- //添加HOME目錄環境變量
- retval = add_uevent_var(env, "HOME=/");
- if (retval)
- goto exit;
- //添加PATH環境變量
- retval = add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
- if (retval)
- goto exit;
- // 呼叫應用程序來處理, UMH_WAIT_EXEC表明等待應用程序處理完,dev會根據先前設置的環境變量進行處理
- retval = call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);
- }
- exit:
- kfree(devpath);
- kfree(env);
- return retval;
- }
- kobject_uevent_env函數最後調用了mdev,mdev的入口函數在busybox的mdev_main
- mdev_main
- {
- if (argv[1] && !strcmp(argv[1], "-s"))//先判斷參數1是否爲-s,如果爲-s則表明mdev爲開機執行的情況(mdev -s位於/etc/init.d/rcS中)
- else
- getenv //提取各個環境變量
- make_device //根據action創建設備
- /*對於設備,當我們創建其節點時,我們可以通過配置文件進行配置,該配置文件位於/etc/mdev.conf
- *設備名稱正則表達式用戶id 組id 節點屬性 創建的設備節點路徑 shell命令
- *配置方式爲<device regex> <uid>:<gid> <octal permissions> [=path] [@|$|*<command>]
- */
- parser = config_open2("/etc/mdev.conf", fopen_for_read); //打開/etc/mdev.conf文件
- while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) //分析mdev.conf文件內容,並執行相關操作command
- {...}
- mknod(device_name, mode | type, makedev(major, minor)) //調用mknod進行節點創建
- }
- 四、示例
- Udev獲取和設置設備節點的信息是通過建立一個本地套接口來實現:socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0)。其中SOCK_SEQPACKET提供連續可信賴的數據包連接。然後通過sendmsg和recvmsg去發送和獲取相應的信息。當獲取到設備的相應信息後再根據用戶設置的規則調用mknod函數去創建相應的設備節點。
- Udev創建的所有設備節點的設備號都可以在/sys/dev/{block,char}目錄中找到。如果驅動程序要讓udev自動創建設備節點,那麼你就必須保存在這個目錄下有這個設備號。下面例舉一個簡單的例子:
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- static int mem_major = 240;
- static struct class *test_class;
- struct cdev cdev;
- /*文件操作結構體*/
- static const struct file_operations mem_fops =
- {
- .owner = THIS_MODULE,
- };
- /*設備驅動模塊加載函數*/
- static int memdev_init(void)
- {
- dev_t devno = MKDEV(mem_major, 0);
- int result = register_chrdev_region(devno, 2, "memdev");
- if (result < 0)
- return result;
- cdev_add(&cdev, MKDEV(mem_major, 0), 1);
- test_class = class_create(THIS_MODULE, "juvi_test_class");//創建一個類,用來表示該設備文件是哪一種類型,創建成功會在/sys/class中出現。
- device_create(test_class, NULL, devno, NULL, "test_udev");//創建設備文件,它會在/sys/dev/char中出現相應的設備接點號。然後dev目錄下面會自動創建設備文件。
- return result;
- }
- /*模塊卸載函數*/
- static void memdev_exit(void)
- {
- cdev_del(&cdev); /*註銷設備*/
- unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
- class_destroy(test_class);//刪除這個類型的設備。
- }
- MODULE_LICENSE("GPL");
- module_init(memdev_init);
- module_exit(memdev_exit);
- 五、分析mdev
- a、執行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。
- b、當mdev因uevnet事件(以前叫hotplug事件)被調用時,mdev通過由uevent事件傳遞給它的環境變量獲取到:引起該uevent事件的設備action及該設備所在的路徑device path。然後判斷引起該uevent事件的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"形式輸出設備編號。
- int mdev_main(int argc UNUSED_PARAM, char **argv)
- {
- //#define RESERVE_CONFIG_BUFFER(buffer,len) char buffer[len]
- //聲明一個數組
- RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
- /*
- struct globals {
- int root_major, root_minor;
- char *subsystem;
- #if ENABLE_FEATURE_MDEV_CONF
- const char *filename;
- parser_t *parser;
- struct rule **rule_vec;
- unsigned rule_idx;
- #endif
- struct rule cur_rule;
- } FIX_ALIASING;
- #define G (*(struct globals*)&bb_common_bufsiz1)
- enum { COMMON_BUFSIZE = (BUFSIZ >= 256*sizeof(void*) ? BUFSIZ+1 : 256*sizeof(void*)) };
- extern char bb_common_bufsiz1[COMMON_BUFSIZE];
- #define INIT_G() do { \
- IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.maj = -1;) \
- IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.mode = 0660;) \
- } while (0)
- */
- //初始化結構體
- INIT_G();
- #if ENABLE_FEATURE_MDEV_CONF
- G.filename = "/etc/mdev.conf";
- #endif
- /* We can be called as hotplug helper */
- //閱讀這個函數的源代碼後,發現這裏其實就是判斷了/dev/null是否存在。如果打不開,那麼就進入die狀態。
- bb_sanitize_stdio();
- /* Force the configuration file settings exactly */
- umask(0);//配置屏蔽位
- xchdir("/dev");//切換到/dev目錄
- if (argv[1] && strcmp(argv[1], "-s") == 0) {//如果執行的是mdev -s,這是在shell裏調用的。在系統啓動時調用。創建所有設備驅動的節點。
- struct stat st;
- #if ENABLE_FEATURE_MDEV_CONF
- /* Same as xrealloc_vector(NULL, 4, 0): */
- G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));//給rule結構體分配空間
- #endif
- xstat("/", &st);//返回根目錄文件狀態信息
- G.root_major = major(st.st_dev);//保存文件的設備號
- G.root_minor = minor(st.st_dev);
- if (access("/sys/class/block", F_OK) != 0) {//判斷/sys/class/block這個文件或者目錄是否存在。存在返回0,否則返回-1
- //這個函數是遞歸函數,它會把/sys/block目錄下的所有文件文件夾都去查看一遍,如果發現dev文件,那麼將按照/etc/mdev.conf文件進行相應的配置。如果沒有配置文件,那麼直接創建設備節點。
- recursive_action("/sys/block",ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,fileAction, dirAction, temp, 0);
- }
- //這個函數是遞歸函數,它會把/sys/class目錄下的所有文件文件夾都去查看一遍,如果發現dev文件,那麼將按照/etc/mdev.conf文件進行相應的配置。如果沒有配置文件,那麼直接創建設備節點。
- recursive_action("/sys/class",ACTION_RECURSE | ACTION_FOLLOWLINKS,fileAction, dirAction, temp, 0);
- } else {//通過hotplug通知mdev創建設備節點
- char *fw;
- char *seq;
- char *action;
- char *env_path;
- static const char keywords[] ALIGN1 = "remove\0add\0";
- enum { OP_remove = 0, OP_add };
- smalluint op;
- /* Hotplug:
- * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
- * ACTION can be "add" or "remove"
- * DEVPATH is like "/block/sda" or "/class/input/mice"
- 經過驅動層分析,所得的環境變量爲,這裏是以spidev,0.0設備爲例:
- ACTION=add: kobject_actions[KOBJ_ADD]
- DEVPATH=/class/spidev/spidev0.0/: kobject_get_path(kobj, GFP_KERNEL) /sys不存在,這裏只統計到/sys目錄下
- SUBSYSTEM=spidev: dev->bus->name,dev->class->name,如果dev->bus不存在的情況下,那麼才使用dev->class->name
- MAJOR=MAJOR(dev->devt)
- MINOR=MINOR(dev->devt)
- PHYSDEVPATH=/devices/platform/atmel_spi.0/spi0.0/: kobject_get_path(&dev->parent->kobj, GFP_KERNEL) /sys不存在,這裏只統計到/sys目錄下
- PHYSDEVBUS=/bus/spi/: dev->parent->bus->name /sys不存在,這裏只統計到/sys目錄下
- PHYSDEVDRIVER=spidev: dev->parent->driver->name
- SEQNUM=++uevent_seqnum
- HOME=/
- PATH=/sbin:/bin:/usr/sbin:/usr/bin
- */
- //獲得環境變量
- action = getenv("ACTION");
- env_path = getenv("DEVPATH");
- G.subsystem = getenv("SUBSYSTEM");
- if (!action || !env_path)
- bb_show_usage();
- fw = getenv("FIRMWARE");
- op = index_in_strings(keywords, action);//比較keywords數組中和action,若是remove則返回0,若是add則返回1
- seq = getenv("SEQNUM");
- /*
- 內核不序列化熱插拔事件,而是爲每一個成功的熱插拔調用增加了 SEQNUM 這個環境變量。通常情況下,mdev不在乎這個。這樣也許可以重新對熱插拔事件進行重調用,典型的症狀就是有時某些設備節點不能像期待的那樣被創建出來。不管怎麼說,如果 /dev/mdev.seq 文件存在,mdev將比較它和SEQNUM的內容,它將重試直到有兩個第二,等待他們匹配。如果他們精確的匹配(甚至連"\n"都不被允許),或者兩個第二齣現,mdev依舊運行,然後它用SEQNUM+1重寫/dev/mdev.seq。
- */
- if (seq) {
- int timeout = 2000 / 32; /* 2000 msec */
- do {
- int seqlen;
- char seqbuf[sizeof(int)*3 + 2];
- //從mdev.seq文件中讀出seq
- seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf) - 1);
- if (seqlen < 0) {
- seq = NULL;
- break;
- }
- seqbuf[seqlen] = '\0';
- if (seqbuf[0] == '\n' /* seed file? */|| strcmp(seq, seqbuf) == 0) { /* correct idx? */
- break;
- }
- usleep(32*1000);
- } while (--timeout);
- }
- //創建的設備路徑名賦值給temp,例如/sys/class/spidev/spidev0.0/
- snprintf(temp, PATH_MAX, "/sys%s", env_path);
- if (op == OP_remove) {//刪除節點
- if (!fw)
- make_device(temp, /*delete:*/ 1);
- }
- else if (op == OP_add) {//添加節點
- make_device(temp, /*delete:*/ 0);
- if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
- if (fw)
- load_firmware(fw, temp);
- }
- }
- if (seq) {//重寫mdev.seq文件
- xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
- }
- }
- if (ENABLE_FEATURE_CLEAN_UP)
- RELEASE_CONFIG_BUFFER(temp);
- return EXIT_SUCCESS;
- }
- //recursive_action("/sys/block",ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,fileAction, dirAction, temp, 0);
- int FAST_FUNC recursive_action(const char *fileName,unsigned flags,int FAST_FUNC (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
- int FAST_FUNC (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),void* userData,unsigned depth)
- {
- struct stat statbuf;
- unsigned follow;
- int status;
- DIR *dir;
- struct dirent *next;
- if (!fileAction)
- fileAction = true_action;
- if (!dirAction)
- dirAction = true_action;
- follow = ACTION_FOLLOWLINKS;
- if (depth == 0)//第一次調用時傳入depth參數爲0
- follow = ACTION_FOLLOWLINKS | ACTION_FOLLOWLINKS_L0;
- follow &= flags;//也設置了ACTION_FOLLOWLINKS標誌
- //stat和lstat的區別:當文件是一個符號鏈接時,lstat返回的是該符號鏈接本身的信息;而stat返回的是該鏈接指向的文件的信息。
- status = (follow ? stat : lstat)(fileName, &statbuf);//這裏用stat讀取目錄或文件屬性
- if (status < 0) {
- if ((flags & ACTION_DANGLING_OK)&& errno == ENOENT&& lstat(fileName, &statbuf) == 0) {/* Dangling link */
- return fileAction(fileName, &statbuf, userData, depth);
- }
- goto done_nak_warn;
- }
- if (!S_ISDIR(statbuf.st_mode)) {//如果不是目錄,則調用下邊函數進行創建設備節點
- return fileAction(fileName, &statbuf, userData, depth);
- }
- //ACTION_RECURSE標誌設置過,不進行以下操作
- if (!(flags & ACTION_RECURSE)) {
- return dirAction(fileName, &statbuf, userData, depth);
- }
- //沒有設置過ACTION_DEPTHFIRST
- if (!(flags & ACTION_DEPTHFIRST)) {
- status = dirAction(fileName, &statbuf, userData, depth);//返回TRUE=1
- if (!status)
- goto done_nak_warn;
- if (status == SKIP)
- return TRUE;
- }
- //執行到這裏表明傳入的參數是目錄
- dir = opendir(fileName);//打開目錄
- if (!dir) {
- goto done_nak_warn;
- }
- status = TRUE;
- while ((next = readdir(dir)) != NULL) {//讀目錄
- char *nextFile;
- nextFile = concat_subpath_file(fileName, next->d_name);//讀目錄下的文件
- if (nextFile == NULL)
- continue;
- /* process every file (NB: ACTION_RECURSE is set in flags) */
- if (!recursive_action(nextFile, flags, fileAction, dirAction,userData, depth + 1))//遞歸調用
- status = FALSE;
- free(nextFile);
- }
- closedir(dir);
- //沒有設置過ACTION_DEPTHFIRST,不會進去執行
- if (flags & ACTION_DEPTHFIRST) {
- if (!dirAction(fileName, &statbuf, userData, depth))
- goto done_nak_warn;
- }
- return status;
- done_nak_warn:
- if (!(flags & ACTION_QUIET))
- bb_simple_perror_msg(fileName);
- return FALSE;
- }
- static int FAST_FUNC fileAction(const char *fileName,struct stat *statbuf UNUSED_PARAM,void *userData,int depth UNUSED_PARAM)
- {
- size_t len = strlen(fileName) - 4; /* can't underflow */
- char *scratch = userData;
- //檢查傳入進來的路徑名的最後4個字符是否爲“/dev”,即只有根據dev文件來創建設備節點
- if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
- return FALSE;
- strcpy(scratch, fileName);//複製文件名
- scratch[len] = '\0';//將最後的"/dev"去掉
- make_device(scratch, /*delete:*/ 0);//進行創建設備節點
- return TRUE;
- }
- static void make_device(char *path, int delete)
- {
- char *device_name, *subsystem_slash_devname;
- int major, minor, type, len;
- dbg("%s('%s', delete:%d)", __func__, path, delete);
- major = -1;
- if (!delete) {
- char *dev_maj_min = path + strlen(path);//將dev_maj_min指針指向路徑名的最後
- //傳入的路徑名是最後dev文件的父目錄,例如/sys/block/mtdblock0
- strcpy(dev_maj_min, "/dev");//即將路徑名添加“/dev”,即/sys/block/mtdblock0/dev確保讀的文件時dev文件
- len = open_read_close(path, dev_maj_min + 1, 64);//從dev文件中讀出主設備號和次設備號存到dev_maj_min + 1地址處
- *dev_maj_min = '\0';
- if (len < 1) {
- if (!ENABLE_FEATURE_MDEV_EXEC)
- return;
- } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) {//主設備號和次設備號複製給major和minor
- major = -1;
- }
- }
- //調用strrchr() 函數查找字符在指定字符串中從後面開始的第一次出現的位置,即找到設備名
- //例如:/sys/block/mtdblock0,返回的設備名就是mtdblock0
- device_name = (char*) bb_basename(path);
- type = S_IFCHR;
- //判斷是否爲block設備
- if (strstr(path, "/block/") || (G.subsystem && strncmp(G.subsystem, "block", 5) == 0))
- type = S_IFBLK;
- /* Make path point to "subsystem/device_name" */
- subsystem_slash_devname = NULL;
- //path路徑名前移
- if (strncmp(path, "/sys/block/", 11) == 0) /* legacy case */
- path += sizeof("/sys/") - 1;
- else if (strncmp(path, "/sys/class/", 11) == 0)
- path += sizeof("/sys/class/") - 1;
- else {
- /* Example of a hotplug invocation:
- * SUBSYSTEM="block"
- * DEVPATH="/sys" + "/devices/virtual/mtd/mtd3/mtdblock3"
- * ("/sys" is added by mdev_main)
- * - path does not contain subsystem
- */
- subsystem_slash_devname = concat_path_file(G.subsystem, device_name);
- path = subsystem_slash_devname;
- }
- #if ENABLE_FEATURE_MDEV_CONF
- G.rule_idx = 0; /* restart from the beginning (think mdev -s) */
- #endif
- for (;;) {
- const char *str_to_match;
- regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
- char *command;
- char *alias;
- char aliaslink = aliaslink; /* for compiler */
- const char *node_name;
- const struct rule *rule;
- str_to_match = "";
- //# define next_rule() (&G.cur_rule)
- rule = next_rule();
- //檢查rule
- #if ENABLE_FEATURE_MDEV_CONF
- if (rule->maj >= 0) { /* @maj,min rule */
- if (major != rule->maj)
- continue;
- if (minor < rule->min0 || minor > rule->min1)
- continue;
- memset(off, 0, sizeof(off));
- goto rule_matches;
- }
- if (rule->envvar) { /* $envvar=regex rule */
- str_to_match = getenv(rule->envvar);
- dbg("getenv('%s'):'%s'", rule->envvar, str_to_match);
- if (!str_to_match)
- continue;
- } else {
- /* regex to match [subsystem/]device_name */
- str_to_match = (rule->regex_has_slash ? path : device_name);
- }
- if (rule->regex_compiled) {
- int regex_match = regexec(&rule->match, str_to_match, ARRAY_SIZE(off), off, 0);
- dbg("regex_match for '%s':%d", str_to_match, regex_match);
- if (regex_match != 0
- /* regexec returns whole pattern as "range" 0 */
- || off[0].rm_so != 0
- || (int)off[0].rm_eo != (int)strlen(str_to_match)
- ) {
- continue; /* this rule doesn't match */
- }
- }
- /* else: it's final implicit "match-all" rule */
- rule_matches:
- #endif
- dbg("rule matched");
- /* Build alias name */
- alias = NULL;
- if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) {
- aliaslink = rule->ren_mov[0];
- if (aliaslink == '!') {
- /* "!": suppress node creation/deletion */
- major = -2;
- }
- else if (aliaslink == '>' || aliaslink == '=') {
- if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
- char *s;
- char *p;
- unsigned n;
- /* substitute %1..9 with off[1..9], if any */
- n = 0;
- s = rule->ren_mov;
- while (*s)
- if (*s++ == '%')
- n++;
- p = alias = xzalloc(strlen(rule->ren_mov) + n * strlen(str_to_match));
- s = rule->ren_mov + 1;
- while (*s) {
- *p = *s;
- if ('%' == *s) {
- unsigned i = (s[1] - '0');
- if (i <= 9 && off[i].rm_so >= 0) {
- n = off[i].rm_eo - off[i].rm_so;
- strncpy(p, str_to_match + off[i].rm_so, n);
- p += n - 1;
- s++;
- }
- }
- p++;
- s++;
- }
- } else {
- alias = xstrdup(rule->ren_mov + 1);
- }
- }
- }
- dbg("alias:'%s'", alias);
- command = NULL;
- IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;)
- if (command) {
- const char *s = "$@*";
- const char *s2 = strchr(s, command[0]);
- /* Are we running this command now?
- * Run $cmd on delete, @cmd on create, *cmd on both
- */
- if (s2 - s != delete) {
- /* We are here if: '*',
- * or: '@' and delete = 0,
- * or: '$' and delete = 1
- */
- command++;
- } else {
- command = NULL;
- }
- }
- dbg("command:'%s'", command);
- /* "Execute" the line we found */
- node_name = device_name;
- if (ENABLE_FEATURE_MDEV_RENAME && alias) {
- node_name = alias = build_alias(alias, device_name);
- dbg("alias2:'%s'", alias);
- }
- if (!delete && major >= 0) {//創建設備節點
- dbg("mknod('%s',%o,(%d,%d))", node_name, rule->mode | type, major, minor);
- if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST)//創建設備節點
- bb_perror_msg("can't create '%s'", node_name);
- if (major == G.root_major && minor == G.root_minor)
- symlink(node_name, "root");
- if (ENABLE_FEATURE_MDEV_CONF) {
- chmod(node_name, rule->mode);
- chown(node_name, rule->ugid.uid, rule->ugid.gid);
- }
- if (ENABLE_FEATURE_MDEV_RENAME && alias) {
- if (aliaslink == '>') {
- //TODO: on devtmpfs, device_name already exists and symlink() fails.
- //End result is that instead of symlink, we have two nodes.
- //What should be done?
- symlink(node_name, device_name);
- }
- }
- }
- if (ENABLE_FEATURE_MDEV_EXEC && command) {
- /* setenv will leak memory, use putenv/unsetenv/free */
- char *s = xasprintf("%s=%s", "MDEV", node_name);
- char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
- putenv(s);
- putenv(s1);
- if (system(command) == -1)
- bb_perror_msg("can't run '%s'", command);
- bb_unsetenv_and_free(s1);
- bb_unsetenv_and_free(s);
- }
- if (delete && major >= -1) {//刪除節點
- if (ENABLE_FEATURE_MDEV_RENAME && alias) {
- if (aliaslink == '>')
- unlink(device_name);
- }
- unlink(node_name);
- }
- if (ENABLE_FEATURE_MDEV_RENAME)
- free(alias);
- /* We found matching line.
- * Stop unless it was prefixed with '-'
- */
- if (!ENABLE_FEATURE_MDEV_CONF || !rule->keep_matching)
- break;
- } /* for (;;) */
- free(subsystem_slash_devname);
- }