源码:4.19.128
背景
在平常开发中偶尔会有这样的场景: 业务脚本中插入一个内核ko模块, 这个内核模块创建一个设备; 然后用户态程序立刻去访问这个内核模块创建的设备文件。 简而言之就是
insmod xxx.ko; open("/dev/xxx");
但是在一般的嵌入式系统中往往存在一个这样的情况:即内核创建设备节点后根文件系统/dev/目录下并不是会立刻创建好对应的设备节点,此时open("/dev/xxx")就会失败B并返回-ENOENT。
如何应对这种情况呢?
对于嵌入式系统而言,一般又两类方法(udev个人觉得比较适用于PC或者服务器系统,使用busybox的嵌入式系统个人没有部署起来),一个是基于mdev 一个是基于devtmpfs。
一、mdev
在内核创建好设备后,可以调用手动调用mdev 来扫描/sys目录下所创建的设备,并在/dev目录下创建对应的节点。所以可以在 insmod xxx.ko执行后,用户态调用mdev -s即可在/dev/目录下生成相应的设备节点。
此外,如果希望在内核中就完成/dev/目录下的设备节点的创建可以借助内核配置 CONFIG_UEVENT_HELPER和CONFIG_UEVENT_HELPER_PATH来完成。
图1 uvent helper
图2 hotplug程序
原理:内核使能CONFIG_UEVENT_HELPER=y后,内核在设备创建的关键函数device_add()中调用call_usermodehelper_exec(info, UMH_NO_WAIT)函数去激活call_usermodehelper_exec_work工作队列线程,然后依赖去call_usermodehelper_exec_work工作队列线程去执行/proc/sys/kernel/hotplug中存放的用户态程序。由于这里call_usermodehelper_exec(info, UMH_NO_WAIT)函数的第二个入参是UMH_NO_WAIT,因而这个流程是异步的,即并不会确保hotplug程序执行完毕后才返回。
而/proc/sys/kernel/hotplug中的程序默认为/sbin/hotplug,也可以通过CONFIG_UEVENT_HELPER_PATH在menuconfig中进行修改,例如 上图2 中就修改为/sbin/mdev; 也可以在系统启动后通过echo写入到/proc/sys/kernel/hotplug中。
二、devtmpfs
上面的CONFIG_UEVENT_HELPER虽然可以通过内核工作队列线程去执行hotplug程序来完成/dev/目录下设备节点的创建,但是这个过程是异步的----也就是说即使内核使能CONFIG_UEVENT_HELPER、配置了CONFIG_UEVENT_HELPER_PATH,但并不能够保证device_add()函数返回后/dev/目录就一定创建了对应的设备节点。 对于一些应用场景来说这个异步过程的不确定性是不友好的。
另外一种解决方案就是devtmpfs,这是一种以同步方式创建设备节点的方案。
使用方法:内核使能CONFIG_DEVTMPFS,同时确保系统挂载devtmpfs(可以通过DEVTMPFS_MOUNT [=y]来自动挂)。
图3 DEVTMPFS
原理:devtmpfs的原理是在device_add()函数中调用devtmpfs_create_node()唤醒kdevtmpfs内核线程, 然后kdevtmpfs内核线程完成设备节点的创建。
需要注意的是,在devtmpfs_create_node()唤醒kdevtmpfs内核线程完成设备节点的创建时会去等待其执行完毕(等待完成量)方可继续允许,所以这个流程是同步有的。 一旦device_add()-->devtmpfs_create_node()函数执行成功并返回后,就能够确保/dev/目录下创建了对应的设备节点。