linux 设备管理 (五) uevent

内核中的uevent

uevent(user space event) 是 内核 提供的一个机制,属于 kobject 中的一个技术实现,代码在lib/kobject_uevent.c
Uevent是Kobject的一部分,用于在Kobject状态发生改变时,通知用户空间进程
改变是什么
	ADD/REMOVE,Kobject(或上层数据结构)的添加/移除事件。
	ONLINE/OFFLINE,Kobject(或上层数据结构)的上线/下线事件,其实是是否使能。
	CHANGE,Kobject(或上层数据结构)的状态或者内容发生改变。
	MOVE,Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)。
	CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。


1.内核如何向用户空间通信(上报uevent事件)
	lib/kobject_uevent.c
		kobject_uevent
		kobject_uevent_env

2.用户空间控制内核给用户空间通信
	sysfs 下的很多 kobject 下都有 uevent 属性,它主要用于内核与udev/mdev或其他用户空间进程之间的一个通信接口,将sysfs挂载到/sys, uevent属性就变为了可见的文件
	内核将 uevent 机制暴露给了用户空间(对应/sys下的uevent文件) , 让用户空间控制内核给用户空间通信
	在每个注册的device文件夹下会生成一个uevent属性文件
		drivers/base/core.c
			uevent_store
			uevent_show
			static DEVICE_ATTR_RW(uevent);

	/sys下的uevent文件支持写入的参数有 "add","remove","change","move","online","offline"
	写入 "add",这样内核就会调用 uevent_store -> kobject_uevent


A.通信中的信息内容
	???

内核uevent的消费者

1.内核如何向用户空间通信 的消费者
	主要用于设备驱动模型中热插拔设备或设备状态变化时。
	该机制通常是用来支持热拔插设备的,
	device_create
	    device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	        device_register(dev);
	            device_add(dev);
	                kobject_uevent(&dev->kobj, KOBJ_ADD);
	                    kobject_uevent_env(kobj, action, NULL);   // action = KOBJ_ADD
	
2.用户空间控制内核给用户空间通信 的消费者
	可用于实现手动触发hotplug(手动触发hotplug就是coldplug)
	可用于 udevmonitor 通过内核向 udevd (udev 后台程序)发送消息
	也可用于检查设备本身所支持的 netlink 消息上的环境变量
	也可用于开发人员调试 udev 规则文件,udevtrigger 这个调试工具本身就是以写各设备的 uevent 属性文件实现的。

内核uevent的性能

无论是usermodehelper还是netlink的方式,uevent对系统性能是有影响的。
特别是netlink的方式,上报的event会通知到所有的用户进程,所以还是少用为妙

内核uevent的底层实现


当消费者调用kobject_uevent上报事件时,kobject_uevent会根据配置不同,主要有两个代码路径
	一种是通过kmod模块,直接调用用户空间的可执行文件;
	一种是通过netlink通信机制,将事件从内核空间传递给用户空间
  • kmod
Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数
调用用户空间的可执行文件(或者脚本,简称uevent helper,目前2021118日 一般为mdev)处理该event。

kobject_uevent(&dev->kobj, KOBJ_ADD);
    kobject_uevent_env(kobj, action, NULL);   // action = KOBJ_ADD
        const char *action_string = kobject_actions[action];    // action_string = "add"
        //把相关信息存到环境变量里,环境变量是内核在调用mdev之前设置的
            // ACTION代表操作类型
            // DEVPATH为设备在class下存在的路径
            // SUBSYSTEM为class_create创建的设备类
        //ACTION=add , DEVPATH=/class/test/test_dev , SUBSYSTEM=test
        add_uevent_var(env, "ACTION=%s", action_string);
        add_uevent_var(env, "DEVPATH=%s", devpath);
        add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
        init_uevent_argv
        /* 调用应用程序:比如mdev */
        /* 在/etc/init.d/rcS 中的echo /sbin/mdev > /proc/sys/kernel/hotplug指定了应用程序*/
        call_usermodehelper_setup
        call_usermodehelper_exec

从上面的调用关系可以看出,最终内核会调用/proc/sys/kernel/hotplug 中指定的
程序(一般会配置为/sbin/mdev),并且使用该程序的环境变量

  • netlink
Netlink套接字
	是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC)
	也是网络应用程序与内核通信的最常用的接口。
	是一种特殊的 socket
	是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大
	是一种在内核与用户应用间进行双向数据传输的非常好的方式
Netlink套接字的消费者
	目前在Linux 内核中使用netlink 进行应用与内核通信的应用很多
Netlink套接字实例
	路由 daemon(NETLINK_ROUTE),
	用户态 socket 协议(NETLINK_USERSOCK),
	防火墙(NETLINK_FIREWALL),
	netfilter 子系统(NETLINK_NETFILTER),
	内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),
	通用 netlink(NETLINK_GENERIC)

Netlink使用实例
	用户态	
		应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,
	内核态
		需要使用专门的内核 API 来使用 netlink。


Netlink特点
	1.netlink使用简单,只需要在include/linux/netlink.h中增加一个新类型的 netlink 协议定义即可,(如 #define NETLINK_TEST 20 )
		然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换);
	2.netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中
		发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息;
	3.使用 netlink 的内核部分可以采用模块的方式实现
		使用 netlink 的应用部分和内核部分没有编译时依赖;
	4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组
		属于该neilink 组的任何内核模块或应用都能接收到该消息
		内核事件向用户态的通知机制就使用了这一特性
	5.内核可以使用 netlink 首先发起会话


对应内核uevent的用户空间实现

  • mdev ----对应kmod

1. mdev怎么处理coldplug的
	只要是用户空间处理创建设备文件,就会有冷插拔的问题
	在启动脚本rcS中会有这样一句命令/sbin/mdev -s
		1. 扫描/sys目录里所有的uevent属性文件,向其写入"add”命令,触发uevent事件
		2. 内核调用 call_usermodehelper (argv[0], argv, envp, 0); fork mdev
		3. mdev 根据 参数  创建设备文件

2. mdev 怎么处理hotplug // mdev 不会自动加载驱动
	1. 内核创建device,同时触发uevent事件
	2. 内核调用 call_usermodehelper (argv[0], argv, envp, 0); fork mdev
	3. mdev 根据 参数  创建设备文件


A.mdev的函数调用路径
	int mdev_main(int argc UNUSED_PARAM, char **argv)
	    xchdir("/dev");  // 先把目录改变到/dev下
	
	    if (argv[1] && strcmp(argv[1], "-s") == 0) {
    
      // 对应coldplug,这个路径 是 初始化脚本里面 mdev -s  走的路径
	    	// 在文件系统启动的时候会调用 mdev -s,创建所有驱动设备节点
	        putenv((char*)"ACTION=add"); // mdev -s 的动作是创建设备节点,所以为add
	
	        if (access("/sys/class/block", F_OK) != 0) {
    
     // 当/sys/class/block目录不存在时,才扫描/sys/block
	         /* Scan obsolete /sys/block only if /sys/class/block
	          * doesn't exist. Otherwise we'll have dupes.
	          * Also, do not complain if it doesn't exist.
	          * Some people configure kernel to have no blockdevs.
	          */
	         recursive_action("/sys/block",
	         ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
	         fileAction, dirAction, temp, 0);
	       }
	
	       /* 
	        * 这个函数是递归函数,它会扫描/sys/class目录下的所有文件,如果发现dev文件,将按照
	        * /etc/mdev.conf文件进行相应的配置。如果没有配置文件,那么直接创建设备节点 
	        * 最终调用的创建函数是 make_device
	        */
	       recursive_action("/sys/class",    
	       ACTION_RECURSE | ACTION_FOLLOWLINKS,
	       fileAction, dirAction, temp, 0);
	
	    }else{
    
     // 对应hotplug,这个路径 是 内核 kmod 机制 uevent_helper 走的路径
	        // 获得环境变量,环境变量是内核在调用mdev之前设置的
	        env_devname = getenv("DEVNAME"); /* can be NULL */
	        G.subsystem = getenv("SUBSYSTEM");
	        action = getenv("ACTION");
	        env_devpath = getenv("DEVPATH");
	
	        snprintf(temp, PATH_MAX, "/sys%s", env_devpath);
	
	        make_device(env_devname, temp, op);
	    }

  • udev ----对应netlink
是 netlink 对应的用户空间进程实现
具体请查看 https://blog.csdn.net/u011011827/article/details/112741721

猜你喜欢

转载自blog.csdn.net/u011011827/article/details/112763385
今日推荐