linux使用udev获取热插拔(hotplug)事件

匿名 (未验证) 提交于 2019-12-02 21:59:42

udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等,设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev同时提供了监视接口,当设备的状态改变时,监视接口可以向应用程序报告发生的事件,当设备加入系统或从系统移除时都可以接到通知。
udev只支持linux-2.6及以上版本的内核,因为udev严重依赖于sysfs文件系统提供的信息,而sysfs文件系统只在linux-2.6内核中才有。
udev能够实现所有devfs实现的功能。但udev运行在用户模式中,而devfs运行在内核模式中。

作用:
1. 动态创建或删除设备文件
2. 遍历sysfs设备文件
3. hotplug(利用netlink)

使用udev需要先安装libudev库,在程序中包含libudev.h头文件,并且在编译时加上-ludev告诉编译器去链接udev库。

1. 安装libudev
sudo apt-get install libudev-dev

2. 编写测试代码udev-hotplugin.c

#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <signal.h> #include <sys/time.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/select.h> #include <linux/types.h> #include <linux/netlink.h> #include <libudev.h>   #undef asmlinkage #ifdef __i386__ #define asmlinkage __attribute__((regparm(0))) #else #define asmlinkage  #endif   static int udev_exit;  static void asmlinkage sig_handler(int signum) { 	if (signum == SIGINT || signum == SIGTERM) 		udev_exit = 1; }   static void print_device(struct udev_device *device, const char *source, int env) { 	struct timeval tv; 	struct timezone tz; 	gettimeofday(&tv, &tz);  	printf("%-6s[%llu.%06u] %-8s %s (%s)\n", 	       source, 	       (unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec, 	       udev_device_get_action(device), 	       udev_device_get_devpath(device), 	       udev_device_get_subsystem(device));  	if (env) { 		struct udev_list_entry *list_entry; 		udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) 			printf("%s=%s\n", 			       udev_list_entry_get_name(list_entry), 			       udev_list_entry_get_value(list_entry)); 		printf("\n"); 	}  }   int udevadm_monitor(struct udev *udev) { 	struct sigaction act; 	int env = 0; 	int print_kernel = 1; 	struct udev_monitor *kernel_monitor = NULL; 	fd_set readfds; 	int rc = 0;  	if (getuid() != 0) { 		fprintf(stderr, "root privileges needed to subscribe to kernel events\n"); 		goto out; 	}  	/* set signal handlers */ 	memset(&act, 0x00, sizeof(struct sigaction)); 	act.sa_handler = (void (*)(int)) sig_handler; 	sigemptyset(&act.sa_mask); 	act.sa_flags = SA_RESTART; 	sigaction(SIGINT, &act, NULL); 	sigaction(SIGTERM, &act, NULL);  	printf("monitor will print the received events.\n"); 	 	if (print_kernel) { 		kernel_monitor = udev_monitor_new_from_netlink(udev, "udev"); //这里的udev源码中没有"udev"这个参数,不加进去返回值就为NULL,所以要加这个 		if (kernel_monitor == NULL) { 			rc = 3; 			printf("udev_monitor_new_from_netlink() error\n"); 			goto out; 		}  		if (udev_monitor_enable_receiving(kernel_monitor) < 0) { 			rc = 4; 			goto out; 		}  		printf("UEVENT the kernel uevent: \n"); 	} 	 	printf("\n"); 	while (!udev_exit) { 		int fdcount; 		FD_ZERO(&readfds);  		if (kernel_monitor != NULL) 			FD_SET(udev_monitor_get_fd(kernel_monitor), &readfds);  		fdcount = select(udev_monitor_get_fd(kernel_monitor)+1, &readfds, NULL, NULL, NULL); 		if (fdcount < 0) { 			if (errno != EINTR) 				fprintf(stderr, "error receiving uevent message: %m\n"); 			continue; 		}  		if ((kernel_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(kernel_monitor), &readfds)) { 			struct udev_device *device; 			device = udev_monitor_receive_device(kernel_monitor); 			if (device == NULL) 				continue; 			print_device(device, "UEVENT", env); 			udev_device_unref(device); 		}  	}  out: 	udev_monitor_unref(kernel_monitor); 	return rc; }    int main(int argc, char *argv[]) { 	struct udev *udev; 	int rc = 1;  	udev = udev_new(); 	if (udev == NULL) 		goto out;  	udevadm_monitor(udev); 	goto out; 	rc = 2;  out: 	udev_unref(udev); 	return rc; }


3. 测试
1)编译
gcc -o udevhotplug udev-hotplugin.c -ludev
2)以root权限执行
sudo ./udevhotplug
当插拔一个USB设备时,显示如下:


4. libudev API介绍
4.1 初始化
首先调用udev_new,创建一个udev library context。udev library context采用引用记数机制,创建的context默认引用记数为1,使用udev_ref和udev_unref增加或减少引用记数,如果引用记数为0,则释放内部资源。

4.2 枚举设备
使用udev_enumrate_new创建一个枚举器,用于扫描系统已接设备。使用udev_enumrate_ref和udev_enumrate_unref增加或减少引用记数。
使用udev_enumrate_add_match/nomatch_xxx系列函数增加枚举的过滤器,过滤关键字以字符表示,如"block"设备。
使用udev_enumrate_scan_xxx系列函数扫描/sys目录下,所有与过滤器匹配的设备。扫描完成后的数据结构是一个链表,使用udev_enumerate_get_list_entry获取链表的首个结点,使用udev_list_entry_foreach遍历整个链表。

1)使用udev_monitor_new_from_netlink创建一个新的monitor,函数的第二个参数是事件源的名称,可选"kernel"或"udev"。基于"kernel"的事件通知要早于"udev",但相关的设备结点未必创建完成,所以一般应用的设计要基于"udev"进行监控。
2)使用udev_monitor_filter_add_match_subsystem_devtype增加一个基于设备类型的udev事件过滤器,例如: "block"设备。
3)使用udev_monitor_enable_receiving启动监控过程。监控可以使用udev_monitor_get_fd获取一个文件描述符,基于返回的fd可以执行poll操作,简化程序设计。
4)插拔事件到达后,可以使用udev_monitor_receive_device获取产生事件的设备映射。调用udev_device_get_action可以获得一个字符串:"add"或者"remove",以及"change", "online", "offline"等,但后三个未知什么情况下会产生。

4.4 获取设备信息
使用udev_list_entry_get_name可以得到一个设备结点的sys路径,基于这个路径使用udev_device_new_from_syspath可以创建一个udev设备的映射,用于获取设备属性。获取设备属性使用udev_device_get_properties_list_entry,返回一个存储了设备所有属性信息的链表,使用udev_list_entry_foreach遍历链表,使用udev_list_entry_get_name和udev_list_entry_get_value获取属性的名称和值。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!