linux 內核與用戶空間通訊之netlink使用方法

轉自:http://blog.csdn.net/haomcu/article/details/7371835linux

 

Linux中的進程間通訊機制源自於Unix平臺上的進程通訊機制。Unix的兩大分支AT&T Unix和BSD Unix在進程通訊實現機制上的各有所不一樣,前者造成了運行在單個計算機上的System V IPC,後者則實現了基於socket的進程間通訊機制。同時Linux也遵循IEEE制定的Posix IPC標準,在三者的基礎之上實現瞭如下幾種主要的IPC機制:管道(Pipe)及命名管道(Named Pipe),信號(Signal),消息隊列(Message queue),共享內存(Shared Memory),信號量(Semaphore),套接字(Socket)。經過這些IPC機制,用戶空間進程之間能夠完成互相通訊。爲了完成內核空間用戶空間通訊,Linux提供了基於socket的Netlink通訊機制,能夠實現內核與用戶空間數據的及時交換。緩存

本文第2節概述相關研究工做,第3節與其餘IPC機制對比,詳細介紹Netlink機制及其關鍵技術,第4節使用KGDB+GDB組合調試,經過一個示例程序演示Netlink通訊過程。第5節作總結並指出Netlink通訊機制的不足之處。安全

2 相關研究

到目前Linux提供了9種機制完成內核與用戶空間的數據交換,分別是內核啓動參數、模塊參數與 sysfs、sysctl、系統調用、netlink、procfs、seq_file、debugfs和relayfs,其中模塊參數與sysfs、procfs、debugfs、relayfs是基於文件系統的通訊機制,用於內核空間向用戶控件輸出信息;sysctl、系統調用是由用戶空間發起的通訊機制。因而可知,以上均爲單工通訊機制,在內核空間與用戶空間的雙向互動數據交換上略顯不足。Netlink是基於socket的通訊機制,因爲socket自己的雙共性、突發性、不阻塞特色,所以可以很好的知足內核與用戶空間小量數據的及時交互,所以在Linux 2.6內核中普遍使用,例如SELinux,Linux系統的防火牆分爲內核態的netfilter和用戶態的iptables,netfilter與iptables的數據交換就是經過Netlink機制完成。 網絡

3 Netlink機制及其關鍵技術

3.1 Netlink機制

Linux操做系統中當CPU處於內核狀態時,能夠分爲有用戶上下文的狀態和執行硬件、軟件中斷兩種。其中當處於有用戶上下文時,因爲內核態和用戶態的內存映射機制不一樣,不可直接將本地變量傳給用戶態的內存區;處於硬件、軟件中斷時,沒法直接向用戶內存區傳遞數據,代碼執行不可中斷。針對傳統的進程間通訊機制,他們均沒法直接在內核態和用戶態之間使用,緣由以下表:數據結構

通訊方法dom

沒法介於內核態與用戶態的緣由異步

管道(不包括命名管道)socket

侷限於父子進程間的通訊。函數

消息隊列post

在硬、軟中斷中沒法無阻塞地接收數據。

信號量

沒法介於內核態和用戶態使用。

內存共享

須要信號量輔助,而信號量又沒法使用。

套接字

在硬、軟中斷中沒法無阻塞地接收數據。

1*(引自 參考文獻5)

    解決內核態和用戶態通訊機制可分爲兩類:

  1. 處於有用戶上下文時,可使用Linux提供的copy_from_user()和copy_to_user()函數完成,但因爲這兩個函數可能阻塞,所以不能在硬件、軟件的中斷過程當中使用。
  2. 處於硬、軟件中斷時。

2.1   能夠經過Linux內核提供的spinlock自旋鎖實現內核線程與中斷過程的同步,因爲內核線程運行在有上下文的進程中,所以能夠在內核線程中使用套接字或消息隊列來取得用戶空間的數據,而後再將數據經過臨界區傳遞給中斷過程.

2.2   經過Netlink機制實現。Netlink 套接字的通訊依據是一個對應於進程的標識,通常定爲該進程的 ID。Netlink通訊最大的特色是對對中斷過程的支持,它在內核空間接收用戶空間數據時再也不須要用戶自行啓動一個內核線程,而是經過另外一個軟中斷調用用戶事先指定的接收函數。經過軟中斷而不是自行啓動內核線程保證了數據傳輸的及時性。

3.2 Netlink優勢

Netlink相對於其餘的通訊機制具備如下優勢:

  1. 使用Netlink經過自定義一種新的協議並加入協議族便可經過socket API使用Netlink協議完成數據交換,而ioctl和proc文件系統均須要經過程序加入相應的設備或文件。
  2. Netlink使用socket緩存隊列,是一種異步通訊機制,而ioctl是同步通訊機制,若是傳輸的數據量較大,會影響系統性能。
  3. Netlink支持多播,屬於一個Netlink組的模塊和進程都能得到該多播消息。
  4. Netlink容許內核發起會話,而ioctl和系統調用只能由用戶空間進程發起。

在內核源碼有關Netlink協議的頭文件中包含了內核預約義的協議類型,以下所示:

 

[cpp]  view plain  copy
 
  1. #define NETLINK_ROUTE         0     
  2.   
  3. #define NETLINK_W1             1      
  4.   
  5. #define NETLINK_USERSOCK     2      
  6.   
  7. #define NETLINK_FIREWALL      3       
  8.   
  9. #define NETLINK_INET_DIAG     4         
  10.   
  11. #define NETLINK_NFLOG         5        
  12.   
  13. #define NETLINK_XFRM          6        
  14.   
  15. #define NETLINK_SELINUX       7        
  16.   
  17. #define NETLINK_ISCSI           8        
  18.   
  19. #define NETLINK_AUDIT          9        
  20.   
  21. #define NETLINK_FIB_LOOKUP    10  
  22.   
  23. #define NETLINK_CONNECTOR    11  
  24.   
  25. #define NETLINK_NETFILTER      12       
  26.   
  27. #define NETLINK_IP6_FW          13  
  28.   
  29. #define NETLINK_DNRTMSG       14       
  30.   
  31. #define NETLINK_KOBJECT_UEVENT 15       
  32.   
  33. #define NETLINK_GENERIC        16  

 

 

上述這些協議已經爲不一樣的系統應用所使用,每種不一樣的應用都有特有的傳輸數據的格式,所以若是用戶不使用這些協議,須要加入本身定義的協議號。對於每個Netlink協議類型,能夠有多達 32多播組,每個多播組用一個位表示,Netlink 的多播特性使得發送消息給同一個組僅須要一次系統調用,於是對於須要多撥消息的應用而言,大大地下降了系統調用的次數。

創建Netlink會話過程以下:

 

內核使用與標準socket API相似的一套API完成通訊過程。首先經過netlink_kernel_create()建立套接字,該函數的原型以下:

[cpp]  view plain  copy
 
  1. struct sock *netlink_kernel_create(struct net *net,  
  2.   
  3.                   int unit,unsigned int groups,  
  4.   
  5.                   void (*input)(struct sk_buff *skb),  
  6.   
  7.                   struct mutex *cb_mutex,  
  8.   
  9.                   struct module *module);  

 

其中net參數是網絡設備命名空間指針,input函數是netlink socket在接受到消息時調用的回調函數指針,module默認爲THIS_MODULE.

而後用戶空間進程使用標準Socket API來建立套接字,將進程ID發送至內核空間,用戶空間建立使用socket()建立套接字,該函數的原型以下:

int socket(int domain, int type, int protocol);

其中domain值爲PF_NETLINK,即Netlink使用協議族。protocol爲Netlink提供的協議或者是用戶自定義的協議,Netlink提供的協議包括NETLINK_ROUTE, NETLINK_FIREWALL, NETLINK_ARPD, NETLINK_ROUTE6和 NETLINK_IP6_FW。

接着使用bind函數綁定。Netlink的bind()函數把一個本地socket地址(源socket地址)與一個打開的socket進行關聯。完成綁定,內核空間接收到用戶進程ID以後即可以進行通信。

用戶空間進程發送數據使用標準socket API中sendmsg()函數完成,使用時需添加struct msghdr消息和nlmsghdr消息頭。一個netlink消息體由nlmsghdr和消息的payload部分組成,輸入消息後,內核會進入nlmsghdr指向的緩衝區。

內核空間發送數據使用獨立建立的sk_buff緩衝區,Linux定義了以下宏方便對於緩衝區地址的設置,以下所示:

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))

在對緩衝區設置完成消息地址以後,可使用netlink_unicast()來發布單播消息,netlink_unicast()原型以下:

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

參數sk爲函數netlink_kernel_create()返回的socket,參數skb存放消息,它的data字段指向要發送的netlink消息結構,而skb的控制塊保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用於方便設置該控制塊,參數pid爲接收消息進程的pid,參數nonblock表示該函數是否爲非阻塞,若是爲1,該函數將在沒有接收緩存可利用時當即返回,而若是爲0,該函數在沒有接收緩存可利用時睡眠。

內核模塊或子系統也可使用函數netlink_broadcast來發送廣播消息:

void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);

前面的三個參數與netlink_unicast相同,參數group爲接收消息的多播組,該參數的每個表明一個多播組,所以若是發送給多個多播組,就把該參數設置爲多個多播組組ID的位或。參數allocation爲內核內存分配類型,通常地爲GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不能夠睡眠),而GFP_KERNEL用於非原子上下文。

接收數據時程序須要申請足夠大的空間來存儲netlink消息頭和消息的payload部分。而後使用標準函數接口recvmsg()來接收netlink消息

4 Netlink通訊過程

調試平臺:Vmware 5.5 + Fedora Core 10(兩臺,一臺做爲host機,一臺做爲target機)。

調試程序:分爲內核模塊和用戶空間程序兩部分,當內核模塊被加載後,運行用戶空間程序,由用戶空間發起Netlink會話,和內核模塊進行數據交換。

被加載的內核模塊沒法經過外加的調試器進行調試,KGDB提供了一種內核源碼級別的調試機制。Linux內核自2.6.26版本以後在內核中內置了KGDB選項,編譯內核時須要選擇與之相關的選項,調試時host端需使用帶有符號表的vmlinz內核,target端使用gdb調試用戶空間的程序。

用戶空間程序關鍵代碼以下:

[cpp]  view plain  copy
 
  1. int send_pck_to_kern(u8 op, const u8 *data, u16 data_len)  
  2.   
  3. {  
  4.   
  5.     struct user_data_ *pck;  
  6.   
  7.     int ret;  
  8.   
  9.    
  10.   
  11.     pck = (struct user_data_*)calloc(1, sizeof(*pck) + data_len);  
  12.   
  13.     if(!pck) {  
  14.   
  15.        printf("calloc in %s failed!!!\n", __FUNCTION__);  
  16.   
  17.        return -1;  
  18.   
  19.     }  
  20.   
  21.    
  22.   
  23.     pck->magic_num = MAGIC_NUM_RNQ;  
  24.   
  25.     pck->op = op;  
  26.   
  27.     pck->data_len = data_len;  
  28.   
  29.     memcpy(pck->data, data, data_len);  
  30.   
  31.    
  32.   
  33.     ret = send_to_kern((const u8*)pck, sizeof(*pck) + data_len);  
  34.   
  35.     if(ret)  
  36.   
  37.        printf("send_to_kern in %s failed!!!\n", __FUNCTION__);  
  38.   
  39.      
  40.   
  41.     free(pck);  
  42.   
  43.    
  44.   
  45.     return ret ? -1 : 0;  
  46.   
  47. }  
  48.   
  49.    
  50.   
  51. static void recv_from_nl()  
  52.   
  53. {  
  54.   
  55.     char buf[1000];  
  56.   
  57.     int len;  
  58.   
  59.     struct iovec iov = {buf, sizeof(buf)};  
  60.   
  61.     struct sockaddr_nl sa;  
  62.   
  63.     struct msghdr msg;  
  64.   
  65.     struct nlmsghdr *nh;  
  66.   
  67.    
  68.   
  69.     memset(&msg, 0, sizeof(msg));  
  70.   
  71.     msg.msg_name = (void *)&sa;  
  72.   
  73.     msg.msg_namelen = sizeof(sa);  
  74.   
  75.     msg.msg_iov = &iov;  
  76.   
  77.     msg.msg_iovlen = 1;  
  78.   
  79.    
  80.   
  81.     //len = recvmsg(nl_sock, &msg, 0);  
  82.   
  83.     len = recvmsg(nl_sock, &msg, 0);  
  84.   
  85.    
  86.   
  87.     for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);  
  88.   
  89.            nh = NLMSG_NEXT (nh, len)) {  
  90.   
  91.        // The end of multipart message.  
  92.   
  93.        if (nh->nlmsg_type == NLMSG_DONE) {  
  94.   
  95.            puts("nh->nlmsg_type == NLMSG_DONE");  
  96.   
  97.            return;  
  98.   
  99.        }  
  100.   
  101.    
  102.   
  103.        if (nh->nlmsg_type == NLMSG_ERROR) {  
  104.   
  105.            // Do some error handling.  
  106.   
  107.            puts("nh->nlmsg_type == NLMSG_ERROR");  
  108.   
  109.            return;  
  110.   
  111.        }  
  112.   
  113.    
  114.   
  115.    
  116.   
  117. #if 1  
  118.   
  119.        puts("Data received from kernel:");  
  120.   
  121.        hex_dump((u8*)NLMSG_DATA(nh), NLMSG_PAYLOAD(nh, 0));  
  122.   
  123. #endif  
  124.   
  125.     }  
  126.   
  127. }  


內核模塊須要防止資源搶佔,保證Netlink資源互斥佔有,內核模塊部分關鍵代碼以下:

 

[cpp]  view plain  copy
 
  1. static void nl_rcv(struct sk_buff *skb)  
  2.   
  3. {  
  4.   
  5.     mutex_lock(&nl_mtx);  
  6.   
  7.    
  8.   
  9.     netlink_rcv_skb(skb, &nl_rcv_msg);  
  10.   
  11.    
  12.   
  13.     mutex_unlock(&nl_mtx);  
  14.   
  15. }  
  16.   
  17.    
  18.   
  19.    
  20.   
  21. static int nl_send_msg(const u8 *data, int data_len)  
  22.   
  23. {  
  24.   
  25.     struct nlmsghdr *rep;  
  26.   
  27.     u8 *res;  
  28.   
  29.     struct sk_buff *skb;  
  30.   
  31.    
  32.   
  33.     if(g_pid < 0 || g_nl_sk == NULL) {  
  34.   
  35.        printk("Invalid parameter, g_pid = %d, g_nl_sk = %p\n",  
  36.   
  37.                      g_pid, g_nl_sk);  
  38.   
  39.        return -1;  
  40.   
  41.     }  
  42.   
  43.    
  44.   
  45.     skb = nlmsg_new(data_len, GFP_KERNEL);  
  46.   
  47.     if(!skb) {  
  48.   
  49.        printk("nlmsg_new failed!!!\n");  
  50.   
  51.        return -1;  
  52.   
  53.     }  
  54.   
  55.    
  56.   
  57.     if(g_debug_level > 0) {  
  58.   
  59.        printk("Data to be send to user space:\n");  
  60.   
  61.        hex_dump((void*)data, data_len);  
  62.   
  63.     }  
  64.   
  65.    
  66.   
  67.     rep = __nlmsg_put(skb, g_pid, 0, NLMSG_NOOP, data_len, 0);  
  68.   
  69.     res = nlmsg_data(rep);  
  70.   
  71.     memcpy(res, data, data_len);  
  72.   
  73.     netlink_unicast(g_nl_sk, skb, g_pid, MSG_DONTWAIT);  
  74.   
  75.    
  76.   
  77.     return 0;  
  78.   
  79. }  
  80.   
  81.    
  82.   
  83. static int nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)  
  84.   
  85. {  
  86.   
  87.     const u8 res_data[] = "Hello, user";  
  88.   
  89.     size_t data_len;  
  90.   
  91.      
  92.   
  93.     u8 *buf;  
  94.   
  95.     struct user_data_ *pck;  
  96.   
  97.     struct user_req *req, *match = NULL;  
  98.   
  99.      
  100.   
  101.      
  102.   
  103.     g_pid = NETLINK_CB(skb).pid;  
  104.   
  105.    
  106.   
  107.     buf = (u8*)NLMSG_DATA(nlh);  
  108.   
  109.     data_len = nlmsg_len(nlh);  
  110.   
  111.    
  112.   
  113.     if(data_len < sizeof(struct user_data_)) {  
  114.   
  115.        printk("Too short data from user space!!!\n");  
  116.   
  117.        return -1;  
  118.   
  119.     }  
  120.   
  121.    
  122.   
  123.     pck = (struct user_data_ *)buf;  
  124.   
  125.     if(pck->magic_num != MAGIC_NUM_RNQ) {  
  126.   
  127.        printk("Magic number not matched!!!\n");  
  128.   
  129.        return -1;  
  130.   
  131.     }  
  132.   
  133.    
  134.   
  135.     if(g_debug_level > 0) {  
  136.   
  137.        printk("Data from user space:\n");  
  138.   
  139.        hex_dump(buf, data_len);  
  140.   
  141.     }  
  142.   
  143.    
  144.   
  145.    
  146.   
  147.     req = user_reqs;  
  148.   
  149.     while(req->op) {  
  150.   
  151.        if(req->op == pck->op) {  
  152.   
  153.            match = req;  
  154.   
  155.            break;  
  156.   
  157.        }  
  158.   
  159.    
  160.   
  161.        req++;  
  162.   
  163.     }  
  164.   
  165.    
  166.   
  167.     if(match) {  
  168.   
  169.        match->handler(buf, data_len);  
  170.   
  171.     }  
  172.   
  173.    
  174.   
  175.    
  176.   
  177.     nl_send_msg(res_data, sizeof(res_data));  
  178.   
  179.    
  180.   
  181.     return 0;  
  182.   
  183. }  

 

 

5.其餘相關說明

 

    Netlink 是一種特殊的 socket,它是 Linux 所特有的,相似於 BSD 中的AF_ROUTE 但又遠比它的功能強大,目前在最新的 Linux 內核(2.6.14)中使用netlink 進行應用與內核通訊的應用不少,包括:路由 daemon(NETLINK_ROUTE),1-wire 子系統(NETLINK_W1),用戶態 socket 協議(NETLINK_USERSOCK),防火牆(NETLINK_FIREWALL),socket 監視(NETLINK_INET_DIAG),netfilter 日誌(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系統(NETLINK_ISCSI),進程審計(NETLINK_AUDIT),轉發信息表查詢(NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系統(NETLINK_NETFILTER),IPv6 防火牆(NETLINK_IP6_FW),DECnet 路由信息(NETLINK_DNRTMSG),內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)。

    Netlink 是一種在內核與用戶應用間進行雙向數據傳輸的很是好的方式,用戶態應用使用標準的 socket API 就可使用 netlink 提供的強大功能,內核態須要使用專門的內核 API 來使用 netlink。

Netlink 相對於系統調用,ioctl 以及 /proc 文件系統而言具備如下優勢:

    1,爲了使用 netlink,用戶僅須要在 include/linux/netlink.h 中增長一個新類型的 netlink 協議定義便可, 如 #define NETLINK_MYTEST 17 而後,內核和用戶態應用就能夠當即經過 socket API 使用該 netlink 協議類型進行數據交換。但系統調用須要增長新的系統調用,ioctl 則須要增長設備或文件, 那須要很多代碼,proc 文件系統則須要在 /proc 下添加新的文件或目錄,那將使原本就混亂的 /proc 更加混亂。

    2. netlink是一種異步通訊機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不須要等待接收者收到消息,但系統調用與 ioctl 則是同步通訊機制,若是傳遞的數據太長,將影響調度粒度。

    3.使用 netlink 的內核部分能夠採用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴,但系統調用就有依賴,並且新的系統調用的實現必須靜態地鏈接到內核中,它沒法在模塊中實現,使用新系統調用的應用在編譯時須要依賴內核。

    4.netlink 支持多播,內核模塊或應用能夠把消息多播給一個netlink組,屬於該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性,任何對內核事件感興趣的應用都能收到該子系統發送的內核事件,在後面的文章中將介紹這一機制的使用。

    5.內核可使用 netlink 首先發起會話,但系統調用和 ioctl 只能由用戶應用發起調用。

    6.netlink 使用標準的 socket API,所以很容易使用,但系統調用和 ioctl則須要專門的培訓才能使用。

用戶態使用 netlink

   用戶態應用使用標準的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket,查詢手冊頁能夠了解這些函數的使用細節,本文只是講解使用 netlink 的用戶應該如何使用這些函數。注意,使用 netlink 的應用必須包含頭文件 linux/netlink.h。固然 socket 須要的頭文件也必不可少,sys/socket.h。

   爲了建立一個 netlink socket,用戶須要使用以下參數調用 socket():

 socket(AF_NETLINK, SOCK_RAW, netlink_type)

 

 

   第一個參數必須是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它們倆實際爲一個東西,它表示要使用netlink,第二個參數必須是SOCK_RAW或SOCK_DGRAM,第三個參數指定netlink協議類型,如前面講的用戶自定義協議類型NETLINK_MYTEST, NETLINK_GENERIC是一個通用的協議類型,它是專門爲用戶使用的,所以,用戶能夠直接使用它,而沒必要再添加新的協議類型。內核預約義的協議類型有:

[cpp]  view plain  copy
 
  1. #define NETLINK_ROUTE 0  
  2. #define NETLINK_W1 1  
  3. #define NETLINK_USERSOCK 2   
  4. #define NETLINK_FIREWALL 3  
  5. #define NETLINK_INET_DIAG 4  
  6. #define NETLINK_NFLOG 5  
  7. #define NETLINK_XFRM 6   
  8. #define NETLINK_SELINUX 7   
  9. #define NETLINK_ISCSI 8   
  10. #define NETLINK_AUDIT 9   
  11. #define NETLINK_FIB_LOOKUP 10   
  12. #define NETLINK_CONNECTOR 11   
  13. #define NETLINK_NETFILTER 12   
  14. #define NETLINK_IP6_FW 13   
  15. #define NETLINK_DNRTMSG 14   
  16. #define NETLINK_KOBJECT_UEVENT 15   
  17. #define NETLINK_GENERIC 16  

 

對於每個netlink協議類型,能夠有多達 32多播組,每個多播組用一個位表示,netlink 的多播特性使得發送消息給同一個組僅須要一次系統調用,於是對於須要多撥消息的應用而言,大大地下降了系統調用的次數。

   函數 bind() 用於把一個打開的 netlink socket 與 netlink 源 socket 地址綁定在一塊兒。netlink socket 的地址結構以下:

[cpp]  view plain  copy
 
  1. struct sockaddr_nl {  
  2.    sa_family_t nl_family;  
  3.    unsigned short nl_pad;  
  4.    __u32 nl_pid;  
  5.    __u32 nl_groups;  
  6.  };  

 

字段 nl_family 必須設置爲 AF_NETLINK 或着 PF_NETLINK,字段 nl_pad 當前沒有使用,所以要老是設置爲 0,字段 nl_pid 爲接收或發送消息的進程的 ID,若是但願內核處理消息或多播消息,就把該字段設置爲 0,不然設置爲處理消息的進程 ID。字段 nl_groups 用於指定多播組,bind 函數用於把調用進程加入到該字段指定的多播組,若是設置爲 0,表示調用者不加入任何多播組。

   傳遞給 bind 函數的地址的 nl_pid 字段應當設置爲本進程的進程 ID,這至關於 netlink socket 的本地地址。可是,對於一個進程的多個線程使用 netlink socket 的狀況,字段 nl_pid 則能夠設置爲其它的值,如:

pthread_self() << 16 | getpid();

 

 

   所以字段 nl_pid 實際上未必是進程 ID,它只是用於區分不一樣的接收者或發送者的一個標識,用戶能夠根據本身須要設置該字段。函數 bind 的調用方式以下:

bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));

 

 

   fd爲前面的 socket 調用返回的文件描述符,參數 nladdr 爲 struct sockaddr_nl 類型的地址。爲了發送一個 netlink 消息給內核或其餘用戶態應用,須要填充目標 netlink socket 地址,此時,字段 nl_pid 和 nl_groups 分別表示接收消息者的進程 ID 與多播組。若是字段 nl_pid 設置爲 0,表示消息接收者爲內核或多播組,若是 nl_groups爲 0,表示該消息爲單播消息,不然表示多播消息。使用函數 sendmsg 發送 netlink 消息時還須要引用結構 struct msghdr、struct nlmsghdr 和 struct iovec,結構 struct msghdr 需以下設置:

[cpp]  view plain  copy
 
  1. struct msghdr msg;   
  2. memset(&msg, 0, sizeof(msg));   
  3. msg.msg_name = (void *)&(nladdr);   
  4. msg.msg_namelen = sizeof(nladdr);  

 

其中 nladdr 爲消息接收者的 netlink 地址。

   struct nlmsghdr 爲 netlink socket 本身的消息頭,這用於多路複用和多路分解 netlink 定義的全部協議類型以及其它一些控制,netlink 的內核實現將利用這個消息頭來多路複用和多路分解已經其它的一些控制,所以它也被稱爲netlink 控制塊。所以,應用在發送 netlink 消息時必須提供該消息頭。

[cpp]  view plain  copy
 
  1. struct nlmsghdr {  
  2.     __u32 nlmsg_len;   
  3.     __u16 nlmsg_type;   
  4.     __u16 nlmsg_flags;  
  5.     __u32 nlmsg_seq;  
  6.     __u32 nlmsg_pid;  
  7. };  


字段 nlmsg_len 指定消息的總長度,包括緊跟該結構的數據部分長度以及該結構的大小,字段 nlmsg_type 用於應用內部定義消息的類型,它對 netlink 內核實現是透明的,所以大部分狀況下設置爲 0,字段 nlmsg_flags 用於設置消息標誌,可用的標誌包括:

 

[cpp]  view plain  copy
 
  1. #define NLM_F_REQUEST 1   
  2. #define NLM_F_MULTI     2   
  3. #define NLM_F_ACK        4   
  4. #define NLM_F_ECHO      8   
  5. #define NLM_F_ROOT     0x100   
  6. #define NLM_F_MATCH    0x200   
  7. #define NLM_F_ATOMIC  0x400   
  8. #define NLM_F_DUMP      (NLM_F_ROOT|NLM_F_MATCH)   
  9. #define NLM_F_REPLACE  0x100   
  10. #define NLM_F_EXCL       0x200   
  11. #define NLM_F_CREATE   0x400   
  12. #define NLM_F_APPEND   0x800  

 

 

標誌NLM_F_REQUEST用於表示消息是一個請求,全部應用首先發起的消息都應設置該標誌。

標誌NLM_F_MULTI 用於指示該消息是一個多部分消息的一部分,後續的消息能夠經過宏NLMSG_NEXT來得到。

宏NLM_F_ACK表示該消息是前一個請求消息的響應,順序號與進程ID能夠把請求與響應關聯起來。

標誌NLM_F_ECHO表示該消息是相關的一個包的回傳。

標誌NLM_F_ROOT 被許多 netlink 協議的各類數據獲取操做使用,該標誌指示被請求的數據表應當總體返回用戶應用,而不是一個條目一個條目地返回。有該標誌的請求一般致使響應消息設置NLM_F_MULTI標誌。注意,當設置了該標誌時,請求是協議特定的,所以,須要在字段 nlmsg_type 中指定協議類型。

標誌 NLM_F_MATCH 表示該協議特定的請求只須要一個數據子集,數據子集由指定的協議特定的過濾器來匹配。

標誌 NLM_F_ATOMIC 指示請求返回的數據應當原子地收集,這預防數據在獲取期間被修改。

標誌 NLM_F_DUMP 未實現。

標誌 NLM_F_REPLACE 用於取代在數據表中的現有條目。

標誌 NLM_F_EXCL_ 用於和 CREATE 和 APPEND 配合使用,若是條目已經存在,將失敗。

標誌 NLM_F_CREATE 指示應當在指定的表中建立一個條目。

標誌 NLM_F_APPEND 指示在表末尾添加新的條目。

內核須要讀取和修改這些標誌,對於通常的使用,用戶把它設置爲 0 就能夠,只是一些高級應用(如 netfilter 和路由 daemon 須要它進行一些複雜的操做),字段 nlmsg_seq 和 nlmsg_pid 用於應用追蹤消息,前者表示順序號,後者爲消息來源進程 ID。下面是一個示例:

[cpp]  view plain  copy
 
  1. #define MAX_MSGSIZE 1024  
  2. char buffer[] = "An example message";   
  3. struct nlmsghdr nlhdr;   
  4. nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));   
  5. strcpy(NLMSG_DATA(nlhdr),buffer);   
  6. nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));   
  7. nlhdr->nlmsg_pid = getpid();   
  8. nlhdr->nlmsg_flags = 0;  


結構 struct iovec 用於把多個消息經過一次系統調用來發送,下面是該結構使用示例:

 

[cpp]  view plain  copy
 
  1. struct iovec iov;   
  2. iov.iov_base = (void *)nlhdr;   
  3. iov.iov_len = nlh->nlmsg_len;   
  4. msg.msg_iov = &iov;   
  5. msg.msg_iovlen = 1;  

 

 

在完成以上步驟後,消息就能夠經過下面語句直接發送:

sendmsg(fd, &msg, 0);

應用接收消息時須要首先分配一個足夠大的緩存來保存消息頭以及消息的數據部分,而後填充消息頭,添完後就能夠直接調用函數 recvmsg() 來接收。

 

[cpp]  view plain  copy
 
  1. #define MAX_NL_MSG_LEN 1024   
  2. struct sockaddr_nl nladdr;   
  3. struct msghdr msg;   
  4. struct iovec iov;   
  5. struct nlmsghdr * nlhdr;   
  6. nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);   
  7. iov.iov_base = (void *)nlhdr;   
  8. iov.iov_len = MAX_NL_MSG_LEN;   
  9. msg.msg_name = (void *)&(nladdr);   
  10. msg.msg_namelen = sizeof(nladdr);   
  11. msg.msg_iov = &iov;   
  12. msg.msg_iovlen = 1;   
  13. recvmsg(fd, &msg, 0);  

 

 

注意:fd爲socket調用打開的netlink socket描述符。

在消息接收後,nlhdr指向接收到的消息的消息頭,nladdr保存了接收到的消息的目標地址,宏NLMSG_DATA(nlhdr)返回指向消息的數據部分的指針。

在linux/netlink.h中定義了一些方便對消息進行處理的宏,這些宏包括:

#define NLMSG_ALIGNTO 4 
#define NLMSG_ALIGN(len)    ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

 

宏NLMSG_ALIGN(len)用於獲得不小於len且字節對齊的最小數值。

#define NLMSG_LENGTH(len)   ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

 

宏NLMSG_LENGTH(len)用於計算數據部分長度爲len時實際的消息長度。它通常用於分配消息緩存。

#define NLMSG_SPACE(len)   NLMSG_ALIGN(NLMSG_LENGTH(len))

 

宏NLMSG_SPACE(len)返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值,它也用於分配消息緩存。

#define NLMSG_DATA(nlh)   ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

 

宏NLMSG_DATA(nlh)用於取得消息的數據部分的首地址,設置和讀取消息數據部分時須要使用該宏。

#define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
 (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

 

宏NLMSG_NEXT(nlh,len)用於獲得下一個消息的首地址,同時len也減小爲剩餘消息的總長度,該宏通常在一個消息被分紅幾個部分發送或接收時使用。

#define NLMSG_OK(nlh,len)   ((len) >= (int)sizeof(struct nlmsghdr) && \
 (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ 
(nlh)->nlmsg_len <= (len))

 

宏NLMSG_OK(nlh,len)用於判斷消息是否有len這麼長。

#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

 

宏NLMSG_PAYLOAD(nlh,len)用於返回payload的長度。

函數close用於關閉打開的netlink socket。

netlink內核API

netlink的內核實如今.c文件net/core/af_netlink.c中,內核模塊要想使用netlink,也必須包含頭文件linux/netlink.h。內核使用netlink須要專門的API,這徹底不一樣於用戶態應用對netlink的使用。若是用戶須要增長新的netlink協議類型,必須經過修改linux/netlink.h來實現,固然,目前的netlink實現已經包含了一個通用的協議類型NETLINK_GENERIC以方便用戶使用,用戶能夠直接使用它而沒必要增長新的協議類型。前面講到,爲了增長新的netlink協議類型,用戶僅需增長以下定義到linux/netlink.h就能夠:

#define NETLINK_MYTEST 17

 

只要增長這個定義以後,用戶就能夠在內核的任何地方引用該協議。

在內核中,爲了建立一個netlink socket用戶須要調用以下函數:

struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));

 

參數unit表示netlink協議類型,如NETLINK_MYTEST,參數input則爲內核模塊定義的netlink消息處理函數,當有消息到達這個netlink socket時,該input函數指針就會被引用。函數指針input的參數sk實際上就是函數netlink_kernel_create返回的struct sock指針,sock實際是socket的一個內核表示數據結構,用戶態應用建立的socket在內核中也會有一個struct sock結構來表示。下面是一個input函數的示例:

void input (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *data = NULL; while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { nlh = (struct nlmsghdr *)skb->data; data = NLMSG_DATA(nlh); } }

 

函數input()會在發送進程執行sendmsg()時被調用,這樣處理消息比較及時,可是,若是消息特別長時,這樣處理將增長系統調用sendmsg()的執行時間,對於這種狀況,能夠定義一個內核線程專門負責消息接收,而函數input的工做只是喚醒該內核線程,這樣sendmsg將很快返回。

函數skb = skb_dequeue(&sk->receive_queue)用於取得socket sk的接收隊列上的消息,返回爲一個struct sk_buff的結構,skb->data指向實際的netlink消息。

函數skb_recv_datagram(nl_sk)也用於在netlink socket nl_sk上接收消息,與skb_dequeue的不一樣指出是,若是socket的接收隊列上沒有消息,它將致使調用進程睡眠在等待隊列nl_sk->sk_sleep,所以它必須在進程上下文使用,剛纔講的內核線程就能夠採用這種方式來接收消息。

下面的函數input就是這種使用的示例:

void input (struct sock *sk, int len) { wake_up_interruptible(sk->sk_sleep); }

 

當內核中發送netlink消息時,也須要設置目標地址與源地址,並且內核中消息是經過struct sk_buff來管理的, linux/netlink.h中定義了一個宏:

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))

 

來方便消息的地址設置。下面是一個消息地址設置的例子:

NETLINK_CB(skb).pid = 0; NETLINK_CB(skb).dst_pid = 0; NETLINK_CB(skb).dst_group = 1;

 

字段pid表示消息發送者進程ID,也即源地址,對於內核,它爲 0, dst_pid 表示消息接收者進程 ID,也即目標地址,若是目標爲組或內核,它設置爲 0,不然 dst_group 表示目標組地址,若是它目標爲某一進程或內核,dst_group 應當設置爲 0。

在內核中,模塊調用函數 netlink_unicast 來發送單播消息:

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

 

參數sk爲函數netlink_kernel_create()返回的socket,參數skb存放消息,它的data字段指向要發送的netlink消息結構,而skb的控制塊保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用於方便設置該控制塊,參數pid爲接收消息進程的pid,參數nonblock表示該函數是否爲非阻塞,若是爲1,該函數將在沒有接收緩存可利用時當即返回,而若是爲0,該函數在沒有接收緩存可利用時睡眠。

內核模塊或子系統也可使用函數netlink_broadcast來發送廣播消息:

void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);

 

前面的三個參數與netlink_unicast相同,參數group爲接收消息的多播組,該參數的每個表明一個多播組,所以若是發送給多個多播組,就把該參數設置爲多個多播組組ID的位或。參數allocation爲內核內存分配類型,通常地爲GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不能夠睡眠),而GFP_KERNEL用於非原子上下文。

在內核中使用函數sock_release來釋放函數netlink_kernel_create()建立的netlink socket:

void sock_release(struct socket * sock);

 

注意函數netlink_kernel_create()返回的類型爲struct sock,所以函數sock_release應該這種調用:

sock_release(sk->sk_socket);

 

sk爲函數netlink_kernel_create()的返回值。

sk爲函數netlink_kernel_create()的返回值。在源代碼包中給出了一個使用 netlink 的示例,它包括一個內核模塊 netlink-exam-kern.c 和兩個應用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。內核模塊必須先插入到內核,而後在一個終端上運行用戶態接收程序,在另外一個終端上運行用戶態發送程序,發送程序讀取參數指定的文本文件並把它做爲 netlink 消息的內容發送給內核模塊,內核模塊接受該消息保存到內核緩存中,它也經過proc接口出口到 procfs,所以用戶也可以經過 /proc/netlink_exam_buffer 看到所有的內容,同時內核也把該消息發送給用戶態接收程序,用戶態接收程序將把接收到的內容輸出到屏幕上。

相關文章
相關標籤/搜索