前言
应用场景:
- 热插拔监测USB的插拔过程。
- 通过设备节点字符串— /dev/ttyACM* 打开USB串口进行通信。
重点难点:
- libudev库在linux的安装。
- libudev库与gcc的交叉编译是否成功?
- 板子的gcc是否可以获取到动态库libudev.so
- 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库函数解析
最后,不要问我为什么这么做,我只是摸索出来的方式。