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获取属性的名称和值。