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遍历整个链表。
4.3 监控设备插拔 udev的设备插拔基于netlink实现。
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获取属性的名称和值。