Libudev和sysfs指南

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对象


  1. #include <libudev.h>

  2. #include <stdio.h>

  3. #include <stdlib.h>

  4. #include <locale.h>

  5. #include <unistd.h>

  6.  

  7. int main (void)

  8. {

  9.  struct udev *udev;

  10.  struct udev_enumerate *enumerate;

  11.  struct udev_list_entry *devices, *dev_list_entry;

  12.  struct udev_device *dev;

  13.  

  14.  /* Create the udev object */

  15.  udev = udev_new();

  16.  if (!udev) {

  17.   printf("Can't create udev\n");

  18.   exit(1);

  19.  }

  20.  

  21.  /* Create a list of the devices in the 'hidraw' subsystem. */

  22.  enumerate = udev_enumerate_new(udev);

  23.  udev_enumerate_add_match_subsystem(enumerate, "hidraw");

  24.  udev_enumerate_scan_devices(enumerate);

  25.  devices = udev_enumerate_get_list_entry(enumerate);

  26.  /* For each item enumerated, print out its information.

  27.     udev_list_entry_foreach is a macro which expands to

  28.     a loop. The loop will be executed for each member in

  29.     devices, setting dev_list_entry to a list entry

  30.     which contains the device's path in /sys. */

  31.  udev_list_entry_foreach(dev_list_entry, devices) {

  32.   const char *path;

  33.  

  34.   /* Get the filename of the /sys entry for the device

  35.      and create a udev_device object (dev) representing it */

  36.   path = udev_list_entry_get_name(dev_list_entry);

  37.   dev = udev_device_new_from_syspath(udev, path);

  38.  

  39.   /* usb_device_get_devnode() returns the path to the device node

  40.      itself in /dev. */

  41.   printf("Device Node Path: %s\n", udev_device_get_devnode(dev));

  42.  

  43.   /* The device pointed to by dev contains information about

  44.      the hidraw device. In order to get information about the

  45.      USB device, get the parent device with the

  46.      subsystem/devtype pair of "usb"/"usb_device". This will

  47.      be several levels up the tree, but the function will find

  48.      it.*/

  49.   dev = udev_device_get_parent_with_subsystem_devtype(

  50.          dev,

  51.          "usb",

  52.          "usb_device");

  53.   if (!dev) {

  54.    printf("Unable to find parent usb device.");

  55.    exit(1);

  56.   }

  57.  

  58.   /* From here, we can call get_sysattr_value() for each file

  59.      in the device'/sys entry. The strings passed into these

  60.      functions (idProduct, idVendor, serial, etc.) correspond

  61.      directly to the files in the directory which represents

  62.      the USB device. Note that USB strings are Unicode, UCS2

  63.      encoded, but the strings returned from

  64.      udev_device_get_sysattr_value() are UTF-8 encoded. */

  65.   printf(" VID/PID: %s %s\n",

  66.           udev_device_get_sysattr_value(dev,"idVendor"),

  67.           udev_device_get_sysattr_value(dev, "idProduct"));

  68.   printf(" %s\n %s\n",

  69.           udev_device_get_sysattr_value(dev,"manufacturer"),

  70.           udev_device_get_sysattr_value(dev,"product"));

  71.   printf(" serial: %s\n",

  72.            udev_device_get_sysattr_value(dev, "serial"));

  73.   udev_device_unref(dev);

  74.  }

  75.  /* Free the enumerator object */

  76.  udev_enumerate_unref(enumerate);

  77.  

  78.  udev_unref(udev);

  79.  

  80.  return 0; 

  81. }

编译程序:

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毫秒。


  1. /* Set up a monitor to monitor hidraw devices */
  2.  mon = udev_monitor_new_from_netlink(udev, "udev");
  3.  udev_monitor_filter_add_match_subsystem_devtype(mon, "hidraw", NULL);
  4.  udev_monitor_enable_receiving(mon);
  5.  /* Get the file descriptor (fd) for the monitor.
  6.     This fd will get passed to select() */
  7.  fd = udev_monitor_get_fd(mon);
  8.  
  9.  /* This section will run continuously, calling usleep() at
  10.     the end of each pass. This is to demonstrate how to use
  11.     a udev_monitor in a non-blocking way. */
  12.  while (1) {
  13.   /* Set up the call to select(). In this case, select() will
  14.      only operate on a single file descriptor, the one
  15.      associated with our udev_monitor. Note that the timeval
  16.      object is set to 0, which will cause select() to not
  17.      block. */
  18.   fd_set fds;
  19.   struct timeval tv;
  20.   int ret;
  21.   
  22.   FD_ZERO(&fds);
  23.   FD_SET(fd, &fds);
  24.   tv.tv_sec = 0;
  25.   tv.tv_usec = 0;
  26.   
  27.   ret = select(fd+1, &fds, NULL, NULL, &tv);
  28.   
  29.   /* Check if our file descriptor has received data. */
  30.   if (ret > 0 && FD_ISSET(fd, &fds)) {
  31.    printf("\nselect() says there should be data\n");
  32.    
  33.    /* Make the call to receive the device.
  34.       select() ensured that this will not block. */
  35.    dev = udev_monitor_receive_device(mon);
  36.    if (dev) {
  37.     printf("Got Device\n");
  38.     printf(" Node: %s\n", udev_device_get_devnode(dev));
  39.     printf(" Subsystem: %s\n", udev_device_get_subsystem(dev));
  40.     printf(" Devtype: %s\n", udev_device_get_devtype(dev));

  41.     printf(" Action: %s\n",udev_device_get_action(dev));
  42.     udev_device_unref(dev);
  43.    }
  44.    else {
  45.     printf("No Device from receive_device(). An error occured.\n");
  46.    } 
  47.   }
  48.   usleep(250*1000);
  49.   printf(".");
  50.   fflush(stdout);
  51.  }

在同时使用监控和枚举接口时有一点需要注意,监控必须在枚举前生效,这样在枚举期间发生的事件才不会丢失。否则如果枚举在监控前生效,则在枚举后,监控生效前的事件将丢失。

 

总结

Libudev接口在创建较为稳定的应用程序来访问指定的硬件设备或实时监控热插拔设备的连接和断开状态时非常有用。


  1. /*******************************************
  2.  libudev example.

  3.  This example prints out properties of
  4.  each of the hidraw devices. It then
  5.  creates a monitor which will report when
  6.  hidraw devices are connected or removed
  7.  from the system.

  8.  This code is meant to be a teaching
  9.  resource. It can be used for anyone for
  10.  any reason, including embedding into
  11.  a commercial product.
  12.  
  13.  The document describing this file, and
  14.  updated versions can be found at:
  15.     http://www.signal11.us/oss/udev/

  16.  Alan Ott
  17.  Signal 11 Software
  18.  2010-05-22 - Initial Revision
  19.  2010-05-27 - Monitoring initializaion
  20.               moved to before enumeration.
  21. *******************************************/

  22. #include <libudev.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <locale.h>
  26. #include <unistd.h>

  27. int main (void)
  28. {
  29.     struct udev *udev;
  30.     struct udev_enumerate *enumerate;
  31.     struct udev_list_entry *devices, *dev_list_entry;
  32.     struct udev_device *dev;

  33.        struct udev_monitor *mon;
  34.     int fd;
  35.     
  36.     /* Create the udev object */
  37.     udev = udev_new();
  38.     if (!udev) {
  39.         printf("Can't create udev\n");
  40.         exit(1);
  41.     }

  42.     /* This section sets up a monitor which will report events when
  43.      devices attached to the system change. Events include "add",
  44.      "remove", "change", "online", and "offline".
  45.      
  46.      This section sets up and starts the monitoring. Events are
  47.      polled for (and delivered) later in the file.
  48.      
  49.      It is important that the monitor be set up before the call to
  50.      udev_enumerate_scan_devices() so that events (and devices) are
  51.      not missed. For example, if enumeration happened first, there
  52.      would be no event generated for a device which was attached after
  53.      enumeration but before monitoring began.
  54.      
  55.      Note that a filter is added so that we only get events for
  56.      "hidraw" devices. */
  57.     
  58.     /* Set up a monitor to monitor hidraw devices */
  59.     mon = udev_monitor_new_from_netlink(udev, "udev");
  60.     udev_monitor_filter_add_match_subsystem_devtype(mon, "hidraw", NULL);
  61.     udev_monitor_enable_receiving(mon);
  62.     /* Get the file descriptor (fd) for the monitor.
  63.      This fd will get passed to select() */
  64.     fd = udev_monitor_get_fd(mon);


  65.     /* Create a list of the devices in the 'hidraw' subsystem. */
  66.     enumerate = udev_enumerate_new(udev);
  67.     udev_enumerate_add_match_subsystem(enumerate, "hidraw");
  68.     udev_enumerate_scan_devices(enumerate);
  69.     devices = udev_enumerate_get_list_entry(enumerate);
  70.     /* For each item enumerated, print out its information.
  71.      udev_list_entry_foreach is a macro which expands to
  72.      a loop. The loop will be executed for each member in
  73.      devices, setting dev_list_entry to a list entry
  74.      which contains the device's path in /sys. */
  75.     udev_list_entry_foreach(dev_list_entry, devices) {
  76.         const char *path;
  77.         
  78.         /* Get the filename of the /sys entry for the device
  79.          and create a udev_device object (dev) representing it */
  80.         path = udev_list_entry_get_name(dev_list_entry);
  81.         dev = udev_device_new_from_syspath(udev, path);

  82.         /* usb_device_get_devnode() returns the path to the device node
  83.          itself in /dev. */
  84.         printf("Device Node Path: %s\n", udev_device_get_devnode(dev));

  85.         /* The device pointed to by dev contains information about
  86.          the hidraw device. In order to get information about the
  87.          USB device, get the parent device with the
  88.          subsystem/devtype pair of "usb"/"usb_device". This will
  89.          be several levels up the tree, but the function will find
  90.          it.*/
  91.         dev = udev_device_get_parent_with_subsystem_devtype(
  92.          dev,
  93.          "usb",
  94.          "usb_device");
  95.         if (!dev) {
  96.             printf("Unable to find parent usb device.");
  97.             exit(1);
  98.         }
  99.     
  100.         /* From here, we can call get_sysattr_value() for each file
  101.          in the device'/sys entry. The strings passed into these
  102.          functions (idProduct, idVendor, serial, etc.) correspond
  103.          directly to the files in the /sys directory which
  104.          represents the USB device. Note that USB strings are
  105.          Unicode, UCS2 encoded, but the strings returned from
  106.          udev_device_get_sysattr_value() are UTF-8 encoded. */
  107.         printf(" VID/PID: %s %s\n",
  108.          udev_device_get_sysattr_value(dev,"idVendor"),
  109.          udev_device_get_sysattr_value(dev, "idProduct"));
  110.         printf(" %s\n %s\n",
  111.          udev_device_get_sysattr_value(dev,"manufacturer"),
  112.          udev_device_get_sysattr_value(dev,"product"));
  113.         printf(" serial: %s\n",
  114.          udev_device_get_sysattr_value(dev, "serial"));
  115.         udev_device_unref(dev);
  116.     }
  117.     /* Free the enumerator object */
  118.     udev_enumerate_unref(enumerate);
  119.     
  120.     /* Begin polling for udev events. Events occur when devices
  121.      attached to the system are added, removed, or change state. 
  122.      udev_monitor_receive_device() will return a device
  123.      object representing the device which changed and what type of
  124.      change occured.

  125.      The select() system call is used to ensure that the call to
  126.      udev_monitor_receive_device() will not block.
  127.      
  128.      The monitor was set up earler in this file, and monitoring is
  129.      already underway.
  130.      
  131.      This section will run continuously, calling usleep() at the end
  132.      of each pass. This is to demonstrate how to use a udev_monitor
  133.      in a non-blocking way. */
  134.     while (1) {
  135.         /* Set up the call to select(). In this case, select() will
  136.          only operate on a single file descriptor, the one
  137.          associated with our udev_monitor. Note that the timeval
  138.          object is set to 0, which will cause select() to not
  139.          block. */
  140.         fd_set fds;
  141.         struct timeval tv;
  142.         int ret;
  143.         
  144.         FD_ZERO(&fds);
  145.         FD_SET(fd, &fds);
  146.         tv.tv_sec = 0;
  147.         tv.tv_usec = 0;
  148.         
  149.         ret = select(fd+1, &fds, NULL, NULL, &tv);
  150.         
  151.         /* Check if our file descriptor has received data. */
  152.         if (ret > 0 && FD_ISSET(fd, &fds)) {
  153.             printf("\nselect() says there should be data\n");
  154.             
  155.             /* Make the call to receive the device.
  156.              select() ensured that this will not block. */
  157.             dev = udev_monitor_receive_device(mon);
  158.             if (dev) {
  159.                 printf("Got Device\n");
  160.                 printf(" Node: %s\n", udev_device_get_devnode(dev));
  161.                 printf(" Subsystem: %s\n", udev_device_get_subsystem(dev));
  162.                 printf(" Devtype: %s\n", udev_device_get_devtype(dev));

  163.                 printf(" Action: %s\n", udev_device_get_action(dev));
  164.                 udev_device_unref(dev);
  165.             }
  166.             else {
  167.                 printf("No Device from receive_device(). An error occured.\n");
  168.             }                    
  169.         }
  170.         usleep(250*1000);
  171.         printf(".");
  172.         fflush(stdout);
  173.     }


  174.     udev_unref(udev);

  175.     return 0; 
  176. }

参考资料:

1.【转】跟我一起写udev规则(译)

2.详解udev

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值