Linux设备模型分析之(一):设备模型核心
Linux设备模型分析之(二):设备模型的基石
Linux设备模型分析之(三):sysfs
Linux设备模型分析之(四):class
Linux设备模型分析之(五):uevent
uevent是kobject的一部分,用于在kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。
该机制通常是用来支持热拔插设备的,例如鼠标插入后,鼠标相关的驱动软件会动态创建用于表示该鼠标的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该鼠标动态的创建设备节点。
uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,调用uevent提供的接口。uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。
其中,netlink是一种socket,专门用来进行内核空间和用户空间的通信;kmod是管理内核模块的工具集,类似busybox,我们熟悉的lsmod,insmod等是指向kmod的链接。
kobject事件有:
enum kobject_action {
KOBJ_ADD, //kobject的增添事件
KOBJ_REMOVE, //kobject的移除事件
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};
任何kobject需要上报事件时,都要调用它所从属的kset的uevent_ops操作接口,因此,如果一个kobject不属于任何kset时,是不允许发送事件。
struct kset {
......
//该kset的uevent操作函数集,
const struct kset_uevent_ops *uevent_ops;
};
struct kset_uevent_ops {
/* 当任何kobject需要上报uevent时,它所属的kset可以通过该接口过滤,阻止不希望上报的event,
从而达到从整体上管理的目的
*/
int (* const filter)(struct kset *kset, struct kobject *kobj);
//该接口可以返回kset的名称
const char *(* const name)(struct kset *kset, struct kobject *kobj);
//当任何kobject需要上报uevent时,调用该接口
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
前面有提到过,在利用kmod向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在Linux系统,可执行文件的执行,依赖于环境变量,因此kobj_uevent_env用于组织此次事件上报时的环境变量。
struct kobj_uevent_env {
char *argv[3]; // argv[0]指向用户空间的可执行文件的路径
char *envp[UEVENT_NUM_ENVP]; //指针数组,用于保存每个环境变量的地址
int envp_idx; //用于访问环境变量指针数组的index
char buf[UEVENT_BUFFER_SIZE]; //保存环境变量的buffer
int buflen;
};
kobject_uevent函数执行流程如下:
怎么指定处理uevent的用户空间程序?在kobject_uevent函数中,会调用到init_uevent_argv函数:
#ifdef CONFIG_UEVENT_HELPER
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
#endif
static int init_uevent_argv(struct kobj_uevent_env *env, const char *subsystem)
{
int len;
......
env->argv[0] = uevent_helper; //用户空间程序程序路径
env->argv[1] = &env->buf[env->buflen];
env->argv[2] = NULL;
......
return 0;
}
可见,可在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定。
也可以动态指定如(/sbin/mdev为用户空间程序):
echo /sbin/mdev > /sys/kernel/uevent_helper
或
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev
mdev是构建在linux的sysfs之上的,是一个用户程序,它能够根据系统中的硬件设备的状态动态更新设备文件。mdev是busybox自带的一个简化版的udev,它比udev占用的内存更小,因此更适合嵌入式系统的应用。
mdev原理
(1)执行mdev -s命令时,mdev会扫描/sys/class这个目录下所有class,如/sys/class/block。block class下的设备有dev属性文件的话,则以包含该dev属性文件的目录名为设备文件名,在/dev目录下创建相应的设备文件。
如下图,block class下有多个设备:
看看loop0这个设备有没dev属性文件:
dev属性文件保存着主次设备号,7为主设备号,0为次设备号,如果执行mdev -s命令,那么在/dev目录下会生成name为loop0的设备文件:
(2)当mdev因uevnet事件被调用时,mdev通过由uevent事件传递给它的环境变量获取到:引起该uevent事件的设备action及该设备所在的路径device path(对于上面举动例子,路径为sys/class/block/loop0/)。
然后判断引起该uevent事件的action是什么。若该action是add,即有新设备加入到系统中,不管该设备是虚拟设备还是实际物理设备,mdev都会通过device path路径下的dev属性文件获取到设备编号,然后以device path路径最后一个目录(即包含该dev属性文件的目录)作为设备名,在/dev目录下创建相应的设备文件。若该action是remove,即设备已从系统中移除,则删除/dev目录下以device path路径最后一个目录名称作为文件名的设备文件。如果该action既不是add也不是remove,mdev则什么都不做。
由上面可知,如果我们想在设备加入到系统中或从系统中移除时,由mdev自动地创建和删除设备文件,那么就必须做到以下两点:
- 在/sys/class 的某class目录下,创建一个以设备名device_name作为名称的目录
- 并且在该device_name目录下还必须包含一个dev属性文件,该dev属性文件以”major:minor\n”形式保存设备号。
回想起驱动程序中想自动创建设备文件,那么:
- 调用class_create函数在/sys/class目录下创建一个class;
- 在这个class下调用device_create函数创建设备
如下一个例子: