udev实现热插拔

一、UDEV是什么?

Udev是一个针对Linux内核2.6的可提供自动创建的设备节点和命名的解决方法的一个文件系统;其实与/etc/目录下的fstab文件类似

二、Udev如何获取内核这些模块的变化信息?

参考博客:http://blog.chinaunix.net/uid-24943863-id-3223000.html

设备节点的创建,是通过sysfs接口分析dev文件取得设备节点号,这个很显而易见。那么udevd是通过什么机制来得知内核里模块的变化情况,如何得知设备的插入移除情况呢?当然是通过hotplug机制了,那hotplug又是怎么实现的?或者说内核是如何通知用户空间一个事件的发生的呢?

答案是通过netlink socket通讯,在内核和用户空间之间传递信息。

新的Linux内核使用udev代替了hotplug作为热拔插管理,虽然有udevd管理热拔插,但有时候我们还是需要在应用程序中检测热拔插事件以便快速地处理,比如在读写SD卡的时候拔下SD卡,那么需要立即检测出该情况,然后结束读写线程,防止VFS崩溃。Netlink是面向数据包的服务,为内核与用户层搭建了一个高速通道,是udev实现的基础。该工作方式是异步的,用户空间程序不必使用轮询等技术来检测热拔插事件

内核中使用uevent事件通知用户空间,uevent首先在内核中调用netlink_kernel_create()函数创建一个socket套接字,该函数原型在netlink.h有定义,其类型是表示往用户空间发送消息的NETLINK_KOBJECT_UEVENT,groups=1,由于uevent只往用户空间发送消息而不接受,因此其输入回调函数input和cb_mutex都设置为NULL。

struct sock *netlink_kernel_create(struct net *net,int unit,unsigned int groups,

                                                  void (*input)(struct sk_buff *skb),

                                                  struct mutex *cb_mutex,

                                                  struct module *module);

ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, 1, NULL, NULL, THIS_MODULE);

当有事件发生的时候,调用 kobject_uevent()函数,实际上最终是调用 netlink_broadcast_filtered(uevent_sock, skb , 0, 1, GFP_KERNEL , kobj_bcast_filter, kobj);

完成广播任务。

  用户空间程序只需要创建一个socket描述符,将描述符绑定到接收地址,就可以实现热拔插事件的监听了。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <errno.h>
 5 #include <sys/types.h>
 6 #include <asm/types.h>
 7 //该头文件需要放在netlink.h前面防止编译出现__kernel_sa_family未定义
 8 #include <sys/socket.h>  
 9 #include <linux/netlink.h>
10 
11 void MonitorNetlinkUevent()
12 {
13     int sockfd;
14     struct sockaddr_nl sa;
15     int len;
16     char buf[4096];
17     struct iovec iov;
18     struct msghdr msg;
19     int i;
20 
21     memset(&sa,0,sizeof(sa));
22     sa.nl_family=AF_NETLINK;
23     sa.nl_groups=NETLINK_KOBJECT_UEVENT;
24     sa.nl_pid = 0;//getpid(); both is ok
25     memset(&msg,0,sizeof(msg));
26     iov.iov_base=(void *)buf;
27     iov.iov_len=sizeof(buf);
28     msg.msg_name=(void *)&sa;
29     msg.msg_namelen=sizeof(sa);
30     msg.msg_iov=&iov;
31     msg.msg_iovlen=1;
32 
33     sockfd=socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);
34     if(sockfd==-1)
35         printf("socket creating failed:%s\n",strerror(errno));
36     if(bind(sockfd,(struct sockaddr *)&sa,sizeof(sa))==-1)
37         printf("bind error:%s\n",strerror(errno));
38 
39     len=recvmsg(sockfd,&msg,0);
40     if(len<0)
41         printf("receive error\n");
42     else if(len<32||len>sizeof(buf))
43         printf("invalid message");
44     for(i=0;i<len;i++)
45         if(*(buf+i)=='\0')
46             buf[i]='\n';
47     printf("received %d bytes\n%s\n",len,buf);
48 }
49 
50 int main(int argc,char **argv)
51 {
52     MonitorNetlinkUevent();
53     return 0;
54 }

创建socket描述符的时候指定协议族为AF_NETLINK或者PF_NETLINK,套接字type选择SOCK_RAW或者SOCK_DGRAM,Netlink协议并不区分这两种类型,第三个参数协议填充NETLINK_KOBJECT_UEVENT表示接收内核uevent信息。接着就绑定该文件描述符到sockadd_nl,注意该结构体nl_groups是接收掩码,取~0是将接收所有来自内核的消息,我们接收热拔插只需要NETLINK_KOBJECT_UEVENT即可。接下来调用recvmsg开始接收内核消息,recvmsg函数需要我们填充message报头,包括指定接收缓存等工作。该函数会阻塞直到有热拔插事件产生。

运行程序,然后我插入一个U盘,得到下面的结果:

$ ./netlink 

received 289 bytes

add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1

ACTION=add

DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1

SUBSYSTEM=usb

MAJOR=189

MINOR=8

DEVNAME=bus/usb/001/009

DEVTYPE=usb_device

DEVICE=/proc/bus/usb/001/009

PRODUCT=781/5530/100

TYPE=0/0/0

BUSNUM=001

DEVNUM=009

SEQNUM=2306

运行程序,拔掉U盘

$ ./netlink 

received 294 bytes

remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1/1-1.1:1.0/host10/target10:0:0/10:0:0:0/bsg/10:0:0:0

ACTION=remove

DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1/1-1.1:1.0/host10/target10:0:0/10:0:0:0/bsg/10:0:0:0

SUBSYSTEM=bsg

MAJOR=253

MINOR=2

DEVNAME=bsg/10:0:0:0

SEQNUM=2345

程序正确地接收到了U盘热拔插事件,通过该信息用户程序可以在第一时间得到事件通知。事实上热拔插的时候产生的消息可不止一条呢,可以在revmsg的时候用一个循环接收更多的消息。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏圣杰的专栏

RabbitMQ知多少

1.引言 RabbitMQ——Rabbit Message Queue的简写,但不能仅仅理解其为消息队列,消息代理更合适。RabbitMQ 是一个由 Erlan...

2007
来自专栏Android机动车

Android热修复简单总结

DexClassLoader -> DexPathList -> DexFile -> -> Element -> dexElements.add(elemen...

682
来自专栏Android 研究

OKHttp源码解析(一)--初阶

这段时间老李的新公司要更换网络层,知道现在主流网络层的模式是RxJava+Retrofit+OKHttp,所以老李开始研究这三个项目的源代码,在更换网络层后,开...

853
来自专栏程序猿DD

Spring Cloud实战小贴士:turbine如何聚合设置了context-path的hystrix数据

之前在spring for all社区看到这样一个问题:当actuator端点设置了context-path之后,turbine如何聚合数据?首先,我们要知道a...

2707
来自专栏扎心了老铁

redis事务

本文记录一些redis事务相关的原理。 1、基本概念 1)什么是redis的事务? 简单理解,可以认为redis事务是一些列redis命令的集合,并且有如下两个...

2604
来自专栏全栈架构

Elasticsearch Java API 索引的增删改查(二)

Index API 允许我们存储一个JSON格式的文档,使数据可以被搜索。文档通过index、type、id唯一确定。我们可以自己提供一个id,或者也使用Ind...

431
来自专栏CaiRui

flask + pymysql操作Mysql数据库

安装flask-sqlalchemy、pymysql模块 pip install flask-sqlalchemy pymysql  ### Flask-SQL...

3549
来自专栏iKcamp

系列3|走进Node.js之多进程模型

文:正龙(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 之前的文章“走进Node.js之HTTP实现分析”中,大家已经了解 Node.js 是...

3437
来自专栏Android先生

OKHttp源码解析--初阶

这段时间老李的新公司要更换网络层,知道现在主流网络层的模式是RxJava+Retrofit+OKHttp,所以老李开始研究这三个项目的源代码,在更换网络层后,开...

562
来自专栏Flutter入门到实战

实现Android主流网络框架封装,可无缝侵入切换框架

先来描述一下需求,比如说,目前正在用的http请求是Volley,现在发现使用OkHttp来封装一套会更好。又比方说,目前正在用的数据存储方案是OrmLite,...

712

扫描关注云+社区