Libudev和sysfs指南
这是一篇译文,原文在:http://blog.csdn.net/fjb2080/article/details/7528894
在unix和类unix系统中,硬件设备可以通过/dev目录下的特殊文件进行访问,这些文件又被称为设备文件或设备节点。通过操作普通文件一样读写这些文件可以利用内核设备驱动程序与硬件设备通信,而这个过程不是读写磁盘上的数据,网上有许多描述/dev目录下文件细节的资源。以前,这些特殊文件是在系统安装的时候通过mknod命令创建的,最近几年,linux系统开始使用udev来在运行时管理/dev下的设备文件。如udev将在设备被检测到时创建设备文件并在设备移除时删除这些文件,包括热插拔设备。因此,/dev目录下的大多数设备文件只在设备的存续期内存在于系统中。
udev在/etc/udev/rules.d目录下有一个强大的脚本接口,终端用户和驱动发布者通常使用脚本文件编写udev规则来定制设备节点文件的创建。可定制的属性包括文件权限、在文件系统中的路径和符号链接。可以想象,这种方式将使用户态应用程序员对设备文件的位置和文件类型的定位变得困难,因为这些因素都可以通过修改udev脚本文件中的规则来改变。如现在,js(joystick)节点从/dev移到了/dev/input目录下,很多以前的程序直接去打开/dev目录下的设备节点文件,若这些程序在现在的系统中运行,就不能正常工作,因为/dev目录下的设备文件已经被移除了。
另外还有一个问题就是当使用同一种类型的多个设备时,它们出现在/dev目录下的编号顺序不能保证每次都是一样的。如usb设备,一些usb设备即便插在同一个usb端口,但系统重启后它的编号可能都不一样。如两个usb设备插入系统中,udev将创建/dev/ttyUSB0和/dev/ttyUSB1两个设备文件,但顺序是不确定的。这种情况可以通过编写udev规则识别设备序列号来创建符号链接解决。
还有一个问题就是出来HID设备时,通过一个/dec/hidraw0设备文件只能知道这是一个HID设备的接口,但不能确定HID设备的类型,而它有可能是任何类型的HID设备。
解决方法-sysfs
Sysfs是内核输出的一种虚拟文件系统,和/proc有点类似。在Sysfs中的文件包含了设备和驱动的信息,而且sysfs中的一些设备还可能是可写的,便于配置和控制加载到系统中设备。Sysfs总是挂载在/sys目录。
在sysfs中的目录包含了设备挂载到系统中的层次结构,如系统中的HID设备hidraw0的路径是:
/sys/devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/1-5.4:1.0/0003:04D8:003F.0001/hidraw/hidraw0
从这个实例路径可以看出,设备是加载在设备1-5的4号端口的配置1(:1.0)上,并且连接在PCI总线的USB控制器1上。这个路径显示了设备连接的控制器和总线等信息,但使用起来确很不方便,所以Sysfs提供了许多符号链接,便于访问设备文件而不用必须知道是连接在那个PCI总线和USB控制器上的。在/sys/class目录下为不同类的设备创建了各自的目录。
[zhang@localhost class]$ ll
total 0
drwxr-xr-x. 2 root root 0 Aug 27 16:05
backlight
drwxr-xr-x. 2 root root 0 Aug 27 16:10 bdi
drwxr-xr-x. 2 root root 0 Aug 27 16:05
block
drwxr-xr-x. 2 root root 0 Aug 28 05:48
bluetooth
drwxr-xr-x. 2 root root 0 Aug 27 16:05 bsg
drwxr-xr-x. 2 root root 0 Aug 27 16:05
cpuid
drwxr-xr-x. 2 root root 0 Aug 27 16:05 dma
drwxr-xr-x. 2 root root 0 Aug 27 16:05 dmi
drwxr-xr-x. 2 root root 0 Aug 27 16:05
firmware
drwxr-xr-x. 2 root root 0 Aug 27 16:05
graphics
drwxr-xr-x. 2 root root 0 Aug 27 16:05
hidraw
drwxr-xr-x. 2 root root 0 Aug 27 16:06
i2c-adapter
drwxr-xr-x. 2 root root 0 Aug 27 16:05
input
drwxr-xr-x. 2 root root 0 Aug 27 16:05 leds
drwxr-xr-x. 2 root root 0 Aug 27 16:05
mdio_bus
drwxr-xr-x. 2 root root 0 Aug 27 16:05 mem
drwxr-xr-x. 2 root root 0 Aug 27 16:10 misc
drwxr-xr-x. 2 root root 0 Aug 27 16:05 msr
drwxr-xr-x. 2 root root 0 Aug 27 16:05 mtd
drwxr-xr-x. 2 root root 0 Aug 27 16:06 net
drwxr-xr-x. 2 root root 0 Aug 27 16:05
pci_bus
drwxr-xr-x. 2 root root 0 Aug 27 16:05
pcmcia_socket
drwxr-xr-x. 2 root root 0 Aug 27 16:05
power_supply
drwxr-xr-x. 2 root root 0 Aug 27 16:05
ppdev
drwxr-xr-x. 2 root root 0 Aug 27 16:05 raw
drwxr-xr-x. 2 root root 0 Aug 27 16:05
regulator
drwxr-xr-x. 2 root root 0 Aug 28 05:48
rfkill
drwxr-xr-x. 2 root root 0 Aug 27 16:05 rtc
drwxr-xr-x. 2 root root 0 Aug 27 16:05
scsi_device
drwxr-xr-x. 2 root root 0 Aug 27 16:05
scsi_disk
drwxr-xr-x. 2 root root 0 Aug 27 16:05
scsi_generic
drwxr-xr-x. 2 root root 0 Aug 27 16:05
scsi_host
drwxr-xr-x. 2 root root 0 Aug 27 16:05
sound
drwxr-xr-x. 2 root root 0 Aug 27 16:05
spi_host
drwxr-xr-x. 2 root root 0 Aug 27 16:05
spi_transport
drwxr-xr-x. 2 root root 0 Aug 27 16:05
thermal
drwxr-xr-x. 2 root root 0 Aug 27 16:05 tty
drwxr-xr-x. 2 root root 0 Aug 27 16:05
usbmon
drwxr-xr-x. 2 root root 0 Aug 27 08:10 vc
drwxr-xr-x. 2 root root 0 Aug 27 16:05
vtconsole
查看hidraw目录下的hidraw0文件可以发现:
[zhang@localhost class]$ cd hidraw/
[zhang@localhost hidraw]$ ll
total 0
lrwxrwxrwx. 1 root root 0 Aug 27 16:05
hidraw0 ->
../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0/0003:0E0F:0003.0001/hidraw/hidraw0
lrwxrwxrwx. 1 root root 0 Aug 27 16:05
hidraw1 -> ../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.1/0003:0E0F:0003.0002/hidraw/hidraw1
因此,通过知道设备的类型而不需要知道它具体是连接到哪个PCI总线和USB控制器就可以很方便地找到设备文件。但通过设备的实际物理路径可以知道设备在系统中连接的结构层级的关系信息。
因为从应用程序来遍历Sysfs结构树是复杂而且易出错的一件事,所以提供了libudev方便的库来帮助我们完成这个任务,关于这个库的API介绍可以查看这个网址:
http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
因为对于大多数人来讲,这个API参考简介是不够的,希望通过这个文档和实例可以方便大家学会使用libudev库。在本文的剩余部分中,讲使用libudev库来访问hidraw的设备。通过libudev库,我们可以查询设备的厂家ID(Vendor ID, VID),产品ID(Product ID, PID),序列号和设备字符串等而不需要打开设备。进一步,libudev可以告诉我们在/dev目录下设备节点的具体位置路径,为应用程序提供一种具有足够鲁棒性而又和系统厂家独立的访问设备的方式。使用libudev库,需要包含libudev.h头文件,并且在编译时加上-ludev告诉编译器去链接udev库。
第一个实例将列出当前连接在系统中的所有hidraw设备,并且输出它们的设备节点路径、生产商、序列号等信息。为了获取这些信息,需要创建一个udev_enumerate对象,其中“hidraw”字符串作为过滤条件,libudev将返回所有匹配这个过滤字符串的udev_device对象。这个列子的步骤如下:
1.
初始化库,获取一个struct udev句柄
2.
枚举设备
3.
对找到的匹配设备输出它的节点名称,找到实际USB设备的起始节点,打印出USB设备的IDs和序列号等,最后解引用设备对象
4.
解引用枚举对象
5.
解引用udev对象
- #include <libudev.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <locale.h>
- #include <unistd.h>
-
- int main (void)
- {
- struct udev *udev;
- struct udev_enumerate *enumerate;
- struct udev_list_entry *devices, *dev_list_entry;
- struct udev_device *dev;
-
- /* Create the udev object */
- udev = udev_new();
- if (!udev) {
- printf("Can't create udev\n");
- exit(1);
- }
-
- /* Create a list of the devices in the 'hidraw' subsystem. */
- enumerate = udev_enumerate_new(udev);
- udev_enumerate_add_match_subsystem(enumerate, "hidraw");
- udev_enumerate_scan_devices(enumerate);
- devices = udev_enumerate_get_list_entry(enumerate);
- /* For each item enumerated, print out its information.
- udev_list_entry_foreach is a macro which expands to
- a loop. The loop will be executed for each member in
- devices, setting dev_list_entry to a list entry
- which contains the device's path in /sys. */
- udev_list_entry_foreach(dev_list_entry, devices) {
- const char *path;
-
- /* Get the filename of the /sys entry for the device
- and create a udev_device object (dev) representing it */
- path = udev_list_entry_get_name(dev_list_entry);
- dev = udev_device_new_from_syspath(udev, path);
-
- /* usb_device_get_devnode() returns the path to the device node
- itself in /dev. */
- printf("Device Node Path: %s\n", udev_device_get_devnode(dev));
-
- /* The device pointed to by dev contains information about
- the hidraw device. In order to get information about the
- USB device, get the parent device with the
- subsystem/devtype pair of "usb"/"usb_device". This will
- be several levels up the tree, but the function will find
- it.*/
- dev = udev_device_get_parent_with_subsystem_devtype(
- dev,
- "usb",
- "usb_device");
- if (!dev) {
- printf("Unable to find parent usb device.");
- exit(1);
- }
-
- /* From here, we can call get_sysattr_value() for each file
- in the device's /sys entry. The strings passed into these
- functions (idProduct, idVendor, serial, etc.) correspond
- directly to the files in the directory which represents
- the USB device. Note that USB strings are Unicode, UCS2
- encoded, but the strings returned from
- udev_device_get_sysattr_value() are UTF-8 encoded. */
- printf(" VID/PID: %s %s\n",
- udev_device_get_sysattr_value(dev,"idVendor"),
- udev_device_get_sysattr_value(dev, "idProduct"));
- printf(" %s\n %s\n",
- udev_device_get_sysattr_value(dev,"manufacturer"),
- udev_device_get_sysattr_value(dev,"product"));
- printf(" serial: %s\n",
- udev_device_get_sysattr_value(dev, "serial"));
- udev_device_unref(dev);
- }
- /* Free the enumerator object */
- udev_enumerate_unref(enumerate);
-
- udev_unref(udev);
-
- return 0;
- }
编译程序:
gcc -Wall -g -o udev_example udev_example.c -ludev
关于libudev几点注意事项:
1. Libudev的函数都是基于字符串的,所有从libudev库返回的数据都是基于文本字符串的,因为数据来源sysfs就是文本格式的。所有如果需要,则用户自己讲文本格式转化为整数类型
2. 传递给udev_device_sysattr_value()函数的字符串应和sysfs结构树中的文件名保持一致,如在这个列子中的idVendor就是:
/sys/devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/idVendor也就是符号链接:/sys/bus/usb/devices/1-5.4/idVendor
为了勒脚对于一个设备来讲哪些属性是可以访问的,可以查看设备的目录下有哪些文件存在,如在/sys/bus/usb/devices/1-5.4/ 目录下有:
1-5.4:1.0
bDeviceSubClass
configuration idProduct remove
authorized bmAttributes descriptors idVendor
serial
avoid_reset_quirk bMaxPacketSize0 dev manufacturer speed
bcdDevice bMaxPower devnum maxchild subsystem
bConfigurationValue bNumConfigurations devpath
power
uevent
bDeviceClass bNumInterfaces driver product urbnum
bDeviceProtocol busnum ep_00 quirks
version
在这个目录下的任何非目录文件或符号链接都可以传递给udev_get_sysattr_value()函数作为设备的属性值查询。
3. 所以从sysfs返回的字符串都是UTF-8格式的,若当做ASCII码会出错
4. Libudev是基于引用对象计数的。通过ref()和unref()函数(包括udev_ref()和udev_unref())被用于对一个对象的引用情况进行跟踪,当引用计数为0时,对象将被释放。当一个新的对象返回时,引用计数为1,所以需要调用unref()函数来显式释放对象。
Libudev-监视接口(Monitoring Interface)
Libudev同时提供了监视接口,当设备的状态改变时,监视接口可以向应用程序报告发生的事件,当设备加入系统或从系统移除时可以接到通知,这非常有用。就像上面例子中的枚举接口一样,监视接口也有过滤匹配机制,应用程序只需要接受它关心的事件。如当将“hidraw”加入过滤器中,只有关于hidraw的事件才会传递给应用程序。当一个设备改变状态时,函数udev_monitor_receive_device()函数返回一个关于udev_device的句柄,表示发生的事件。通过函数udev_device_get_action()可以查询发生的具体动作,函数的返回字符串如下:
add:设备连接到系统
remove:设备从系统断开连接
change:设备的状态改变
move:设备节点被移动、复制或在重新指定了父节点
函数udev_monitor_receive_device()是阻塞函数,当程序执行到这个函数时将阻塞直到有事件发生才返回。在某些情况下这是不方便的,所以udev_monitor对象提供了一个文件描述符给系统调用select(),select系统调用提供了udev_monitor_receive_device()非阻塞的用法。
下面的例子演示了udev的监视接口用法,程序执行一个循环查询select()函数等待事件的发生。若有事件发生将调用udev_monitor_receive_device()函数接受事件并打印出来。在循环中每次调用sleep()睡眠250毫秒。
- /* Set up a monitor to monitor hidraw devices */
- mon = udev_monitor_new_from_netlink(udev, "udev");
- udev_monitor_filter_add_match_subsystem_devtype(mon, "hidraw", NULL);
- udev_monitor_enable_receiving(mon);
- /* Get the file descriptor (fd) for the monitor.
- This fd will get passed to select() */
- fd = udev_monitor_get_fd(mon);
-
- /* This section will run continuously, calling usleep() at
- the end of each pass. This is to demonstrate how to use
- a udev_monitor in a non-blocking way. */
- while (1) {
- /* Set up the call to select(). In this case, select() will
- only operate on a single file descriptor, the one
- associated with our udev_monitor. Note that the timeval
- object is set to 0, which will cause select() to not
- block. */
- fd_set fds;
- struct timeval tv;
- int ret;
-
- FD_ZERO(&fds);
- FD_SET(fd, &fds);
- tv.tv_sec = 0;
- tv.tv_usec = 0;
-
- ret = select(fd+1, &fds, NULL, NULL, &tv);
-
- /* Check if our file descriptor has received data. */
- if (ret > 0 && FD_ISSET(fd, &fds)) {
- printf("\nselect() says there should be data\n");
-
- /* Make the call to receive the device.
- select() ensured that this will not block. */
- dev = udev_monitor_receive_device(mon);
- if (dev) {
- printf("Got Device\n");
- printf(" Node: %s\n", udev_device_get_devnode(dev));
- printf(" Subsystem: %s\n", udev_device_get_subsystem(dev));
- printf(" Devtype: %s\n", udev_device_get_devtype(dev));
- printf(" Action: %s\n",udev_device_get_action(dev));
- udev_device_unref(dev);
- }
- else {
- printf("No Device from receive_device(). An error occured.\n");
- }
- }
- usleep(250*1000);
- printf(".");
- fflush(stdout);
- }
在同时使用监控和枚举接口时有一点需要注意,监控必须在枚举前生效,这样在枚举期间发生的事件才不会丢失。否则如果枚举在监控前生效,则在枚举后,监控生效前的事件将丢失。
总结
Libudev接口在创建较为稳定的应用程序来访问指定的硬件设备或实时监控热插拔设备的连接和断开状态时非常有用。
- /*******************************************
- libudev example.
- This example prints out properties of
- each of the hidraw devices. It then
- creates a monitor which will report when
- hidraw devices are connected or removed
- from the system.
- This code is meant to be a teaching
- resource. It can be used for anyone for
- any reason, including embedding into
- a commercial product.
-
- The document describing this file, and
- updated versions can be found at:
- http://www.signal11.us/oss/udev/
- Alan Ott
- Signal 11 Software
- 2010-05-22 - Initial Revision
- 2010-05-27 - Monitoring initializaion
- moved to before enumeration.
- *******************************************/
- #include <libudev.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <locale.h>
- #include <unistd.h>
- int main (void)
- {
- struct udev *udev;
- struct udev_enumerate *enumerate;
- struct udev_list_entry *devices, *dev_list_entry;
- struct udev_device *dev;
- struct udev_monitor *mon;
- int fd;
-
- /* Create the udev object */
- udev = udev_new();
- if (!udev) {
- printf("Can't create udev\n");
- exit(1);
- }
- /* This section sets up a monitor which will report events when
- devices attached to the system change. Events include "add",
- "remove", "change", "online", and "offline".
-
- This section sets up and starts the monitoring. Events are
- polled for (and delivered) later in the file.
-
- It is important that the monitor be set up before the call to
- udev_enumerate_scan_devices() so that events (and devices) are
- not missed. For example, if enumeration happened first, there
- would be no event generated for a device which was attached after
- enumeration but before monitoring began.
-
- Note that a filter is added so that we only get events for
- "hidraw" devices. */
-
- /* Set up a monitor to monitor hidraw devices */
- mon = udev_monitor_new_from_netlink(udev, "udev");
- udev_monitor_filter_add_match_subsystem_devtype(mon, "hidraw", NULL);
- udev_monitor_enable_receiving(mon);
- /* Get the file descriptor (fd) for the monitor.
- This fd will get passed to select() */
- fd = udev_monitor_get_fd(mon);
- /* Create a list of the devices in the 'hidraw' subsystem. */
- enumerate = udev_enumerate_new(udev);
- udev_enumerate_add_match_subsystem(enumerate, "hidraw");
- udev_enumerate_scan_devices(enumerate);
- devices = udev_enumerate_get_list_entry(enumerate);
- /* For each item enumerated, print out its information.
- udev_list_entry_foreach is a macro which expands to
- a loop. The loop will be executed for each member in
- devices, setting dev_list_entry to a list entry
- which contains the device's path in /sys. */
- udev_list_entry_foreach(dev_list_entry, devices) {
- const char *path;
-
- /* Get the filename of the /sys entry for the device
- and create a udev_device object (dev) representing it */
- path = udev_list_entry_get_name(dev_list_entry);
- dev = udev_device_new_from_syspath(udev, path);
- /* usb_device_get_devnode() returns the path to the device node
- itself in /dev. */
- printf("Device Node Path: %s\n", udev_device_get_devnode(dev));
- /* The device pointed to by dev contains information about
- the hidraw device. In order to get information about the
- USB device, get the parent device with the
- subsystem/devtype pair of "usb"/"usb_device". This will
- be several levels up the tree, but the function will find
- it.*/
- dev = udev_device_get_parent_with_subsystem_devtype(
- dev,
- "usb",
- "usb_device");
- if (!dev) {
- printf("Unable to find parent usb device.");
- exit(1);
- }
-
- /* From here, we can call get_sysattr_value() for each file
- in the device's /sys entry. The strings passed into these
- functions (idProduct, idVendor, serial, etc.) correspond
- directly to the files in the /sys directory which
- represents the USB device. Note that USB strings are
- Unicode, UCS2 encoded, but the strings returned from
- udev_device_get_sysattr_value() are UTF-8 encoded. */
- printf(" VID/PID: %s %s\n",
- udev_device_get_sysattr_value(dev,"idVendor"),
- udev_device_get_sysattr_value(dev, "idProduct"));
- printf(" %s\n %s\n",
- udev_device_get_sysattr_value(dev,"manufacturer"),
- udev_device_get_sysattr_value(dev,"product"));
- printf(" serial: %s\n",
- udev_device_get_sysattr_value(dev, "serial"));
- udev_device_unref(dev);
- }
- /* Free the enumerator object */
- udev_enumerate_unref(enumerate);
-
- /* Begin polling for udev events. Events occur when devices
- attached to the system are added, removed, or change state.
- udev_monitor_receive_device() will return a device
- object representing the device which changed and what type of
- change occured.
- The select() system call is used to ensure that the call to
- udev_monitor_receive_device() will not block.
-
- The monitor was set up earler in this file, and monitoring is
- already underway.
-
- This section will run continuously, calling usleep() at the end
- of each pass. This is to demonstrate how to use a udev_monitor
- in a non-blocking way. */
- while (1) {
- /* Set up the call to select(). In this case, select() will
- only operate on a single file descriptor, the one
- associated with our udev_monitor. Note that the timeval
- object is set to 0, which will cause select() to not
- block. */
- fd_set fds;
- struct timeval tv;
- int ret;
-
- FD_ZERO(&fds);
- FD_SET(fd, &fds);
- tv.tv_sec = 0;
- tv.tv_usec = 0;
-
- ret = select(fd+1, &fds, NULL, NULL, &tv);
-
- /* Check if our file descriptor has received data. */
- if (ret > 0 && FD_ISSET(fd, &fds)) {
- printf("\nselect() says there should be data\n");
-
- /* Make the call to receive the device.
- select() ensured that this will not block. */
- dev = udev_monitor_receive_device(mon);
- if (dev) {
- printf("Got Device\n");
- printf(" Node: %s\n", udev_device_get_devnode(dev));
- printf(" Subsystem: %s\n", udev_device_get_subsystem(dev));
- printf(" Devtype: %s\n", udev_device_get_devtype(dev));
- printf(" Action: %s\n", udev_device_get_action(dev));
- udev_device_unref(dev);
- }
- else {
- printf("No Device from receive_device(). An error occured.\n");
- }
- }
- usleep(250*1000);
- printf(".");
- fflush(stdout);
- }
- udev_unref(udev);
- return 0;
- }
参考资料:
1.【转】跟我一起写udev规则(译)
2.详解udev