Linux应用程序利用libudev库识别USB设备

前言

应用场景:

  1. 热插拔监测USB的插拔过程。
  2. 通过设备节点字符串— /dev/ttyACM* 打开USB串口进行通信。

重点难点:

  1. libudev库在linux的安装。
  2. libudev库与gcc的交叉编译是否成功?
  3. 板子的gcc是否可以获取到动态库libudev.so
  4. libudev.a静态库怎么生成?

一、认识 libudev库

libudev库的使用主要通过调用库函数来对USB进行操作。其过程主要包括以下几个部分:

1. 初始化

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

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遍历整个链表。

3. 监控设备插拔 udev的设备插拔基于netlink实现。

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

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

二、libudev示例代码

#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) {
       
    /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */
    const char *path;
	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; 
}

三、解决libudev使用的难点

1. libudev库在linux的安装。

话不多说,先看安装过程,后面在解释:

1.1 使用默认的网络安装方式:

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

 2. 编写测试代码

 3. 测试
 
 	1)编译
 		gcc -o udevhotplug udev-hotplugin.c -ludev

  	2)以root权限执行
  		sudo ./udevhotplug
  		

通过以上方法是可以编译通过的,并且也可以运行。但是,你的开发板是否可以运行生成的文件,显然不可以。

1.2 使用交叉编译的方式安装eudev库:

安装过程所需要的资料下载:eudev库安装
提取码:1234

  1,先安装gpref工具,解压后进入该文件夹。
	    ./configure
	    ./make&&make install

  2,再安装eudev,将eudev库复制到Liunx中,解压后创建build文件夹为安装目录
	2.1 默认的gcc的安装     
	./configure
	./make&&make install
	测试:gcc -o test use_libudev.c -ludev    通过!
	
	2.2 指定gcc安装过程
	# tar zxvf eudev-3.2.5.tar.gz
	# cd eudev-3.2.5
	# mkdir build
	# export PATH=/opt/gcc-xxx-gnu/bin:$PATH
	# ./configure --build=ixxx-linux --host=xxxxx-linux --prefix=/eudev-3.2.5/build/ CC=xxx-linux-gcc  AR=xxxxx-linux-ar
	(上面的configure参数自行百度,依据自己开发板使用的gcc进行填写)
	# make  
	(make可能有錯誤-->#pragma GCC diagnostic,將macro.h中的代碼拷貝到出錯的目录下对应的macro.h文件中即可)
	(还有其他报错的,根据提示自行解决,基本上都是.c程序文件中缺点定义,补上定义即可)
	# make install 
	# ls /eudev-3.2.5/build/ 
	(bin etc include lib sbin share    (在lib文件夹有libudev.so*文件))

  3. 最后,将生成的.so文件拷贝到开发板编译器gcc库文件目录下。
	

2. 为什么使用eudev库?

  • eudev是libudev库的升级,eudev里面包含了libudev库,并且在linux安装过程中缺少的文件较少,使用更加方便。 所以建议选择eudev库进行安装。

3. libudev库与gcc的交叉编译是否成功?

  • 交叉编译是要手动安装的,即方式二。

4. 板子的gcc是否可以获取到动态库libudev.so.x ?

  • 一定要把动态库放对位置,放在开发板gcc库文件目录,若不成功、多放几个地方试一试。
  • 若采用动态库编译成功后,一定要把libudev.so.x 拷貝到开发板的linux上,放在编译成功的文件同一个目录。

5. libudev.a静态库怎么生成?

  • 在上面安装过程中,configure时使用以下方式:
   AR=xxxxx-linux-ar
  • 就已经生成libudev.a文件了,在eudev安装目录中:
# ls /eudev-3.2.5/build/ 
	(bin etc include lib sbin share    (在lib文件夹有libudev.a文件))
  • 采用静态库编译成功,不需要将静态库放在开发板的Linux上。

四、调试过程debug

枚举不到设备,监控不到热插拔?

在示例代码中,需要更改一些参数:

  • 第一处:

  • mon = udev_monitor_new_from_netlink(udev, "udev");

  • 将上面第二个参数改为 kernel

  • 即:mon = udev_monitor_new_from_netlink(udev, "kernel");

  • 第二处:

  • udev_enumerate_add_match_subsystem(enumerate, "hidraw");

  • 第二个参数改为 tty

  • 即:udev_enumerate_add_match_subsystem(enumerate, "tty");

其他需要更改的地方请自行摸索,基本没什么问题,一步枚举,一步监控。
附上libudev库函数解释地址:libudev库函数解析


最后,不要问我为什么这么做,我只是摸索出来的方式。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大头熊在学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值