admin管理员组

文章数量:1567003

文章参考:https://blog.csdn/newnewman80/article/details/8766657

说明

做嵌入式开发,尤其在网关、路由器或者其他支持USB设备的终端上,为了提高用户体验,我们常常需要支持自动识别并挂载USB设备功能,USB的热插拔应用广泛,比如U盘、手机、USB网卡等。
某些应用程序,在使用USB设备的过程中,也希望能够侦测到USB断开事件,不至于某些工作因为USB已经不存在而白做,或者就是需要显示U盘是否插入的图标。在Linux下,主要有两种办法检测USB热插拔。

文件夹检测方式

第一种便是定时检查/proc/scsi/目录下的文件,该文件内会按照标准格式保存着当前设备内挂载的存储介质基本信息,如果在PC端,除了硬盘(ATA)、光驱(CD-ROM)外,就是USB设备(Direct-Access)了,轮询该scsi文件,检查文件内是否新增或减少数据便可实现自动侦测USB热插拔的效果。

但是这种方法对于热插拔(hotplug)设备,如U盘,效果就没那么理想了,因为我们不知道设备什么时候插上,又是什么时候被拔掉了,只能验证当前是否已经插上或者已经拔除的事实。应用程序一般可以使用定时器处理函数不断查询,从而实现U盘拔插状态的更新。

针对上面这个问题,于是便有了另一种办法,我们采用一种特殊类的的文件描述符(套结字)专门用于Linux内核跟用户空间之间的异步通信,这种技术通常被成为NETLINK。

插入U盘情况下显示: (我的仪器中为usb-storage文件夹)

拔出U盘后显示:

netlink捕获USB插拔事件方式

Linux中使用udev来管理热插拔,使用netlink监听udev的消息可以为用户空间提供管理策略,通过解析消息中的字符串来完成控制逻辑,netlink捕获消息后,解析消息中的字符串可以进一步识别是哪种设备,从而在用户空间完成逻辑控制。

由于NETLINK是Linux内置功能,所以使用起来很简单:

  1. 创建一个AF_NETLINK协议族下NETLINK_KOBJECT_UEVENT类型的特殊文件描述符(套结字)CppLive

  2. 然后利用setsocketopt允许该文件描述符(套结字)复用其他端口,再利用band函数将自身进程绑定到特殊文件描述符(套结字)CppLive

  3. 最后利用select在while循环内监听CppLive是否可读,如果可读则调用recv接收Linux系统内核传递过来的数据并打印出来,这些输出便是USB热插拔信息,当然你也可以个性化地处理来自内核的热插拔信息,让程序变得更加智能以及人性化。

利用NETLINK检测USB热插拔的C语言实现代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#define UEVENT_BUFFER_SIZE 2048
 
int main(void)
{
    struct sockaddr_nl client;
    struct timeval tv;
    int CppLive, rcvlen, ret;
    fd_set fds;
    int buffersize = 1024;
    CppLive = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    memset(&client, 0, sizeof(client));
    client.nl_family = AF_NETLINK;
    client.nl_pid = getpid();
    client.nl_groups = 1; /* receive broadcast message*/
    setsockopt(CppLive, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
    bind(CppLive, (struct sockaddr*)&client, sizeof(client));
    while (1) {
        char buf[UEVENT_BUFFER_SIZE] = { 0 };
        FD_ZERO(&fds);
        FD_SET(CppLive, &fds);
        tv.tv_sec = 0;
        tv.tv_usec = 100 * 1000;
        ret = select(CppLive + 1, &fds, NULL, NULL, &tv);
        if(ret < 0)
            continue;
        if(!(ret > 0 && FD_ISSET(CppLive, &fds)))
            continue;
        /* receive data */
        rcvlen = recv(CppLive, &buf, sizeof(buf), 0);
        if (rcvlen > 0) {
            printf("%s\n", buf);
            /*You can do something here to make the program more perfect!!!*/
        }
    }
    close(CppLive);
    return 0;
}

本文标签: 嵌入式Linux盘拔插