PF_NETLINK應用實例:NETLINK_KOBJECT_UEVENT的實現

udev的文檔介紹:
  1. dynamic replacement for  /dev。作爲devfs的替代者,傳統的devfs不能動態分配major和minor的值,而major和minor非常有限,很快就會用完了。 udev能夠像DHCP動態分配IP地址一樣去動態分配major和minor。
  2. device naming。提供設備命名持久化的機制。傳統設備命名方式不具直觀性,像/dev/hda1這樣的名字肯定沒有boot_disk這樣的名字直觀。udev能夠像DNS解析域名一樣去給設備指定一個有意義的名稱。
  3. API to access info about current system devices 。提供了一組易用的API去操作sysfs,避免重複實現同樣的代碼。
  
  用戶空間的程序與設備通信的方法,主要有以下幾種方式,
  1. 通過ioperm獲取操作IO端口的權限,然後用inb/inw/ inl/ outb/outw/outl等函數,避開設備驅動程序,直接去操作IO端口。
  2. 用ioctl函數去操作/dev目錄下對應的設備,這是設備驅動程序提供的接口。像鍵盤、鼠標和觸摸屏等輸入設備一般都是這樣做的。
  3. 用write/read/mmap去操作/dev目錄下對應的設備,這也是設備驅動程序提供的接口。像framebuffer等都是這樣做的。
   #define NETLINK_ROUTE           0       /* Routing/device hook*/

        #define NETLINK_W1                1       /* 1-wire subsystem*/

        #define NETLINK_USERSOCK    2       /* Reserved for user mode socket protocols*/

        #define NETLINK_FIREWALL     3       /* Firewalling hoo*/

        #define NETLINK_INET_DIAG    4       /* INET socket monitoring */

        #define NETLINK_NFLOG           5       /* netfilter/iptables ULOG */

        #define NETLINK_XFRM             6       /* ipsec */

        #define NETLINK_SELINUX        7       /* SELinux event notifications */

        #define NETLINK_ISCSI             8       /* Open-iSCSI */

        #define NETLINK_AUDIT            9       /* auditing */

        #define NETLINK_FIB_LOOKUP       10

        #define NETLINK_CONNECTOR       11

        #define NETLINK_NETFILTER          12      /* netfilter subsystem */

        #define NETLINK_IP6_FW              13

        #define NETLINK_DNRTMSG           14      /* DECnet routing messages */

        #define NETLINK_KOBJECT_UEVENT  15      /* Kernel messages to userspace */

        #define NETLINK_GENERIC                16 


  上面的方法在大多數情況下,都可以正常工作,但是對於熱插撥(hotplug)的設備,比如像U盤,就有點困難了,因爲你不知道:什麼時候設備插上了,什麼時候設備拔掉了。這就是所謂的hotplug問題了。
  
   處理hotplug傳統的方法是,在內核中執行一個稱爲hotplug的程序,相關參數通過環境變量傳遞過來,再由hotplug通知其它關注 hotplug事件的應用程序。這樣做不但效率低下,而且感覺也不那麼優雅。新的方法是採用NETLINK實現的,這是一種特殊類型的socket,專門 用於內核空間與用戶空間的異步通信。下面的這個簡單的例子,可以監聽來自內核hotplug的事件。
  1. #include <linux/netlink.h>
  2. #include <linux/types.h>
  3. #include <arpa/inet.h>
  4. #include <netinet/in.h>
  5. #include <sys/socket.h>
  6. #include <sys/ioctl.h>
  7. #include <sys/un.h>
  8. #include <errno.h>
  9. #include <unistd.h>
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <ctype.h>


  14. #define UEVENT_BUFFER_SIZE 2048

  15. static int init_hotplug_sock(void)
  16. {
  17.     int ret;
  18.     int s =-1;
  19.     const int buffersize= 1024;
  20.     struct sockaddr_nl snl;

  21.     bzero(&snl, sizeof(struct sockaddr_nl));
  22.     snl.nl_family = AF_NETLINK;
  23.     snl.nl_pid = getpid();
  24.     snl.nl_groups = 1;

  25.     s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
  26.     if (s == -1){
  27.         perror("socket");
  28.         return -1;
  29.     }
  30.     setsockopt(s, SOL_SOCKET, SO_RCVBUF,&buffersize, sizeof(buffersize));

  31.     ret = bind(s,(struct sockaddr *)&snl, sizeof(struct sockaddr_nl));
  32.     if (ret< 0) {
  33.         perror("bind");
  34.         close(s);
  35.         return -1;
  36.     }

  37.     return s;
  38. }

  39. int main(int argc, char*argv[])
  40. {
  41.     int hotplug_sock = init_hotplug_sock();

  42.     while (1){   
  43.         char buf[UEVENT_BUFFER_SIZE * 2] = { 0 };

  44.         recv(hotplug_sock,&buf, sizeof(buf), 0);
  45.         printf("%s\n", buf);

  46.         /* USB 設備的插拔會出現字符信息,通過比較不同的信息確定特定設備的插拔,在這添加比較代碼*/
  47.     }

  48.     return 0;
  49. }
編譯:
  1. $ gcc -g hotplug.c -o hotplug
執行:
  1. $ ./hotplug
  2. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0
  3. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1
  4. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1
  5. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0

udev的主體部分在udevd.c文件中,它主要監控來自4個文件描述符的事件/消息,並做出處理:
  1. 來自客戶端的控制消息。這通常由udevcontrol命令通過地址爲/org/kernel/udev/udevd的本地socket,向udevd發送的控制消息。其中消息類型有:
  l UDEVD_CTRL_STOP_EXEC_QUEUE 停止處理消息隊列。
  l UDEVD_CTRL_START_EXEC_QUEUE 開始處理消息隊列。
  l UDEVD_CTRL_SET_LOG_LEVEL 設置LOG的級別。
  l UDEVD_CTRL_SET_MAX_CHILDS 設置最大子進程數限制。好像沒有用。
  l UDEVD_CTRL_SET_MAX_CHILDS_RUNNING 設置最大運行子進程數限制(遍歷proc目錄下所有進程,根據session的值判斷)。
  l UDEVD_CTRL_RELOAD_RULES 重新加載配置文件。
   2. 來自內核的hotplug事件。如果有事件來源於hotplug,它讀取該事件,創建一個udevd_uevent_msg對象,記錄當前的消息序列號, 設置消息的狀態爲EVENT_QUEUED,然後並放入running_list和exec_list兩個隊列中,稍後再進行處理。
  3. 來自signal handler中的事件。signal  handler是異步執行的,即使有signal產生,主進程的select並不會喚醒,爲了喚醒主進程的select,它建立了一個管道,在 signal handler中,向該管道寫入長度爲1個子節的數據,這樣就可以喚醒主進程的select了。
  4. 來自配置文件變化的事件。udev通過文件系統inotify功能,監控其配置文件目錄/etc/udev/rules.d,一旦該目錄中文件有變化,它就重新加載配置文件。
  
  其中最主要的事件,當然是來自內核的hotplug事件,如何處理這些事件是udev的關鍵。udev本身並不知道如何處理這些事件,也沒有必要知道,因爲它只實現機制,而不實現策略。事件的處理是由配置文件決定的,這些配置文件即所謂的rule。
  
  關於rule的編寫方法可以參考《writing_udev_rules》,udev_rules.c實現了對規則的解析。
  
  在規則中,可以讓外部應用程序處理某個事件,這有兩種方式,一種是直接執行命令,通常是讓modprobe去加載驅動程序,或者讓mount去加載分區。另外一種是通過本地socket發送消息給某個應用程序。
  
  在udevd.c:udev_event_process函數中,我們可以看到,如果RUN參數以”socket:”開頭則認爲是發到socket,否則認爲是執行指定的程序。
  
  下面的規則是執行指定程序:
  60-pcmcia.rules: RUN+="/sbin/modprobe pcmcia"
  
  下面的規則是通過socket發送消息:
  90-hal.rules:RUN+="socket:/org/freedesktop/hal/udev_event"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章