Linux uevent分析、使用者接收uevent以及mdev分析
關鍵詞:uevent、netlink、ADD/REMOVE/CHANGE、uevent_helper、hotplug、usermode helper、mdev、mdev.conf等等。
本文從三方面了解uevent相關内容:核心中uevent如何傳送、使用者空間如何處理uevent、如何通過mdev實作熱插拔功能。
1. Linux uevent分析
kobject_action定義了 Linux下的uevent類型;struct kerenl_uevent_env表示一個待發送的uevent。
uevent_net_init()建立發送uevent所需要的socket等資訊。
核心驅動通過kobject_uevent()/kobject_uevent_env()發送uevent到使用者空間,主要包括兩部分工作:一是通過netlink_broadcast_filtered()發送netlink消息;另一是通過call_usermodehelper_setup()/call_usermodehelper_exec()調用使用者空間程式處理uevent消息。
1.1 uevent資料結構
kobject_action定義了kobject的動作,包括ADD、REMOVE、CHANGE等等。使用者空間根據ADD或者REMOVE處理熱插拔時間,電池子產品根據CHANGE處理電量更新。
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;
};
1.2 uevent初始化
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);
1.3 對uevent_helper設定
對uevent_helper設定,可以對/proc/sys/kernel/hotplug寫可執行檔案路徑即可。
然後在核心觸發uevent事件的之後調用相關可執行檔案進行處理。
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);
1.4 usermode 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);
}
1.5 uevent發送
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機制》。
2. 使用者空間處理uevent
2.1 kernel發送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
2.2 使用者空間uevent處理
使用者空間首先建立一個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);
}
3. mdev
3.1 buxybox下mdev分析
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;
}
3.2 mdev.conf規則
下面是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動作。
3.3 mdev和udev的差別
udev和mdev都是使用uevent機制處理熱插拔的使用者空間程式。
但是udev通過監聽核心發送的uevent消息,解析後進行相應的熱插拔擦歐洲哦,包括建立/删除裝置節點,加載/解除安裝驅動程式,加載Firmware等等。
mdev則是基于uevent_helper機制,核心在發送uevent的時候,同時調用uevent_helper指向的使用者空間程式進行熱插拔處理。
另外udev是作為一個daemon常駐記憶體的,一直在監聽uevent;mdev隻是在需要的時候被調用。
4. 小結
uevent是核心發送消息到使用者空間的一種途徑,這種技術基于netlink實作。
核心中通過kobject_uevent()/kobject_uevent_env()發送uevent消息。
使用者空間使用标準的socket接口,等待接收消息,然後進行解析處理;或者通過usermode helper調用使用者空間程序mdev進行熱插拔處理,處理的方式遵循mdev.conf規則。