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的時候用一個循環接收更多的消息。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM