在分析之前,我们首先要知道uevent作用是什么。在此我们先来看一个uevent机制的框架图:
该图片来自:Linux设备模型(3)_Uevent
通过图片我们可以确定uevent的作用:设备产生上报事件时会触发uevent接口,uevent则通过netlink和kmod这两种方式把事件上报到用户空间。kmod会直接调用用户空间的程序,netlink只是将事件上报到用户空间。
之前我们分析的大部分设备驱动都会在/dev/目录下创建节点给用户使用。那么在我们调用device_create()后内核会做什么呢?
现在我们来分析device_create()的详细调用关系:
device_create() -> va_start(vargs, fmt); /* 初始化va_list可变参数变量 */ -> dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs); -> dev = kzalloc(sizeof(*dev), GFP_KERNEL); -> dev->devt = devt; /* 设置device成员 */ -> retval = device_register(dev); -> device_initialize(dev); /* 初始化device链表头 */ -> device_add(dev); /* 添加device */ -> kobject_uevent(&dev->kobj, KOBJ_ADD); -> kobject_uevent_env(kobj, action, NULL); -> env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); /* 分配环境变量 */ -> if (uevent_helper[0] && !kobj_usermode_filter(kobj)) -> argv [0] = uevent_helper; /* 下面调用的就是uevent_helper程序 */ -> call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC); /* 调用应用程序argv[0] */ -> va_end(vargs);
为了确定调用程序,我们可以在代码中添加打印语句,如8-14行:
1 if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { 2 char *argv [3]; 3 4 argv [0] = uevent_helper; 5 argv [1] = (char *)subsystem; 6 argv [2] = NULL; 7 8 int i; 9 for (i = 0; i < 2; ++i) { /* 参数 */ 10 printk("device: argv[%d] = %s\n", i, argv[i]); 11 } 12 for (i = 0; env[i]; ++i) { /* 环境变量 */ 13 printk("device: envp[%d] = %s", i, env[i]); 14 } 15 16 retval = add_uevent_var(env, "HOME=/"); 17 if (retval) 18 goto exit; 19 retval = add_uevent_var(env, 20 "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); 21 if (retval) 22 goto exit; 23 24 retval = call_usermodehelper(argv[0], argv, 25 env->envp, UMH_WAIT_EXEC); 26 }
重新编译烧写内核后,insmod某个模块后可以确定uevent_helper为/sbin/mdev
/sbin/mdev定义在busybox的mdev.c中:
我们使用SI4创建busybox工程后,打开mdev.c,分析mdev_main()函数:
1 int mdev_main(int argc, char **argv) 2 { 3 char *action; 4 char *env_path; 5 RESERVE_CONFIG_BUFFER(temp,PATH_MAX); 6 7 xchdir("/dev"); 8 9 if (argc == 2 && !strcmp(argv[1],"-s")) { /* 判断参数个数,如果不是mdev -s进入if */ 10 struct stat st; 11 12 xstat("/", &st); 13 root_major = major(st.st_dev); 14 root_minor = minor(st.st_dev); 15 16 recursive_action("/sys/block", 17 ACTION_RECURSE | ACTION_FOLLOWLINKS, 18 fileAction, dirAction, temp, 0); 19 20 recursive_action("/sys/class", 21 ACTION_RECURSE | ACTION_FOLLOWLINKS, 22 fileAction, dirAction, temp, 0); 23 24 } else { /* 热拔插mdev -s */ 25 action = getenv("ACTION"); /* 设备驱动中ACTION = add */ 26 env_path = getenv("DEVPATH"); /* DEVPATH = /class/dma_test */ 27 if (!action || !env_path) 28 bb_show_usage(); 29 30 sprintf(temp, "/sys%s", env_path); /* temp = /sys/class/dma_test */ 31 if (!strcmp(action, "remove")) 32 make_device(temp, 1); 33 else if (!strcmp(action, "add")) { 34 make_device(temp, 0); 35 36 if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) 37 load_firmware(getenv("FIRMWARE"), temp); 38 } 39 } 40 41 if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); 42 return 0; 43 }