一、UDEV是什麼? Udev是一個針對Linux內核2.6的可提供自動創建的設備節點和命名的解決方法的一個文件系統;其實與/etc/目錄下的fstab文件類似 二、Udev如何獲取內核這些模塊的變化信息? 參考博客:http://blog.chinaunix.net/uid-24943863-id ...
一、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的時候用一個迴圈接收更多的消息。