關鍵詞:uevent、netlink、ADD/REMOVE/CHANGE、uevent_helper、hotplug、usermode helper、mdev、mdev.conf等等。html
本文從三方面瞭解uevent相關內容:內核中uevent如何傳送、用戶空間如何處理uevent、如何經過mdev實現熱插拔功能。node
kobject_action定義了 Linux下的uevent類型;struct kerenl_uevent_env表示一個待發送的uevent。linux
uevent_net_init()建立發送uevent所須要的socket等信息。正則表達式
內核驅動經過kobject_uevent()/kobject_uevent_env()發送uevent到用戶空間,主要包括兩部分工做:一是經過netlink_broadcast_filtered()發送netlink消息;另外一是經過call_usermodehelper_setup()/call_usermodehelper_exec()調用用戶空間程序處理uevent消息。編程
kobject_action定義了kobject的動做,包括ADD、REMOVE、CHANGE等等。用戶空間根據ADD或者REMOVE處理熱插拔時間,電池模塊根據CHANGE處理電量更新。api
kobj_uevent_env用於表示一個kobject事件,argv是用戶空間執行的helper參數;envp和buf組成發送uevent字符串信息。數組
enum kobject_action { KOBJ_ADD,------------------------ADD/REMOVE添加/移除事件。 KOBJ_REMOVE, KOBJ_CHANGE,---------------------設備狀態或者內容發生改變。 KOBJ_MOVE,-----------------------更更名稱或者更改parent,即更改了目錄結構。 KOBJ_ONLINE,---------------------設備上線/下線事件,常表示使能或者去使能。 KOBJ_OFFLINE, KOBJ_MAX }; static const char *kobject_actions[] = { [KOBJ_ADD] = "add", [KOBJ_REMOVE] = "remove", [KOBJ_CHANGE] = "change", [KOBJ_MOVE] = "move", [KOBJ_ONLINE] = "online", [KOBJ_OFFLINE] = "offline", }; struct kobj_uevent_env { char *argv[3];------------------------------用戶空間可執行文件路徑,以及參數等。 char *envp[UEVENT_NUM_ENVP];----------------指針數組,保存每一個環境變量的地址。 int envp_idx; char buf[UEVENT_BUFFER_SIZE];---------------環境變量內容。 int buflen; };
uevent_net_init()建立類型爲NETLINK_KOBJECT_UEVENT的socket,並將其放入uevent_sock_list鏈表上。uevent_net_exit()則將其從uevent_socket_list中摘除,而且釋放socket相關資源。網絡
static int uevent_net_init(struct net *net) { struct uevent_sock *ue_sk; struct netlink_kernel_cfg cfg = { .groups = 1, .flags = NL_CFG_F_NONROOT_RECV, }; ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL); if (!ue_sk) return -ENOMEM; ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);------------建立NETLINK_KOBJECT_UEVENT類型的socket。 if (!ue_sk->sk) { printk(KERN_ERR "kobject_uevent: unable to create netlink socket!\n"); kfree(ue_sk); return -ENODEV; } mutex_lock(&uevent_sock_mutex); list_add_tail(&ue_sk->list, &uevent_sock_list);-----------------------------------將建立的uevent_sock加入到uevent_sock_list中。 mutex_unlock(&uevent_sock_mutex); return 0; } static void uevent_net_exit(struct net *net) { struct uevent_sock *ue_sk; mutex_lock(&uevent_sock_mutex); list_for_each_entry(ue_sk, &uevent_sock_list, list) { if (sock_net(ue_sk->sk) == net) goto found; } mutex_unlock(&uevent_sock_mutex); return; found: list_del(&ue_sk->list); mutex_unlock(&uevent_sock_mutex); netlink_kernel_release(ue_sk->sk); kfree(ue_sk); } static struct pernet_operations uevent_net_ops = { .init = uevent_net_init, .exit = uevent_net_exit, }; static int __init kobject_uevent_init(void) { return register_pernet_subsys(&uevent_net_ops);-----------將uevent網絡協議模塊添加到新的命名空間子系統中,而且調用init初始化函數。 } postcore_initcall(kobject_uevent_init);
對uevent_helper設置,能夠對/proc/sys/kernel/hotplug寫可執行文件路徑便可。數據結構
而後在內核觸發uevent事件的以後調用相關可執行文件進行處理。app
static struct ctl_table kern_table[] = { ... #ifdef CONFIG_UEVENT_HELPER { .procname = "hotplug", .data = &uevent_helper, .maxlen = UEVENT_HELPER_PATH_LEN, .mode = 0644, .proc_handler = proc_dostring, }, #endif... { } };
或者還能夠對/proc/kernel/uevent_helper寫入可執行文件路徑。
static ssize_t uevent_helper_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%s\n", uevent_helper); } 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; } KERNEL_ATTR_RW(uevent_helper);
usermode helper用於幫助在內核空間啓動一個用戶空間程序。首先經過call_usermodehelper_setup()初始化一個struct subprocess_info實例;而後調用call_usermodehelper_exec()執行,經過kernel_thread()建立線程,入口函數call_usermodehelper_exec_async()調用do_execve()加載用戶空間程序。
這裏不一樣等待程序運行結束的方式,UMH_NO_WAIT在將work放入system_unbound_wq以後,不等待直接退出;UMH_KILLABLE則會等待進程變爲TASK_KILLABLE。UMH_WAIT_PROC等待進程執行完畢,UMH_WAIT_EXEC只是等待do_exec()執行完畢,而不是進程結束。
struct subprocess_info表示一個usermode helper執行的實例。
#define UMH_NO_WAIT 0 /* don't wait at all */ #define UMH_WAIT_EXEC 1 /* wait for the exec, but not the process */ #define UMH_WAIT_PROC 2 /* wait for the process to complete */ #define UMH_KILLABLE 4 /* wait for EXEC/PROC killable */ struct subprocess_info { struct work_struct work;---------------將usermode helper做爲一個work放入system_unbound_wq中。 struct completion *complete; char *path;----------------------------用戶空間可執行文件路徑。 char **argv;---------------------------可執行文件所需參數。 char **envp;---------------------------可執行文件所需環境變量。 int wait;------------------------------等待標誌。 int retval; int (*init)(struct subprocess_info *info, struct cred *new);---執行產需以前的初始化函數。 void (*cleanup)(struct subprocess_info *info);-----------------釋放struct subprocess_info是的清理程序。 void *data; };
call_usermodehelper()首先建立struct subprocess_info,而後執行用戶空間程序。
int call_usermodehelper(char *path, char **argv, char **envp, int wait) { struct subprocess_info *info; gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL; info = call_usermodehelper_setup(path, argv, envp, gfp_mask, NULL, NULL, NULL);-------------------------------須要用戶空間執行的程序路徑以及參數,內存分配gfp_mask等等,填充倒struc subprocess_info中。 if (info == NULL) return -ENOMEM; return call_usermodehelper_exec(info, wait);----------------------將subprocess_info->work放入system_unbound_eq執行。 }
call_usermodehelper_setup()初始化struct subprocess_info實例,包括程序路徑、參數等等,還有初始化一個work,對應的執行函數式call_usermodehelper_exec_work()。
struct subprocess_info *call_usermodehelper_setup(char *path, char **argv, char **envp, gfp_t gfp_mask, int (*init)(struct subprocess_info *info, struct cred *new), void (*cleanup)(struct subprocess_info *info), void *data) { struct subprocess_info *sub_info; sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask); if (!sub_info) goto out; INIT_WORK(&sub_info->work, call_usermodehelper_exec_work); sub_info->path = path; sub_info->argv = argv; sub_info->envp = envp; sub_info->cleanup = cleanup; sub_info->init = init; sub_info->data = data; out: return sub_info; } static void call_usermodehelper_exec_work(struct work_struct *work) { struct subprocess_info *sub_info = container_of(work, struct subprocess_info, work); if (sub_info->wait & UMH_WAIT_PROC) { call_usermodehelper_exec_sync(sub_info); } else { pid_t pid; /* * Use CLONE_PARENT to reparent it to kthreadd; we do not * want to pollute current->children, and we need a parent * that always ignores SIGCHLD to ensure auto-reaping. */ pid = kernel_thread(call_usermodehelper_exec_async, sub_info, CLONE_PARENT | SIGCHLD);--------------------------CLONE_PARENT讓新建立的進程與建立它的進程成了‘兄弟’而不是‘父子’。 if (pid < 0) { sub_info->retval = pid; umh_complete(sub_info); } } }
call_usermode_herlper_exec_async()和call_usermodehelper_exec_sync()最大的區別是 建立進程的flags,前者CLONE_PARENT致使新建立的進程和建立它的進程編程兄弟關係,然後者還保持父子關係。
static void call_usermodehelper_exec_sync(struct subprocess_info *sub_info) { pid_t pid; /* If SIGCLD is ignored sys_wait4 won't populate the status. */ kernel_sigaction(SIGCHLD, SIG_DFL); pid = kernel_thread(call_usermodehelper_exec_async, sub_info, SIGCHLD); if (pid < 0) { sub_info->retval = pid; } else { int ret = -ECHILD; sys_wait4(pid, (int __user *)&ret, 0, NULL);-------------------------等待子進程退出,這也是async和sync最大的區別所在。 if (ret) sub_info->retval = ret; } kernel_sigaction(SIGCHLD, SIG_IGN); umh_complete(sub_info); } static int call_usermodehelper_exec_async(void *data) { struct subprocess_info *sub_info = data; struct cred *new; int retval; spin_lock_irq(¤t->sighand->siglock); flush_signal_handlers(current, 1);------------------------------------------進行signal、nice、credential準備工做。 spin_unlock_irq(¤t->sighand->siglock); set_user_nice(current, 0); retval = -ENOMEM; new = prepare_kernel_cred(current); if (!new) goto out; spin_lock(&umh_sysctl_lock); new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset); new->cap_inheritable = cap_intersect(usermodehelper_inheritable, new->cap_inheritable); spin_unlock(&umh_sysctl_lock); if (sub_info->init) { retval = sub_info->init(sub_info, new);--------------------------------爲進程建立進行初始化工做。 if (retval) { abort_creds(new); goto out; } } commit_creds(new); retval = do_execve(getname_kernel(sub_info->path), (const char __user *const __user *)sub_info->argv, (const char __user *const __user *)sub_info->envp);------------調用usermode程序替代當前進程。 ... }
call_usermodehelper_exec()最主要的工做就是將一個usermode helper命令放入system_unbound_wq執行,而後根據wait類型進行不一樣條件的等待。
int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait) { DECLARE_COMPLETION_ONSTACK(done); int retval = 0; if (!sub_info->path) { call_usermodehelper_freeinfo(sub_info); return -EINVAL; } helper_lock(); if (usermodehelper_disabled) { retval = -EBUSY; goto out; } sub_info->complete = (wait == UMH_NO_WAIT) ? NULL : &done; sub_info->wait = wait; queue_work(system_unbound_wq, &sub_info->work);---------------將usermode helper進程放入system_unbound_wq上調度,即不綁定到任何CPU上,儘快獲得執行。 if (wait == UMH_NO_WAIT) /* task has freed sub_info */-----對於UMH_NO_WAIT類型,跳過下面的completion同步等待步驟。 goto unlock; if (wait & UMH_KILLABLE) { retval = wait_for_completion_killable(&done);-------------等待進程屬性變爲TASK_KILLABLE。 if (!retval) goto wait_done; /* umh_complete() will see NULL and free sub_info */ if (xchg(&sub_info->complete, NULL)) goto unlock; /* fallthrough, umh_complete() was already called */ } wait_for_completion(&done); wait_done: retval = sub_info->retval; out: call_usermodehelper_freeinfo(sub_info); unlock: helper_unlock(); return retval; } static void umh_complete(struct subprocess_info *sub_info) { struct completion *comp = xchg(&sub_info->complete, NULL); if (comp) complete(comp); else call_usermodehelper_freeinfo(sub_info); } static void call_usermodehelper_freeinfo(struct subprocess_info *info) { if (info->cleanup) (*info->cleanup)(info); kfree(info); }
uevent發送能夠經過kobject_uevent(),或者經過kobject_uevent_env()附加更多uevent信息。
kobject_uevent_env()主要分爲兩部分,一是經過netlink_broadcast_filtered()將socket信息發出去;另外一個是經過uevent helper將uevent調用指定的uevent_helper進行處理,一般是熱插拔程序mdev、udevd等。
int kobject_uevent(struct kobject *kobj, enum kobject_action action) { return kobject_uevent_env(kobj, action, NULL); } 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];------------將action轉換成字符串。 const char *devpath = NULL; const char *subsystem; struct kobject *top_kobj; struct kset *kset; const struct kset_uevent_ops *uevent_ops; int i = 0; int retval = 0; #ifdef CONFIG_NET struct uevent_sock *ue_sk; #endif top_kobj = kobj; while (!top_kobj->kset && top_kobj->parent) top_kobj = top_kobj->parent; ... kset = top_kobj->kset; uevent_ops = kset->uevent_ops; ... /* originating subsystem */ if (uevent_ops && uevent_ops->name) subsystem = uevent_ops->name(kset, kobj); else subsystem = kobject_name(&kset->kobj); ... env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); if (!env) return -ENOMEM; /* complete object path */ devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; } /* default keys */ retval = add_uevent_var(env, "ACTION=%s", action_string);-----------默認添加ACTION、DEVPATH、SUBSYSTEM三個鍵值。 if (retval) goto exit; ... if (envp_ext) {-----------------------------------------------------將自定義的鍵值附上。 for (i = 0; envp_ext[i]; i++) { retval = add_uevent_var(env, "%s", envp_ext[i]); if (retval) goto exit; } } /* let the kset specific function add its stuff */ 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; mutex_lock(&uevent_sock_mutex); ... #if defined(CONFIG_NET) /* send netlink message */ list_for_each_entry(ue_sk, &uevent_sock_list, list) {------------------遍歷uevent_sock_list上全部的socket。 struct sock *uevent_sock = ue_sk->sk; struct sk_buff *skb; size_t len; if (!netlink_has_listeners(uevent_sock, 1)) continue; /* allocate message with the maximum possible size */ len = strlen(action_string) + strlen(devpath) + 2; skb = alloc_skb(len + env->buflen, GFP_KERNEL);--------------------爲下面消息發送建立sk_buff實例。 if (skb) { char *scratch; /* add header */ scratch = skb_put(skb, len); sprintf(scratch, "%s@%s", action_string, devpath);-------------在已有鍵值基礎上添加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_filtered(uevent_sock, skb, 0, 1, GFP_KERNEL, kobj_bcast_filter, kobj);--------------------------------------經過netlink_broadcast_filtered()發送skb數據。 /* ENOBUFS should be handled in userspace */ if (retval == -ENOBUFS || retval == -ESRCH) retval = 0; } else retval = -ENOMEM; } #endif mutex_unlock(&uevent_sock_mutex); #ifdef CONFIG_UEVENT_HELPER /* call uevent_helper, usually only enabled during early boot */ if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { struct subprocess_info *info; 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 = init_uevent_argv(env, subsystem); if (retval) goto exit; retval = -ENOMEM; info = call_usermodehelper_setup(env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env); if (info) { retval = call_usermodehelper_exec(info, UMH_NO_WAIT); env = NULL; /* freed by cleanup_uevent_env */ } } #endif... } int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...) { va_list args; int len; if (env->envp_idx >= ARRAY_SIZE(env->envp)) { WARN(1, KERN_ERR "add_uevent_var: too many keys\n"); return -ENOMEM; } va_start(args, format); len = vsnprintf(&env->buf[env->buflen], sizeof(env->buf) - env->buflen, format, args); va_end(args); if (len >= (sizeof(env->buf) - env->buflen)) { WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n"); return -ENOMEM; } env->envp[env->envp_idx++] = &env->buf[env->buflen]; env->buflen += len + 1; return 0; } static int kobj_bcast_filter(struct sock *dsk, struct sk_buff *skb, void *data) { struct kobject *kobj = data, *ksobj; const struct kobj_ns_type_operations *ops; ops = kobj_ns_ops(kobj); if (!ops && kobj->kset) { ksobj = &kobj->kset->kobj; if (ksobj->parent != NULL) ops = kobj_ns_ops(ksobj->parent); } if (ops && ops->netlink_ns && kobj->ktype->namespace) { const void *sock_ns, *ns; ns = kobj->ktype->namespace(kobj); sock_ns = ops->netlink_ns(dsk); return sock_ns != ns; } return 0; } static int kobj_usermode_filter(struct kobject *kobj) { const struct kobj_ns_type_operations *ops; ops = kobj_ns_ops(kobj); if (ops) { const void *init_ns, *ns; ns = kobj->ktype->namespace(kobj); init_ns = ops->initial_ns(); return ns != init_ns; } return 0; } static int init_uevent_argv(struct kobj_uevent_env *env, const char *subsystem) { int len; len = strlcpy(&env->buf[env->buflen], subsystem, sizeof(env->buf) - env->buflen); if (len >= (sizeof(env->buf) - env->buflen)) { WARN(1, KERN_ERR "init_uevent_argv: buffer size too small\n"); return -ENOMEM; } env->argv[0] = uevent_helper; env->argv[1] = &env->buf[env->buflen]; env->argv[2] = NULL; env->buflen += len + 1; return 0; } static void cleanup_uevent_env(struct subprocess_info *info) { kfree(info->data); }
kobject_uevent_env()詳細解釋參考《設備模型的uevent機制》。
經過內核發送uevent很簡單,將數據表明環境變量的字符串組裝好後,選擇合適的action,指定對應的kobject設備便可。
static int user_cooling_set_cur_state(struct thermal_cooling_device *cdev, unsigned long new_target_ratio) { int ret = 0, i = 0, temperature = 0; char *thermal_prop[4]; struct thermal_instance *instance; list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) { if (instance->tz->temperature > temperature) temperature = instance->tz->temperature; } user_cooling_state = new_target_ratio; thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", cdev->type); thermal_prop[1] = kasprintf(GFP_KERNEL, "STATE=%lu", new_target_ratio); thermal_prop[2] = kasprintf(GFP_KERNEL, "TEMP=%d", temperature); thermal_prop[3] = NULL; kobject_uevent_env(&cdev->device.kobj, KOBJ_CHANGE, thermal_prop); for (i = 0; i < 3; ++i) kfree(thermal_prop[i]); return ret; }
經過kobject_uevent_env()能夠添加自定義環境變量,用戶空間就會收到以下uevent消息。
change@/devices/virtual/thermal/cooling_device0 ACTION=change DEVPATH=/devices/virtual/thermal/cooling_device0 SUBSYSTEM=thermal NAME=user_cooling STATE=1 TEMP=90 SEQNUM=747
用戶空間首先建立一個socket,並綁定到AF_NETLINK上,而後recv()接收消息,在處理字符串。
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/socket.h> #include <linux/netlink.h> #define UEVENT_MSG_LEN 2048 #define USER_COOLING_DEV "/devices/virtual/thermal/cooling_device0" struct cooling_device { const char *name; const char *action; const char *path; int state; int temp; }; static int open_uevent_socket(void); static void parse_uevent(const char *msg, struct cooling_device *cdev); int main(int argc, char* argv[]) { int socket_fd = -1; char msg[UEVENT_MSG_LEN+2]; int n; socket_fd = open_uevent_socket();--------------------------------------建立socket。 printf("socket_fd = %d\n", socket_fd); do { while((n = recv(socket_fd, msg, UEVENT_MSG_LEN, 0)) > 0) {---------接收uevent信息。 struct cooling_device cdev; memset(&cdev, 0x0, sizeof(cdev)); if(n == UEVENT_MSG_LEN) continue; msg[n] = '\0'; msg[n+1] = '\0'; parse_uevent(msg, &cdev);---------------------------------------解析收到的uevent字符。 } } while(1); } static int open_uevent_socket(void) { struct sockaddr_nl addr; int sz = 64*1024; int s = 0; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); addr.nl_groups = 0xffffffff; s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);-------------地址族是AF_NETLINK類型的socket,協議類型是NETLINK_KOBJECT_UEVENT。 if (s < 0) { return -1; } setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)); if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {-------------將當前socket綁定到AF_NETLINK地址族,而且設置本進程爲處理消息的進程。 close(s); return -1; } return s; } static void parse_uevent(const char *msg, struct cooling_device *cdev) { while (*msg) { //printf("%s\n", msg); if (!strncmp(msg, "NAME=", 5)) { msg += 5; cdev->name = msg; } else if (!strncmp(msg, "ACTION=", 7)) { msg += 7; cdev->action = msg; } else if (!strncmp(msg, "DEVPATH=", 8)) { msg += 8; cdev->path = msg; } else if (!strncmp(msg, "STATE=", 6)) { msg += 6; cdev->state = atoi(msg); } else if (!strncmp(msg, "TEMP=", 5)) { msg += 5; cdev->temp = atoi(msg); } while(*msg++); } if(!strncmp(cdev->path, USER_COOLING_DEV, sizeof(USER_COOLING_DEV)) && !strncmp(cdev->action, "change", 5)) printf("event { name=%s, action=%s, path=%s, state=%d, temp=%d}\n", cdev->name, cdev->action, cdev->path, cdev->state, cdev->temp); }
mdev一種是附加-s主動遍歷/sys/dev下設備,另外一種是做爲hotplug處理程序,被內核uevent_helper調用到。
mdev做爲hotplug程序處理時,從環境變量中獲取參數,建立或者刪除設備,或者加載firmware。
int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int mdev_main(int argc UNUSED_PARAM, char **argv) { RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); INIT_G(); #if ENABLE_FEATURE_MDEV_CONF G.filename = "/etc/mdev.conf"; #endif bb_sanitize_stdio(); umask(0); xchdir("/dev");--------------------------------------------------當前工做目錄切換到/dev下。 if (argv[1] && strcmp(argv[1], "-s") == 0) {---------------------mdev -s狀況下遍歷/sys/dev下面全部設備。 /* * Scan: mdev -s */ 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)); #endif xstat("/", &st); G.root_major = major(st.st_dev); G.root_minor = minor(st.st_dev); putenv((char*)"ACTION=add"); /* Create all devices from /sys/dev hierarchy */ recursive_action("/sys/dev", ACTION_RECURSE | ACTION_FOLLOWLINKS, fileAction, dirAction, temp, 0);----------------這個函數是遞歸函數,掃描/sys/dev下全部文件,若是發現dev文件,則按照/etc/mdev.con文件進行相應的設置。 } else { char *fw; char *seq; char *action; char *env_devname; char *env_devpath; unsigned my_pid; unsigned seqnum = seqnum; /* for compiler */ int seq_fd; smalluint op; /* Hotplug: * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev * ACTION can be "add", "remove", "change" * DEVPATH is like "/block/sda" or "/class/input/mice" */ env_devname = getenv("DEVNAME"); /* can be NULL */----------在內核的kobject_uevent_env()中已經將參數和環境變量做爲參數傳入do_execve()中。這裏mdev能夠經過getenv來解析。 G.subsystem = getenv("SUBSYSTEM"); action = getenv("ACTION"); env_devpath = getenv("DEVPATH"); if (!action || !env_devpath /*|| !G.subsystem*/) bb_show_usage(); fw = getenv("FIRMWARE"); seq = getenv("SEQNUM"); op = index_in_strings(keywords, action);--------------------keywords僅包含add和remove,因此op也僅有OP_add和OP_remove。 ... snprintf(temp, PATH_MAX, "/sys%s", env_devpath); if (op == OP_remove) { /* Ignoring "remove firmware". It was reported * to happen and to cause erroneous deletion * of device nodes. */ if (!fw) make_device(env_devname, temp, op);-----------------在temp指定的目錄下建立env_devnam名稱的設備。 } else { make_device(env_devname, temp, op);---------------------刪除temp目錄下名稱爲env_devname的設備。 if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { if (op == OP_add && fw) load_firmware(fw, temp);------------------------將fw文件加載到temp路徑中。 } } ... } if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); return EXIT_SUCCESS; }
下面是mdev.conf配置文件的基本格式:
<device regex> <uid>:<gid> <permissions> [=path] [@|$|*<command>] <device regex> <uid>:<gid> <permissions> [>path] [@|$|*<command>] <device regex> <uid>:<gid> <permissions> [!] [@|$|*<command>]
<device regex>:設備名稱,支持正則表達式如hd[a-z][0-9]*等等。
<uid>:<gid>:用戶ID和組ID。
<permissions>:表示設備的屬性。
[=path]:若是path是個目錄(好比drivers/),則將設備節點移動到目錄下;若是path是個名稱,則將設備節點重命名爲這個名稱。
hda 0:3 660 =drivers/:移動hda到drivers目錄下。
hdb 0:3 60 =cdrom:將hdb重命名爲cdrom。
[>path]:重命名或者移動設備節點,相似於[=path]。可是同時會在/dev/下建立相關設備節點。
[!]:則不會建立設備節點。
[@<command>]:在建立設備節點以後執行command。
[$<command>]:在移動設備以前執行command。
[*<command>]:在建立設備以後以及移動設備以前都執行command。
上面的<command>經過system()調用執行,而且stdin/stdout/stderr都被重定向到/dev/null中。同時環境變量$MDEV指向匹配成功的設備節點名稱,$ACTION表示uevent動做。
udev和mdev都是使用uevent機制處理熱插拔的用戶空間程序。
可是udev經過監聽內核發送的uevent消息,解析後進行相應的熱插拔擦歐洲哦,包括建立/刪除設備節點,加載/卸載驅動程序,加載Firmware等等。
mdev則是基於uevent_helper機制,內核在發送uevent的時候,同時調用uevent_helper指向的用戶空間程序進行熱插拔處理。
另外udev是做爲一個daemon常駐內存的,一直在監聽uevent;mdev只是在須要的時候被調用。
uevent是內核發送消息到用戶空間的一種途徑,這種技術基於netlink實現。
內核中經過kobject_uevent()/kobject_uevent_env()發送uevent消息。
用戶空間使用標準的socket接口,等待接收消息,而後進行解析處理;或者經過usermode helper調用用戶空間進程mdev進行熱插拔處理,處理的方式遵循mdev.conf規則。