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)。linux
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 更加混亂。數據結構
問題:異步
增長了netlink協議後,須要從新編譯內核嗎?仍是僅僅修改了頭文件netlink.h就能夠了呢?在後期的實驗中,須要對這一問題進行驗證。socket
2. netlink是一種異步通訊機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不須要等待接收者收到消息,但系統調用與 ioctl 則是同步通訊機制,若是傳遞的數據太長,將影響調度粒度。ide
異步通訊與同步通訊的區別:看接受者的響應方式。異步通信是不須要接受者當即響應的函數
3.使用 netlink 的內核部分能夠採用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴,但系統調用就有依賴,並且新的系統調用的實現必須靜態地鏈接到內核中,它沒法在模塊中實現,使用新系統調用的應用在編譯時須要依賴內核。this
能夠像編寫驅動模塊同樣的實現方式來實現netlink部分。atom
4.netlink 支持多播,內核模塊或應用能夠把消息多播給一個netlink組,屬於該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性,任何對內核事件感興趣的應用都能收到該子系統發送的內核事件,在後面的文章中將介紹這一機制的使用。
5.內核可使用 netlink 首先發起會話,但系統調用和 ioctl 只能由用戶應用發起調用。
內核主動向用戶應用發起數據
6.netlink 使用標準的 socket API,所以很容易使用,但系統調用和 ioctl則須要專門的培訓才能使用。
用戶態應用使用標準的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是一個通用的協議類型,它是專門爲用戶使用的,所以,用戶能夠直接使用它,而沒必要再添加新的協議類型。內核預約義的協議類型有:
#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 hook */
#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
對於每個netlink協議類型,能夠有多達 32多播組,每個多播組用一個位表示,netlink 的多播特性使得發送消息給同一個組僅須要一次系統調用,於是對於須要多撥消息的應用而言,大大地下降了系統調用的次數。
函數 bind() 用於把一個打開的 netlink socket 與 netlink 源 socket 地址綁定在一塊兒。
補充一下bind()函數的意義:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to
it. bind() assigns the address specified to by addr to the socket referred to by the file descriptor sockfd.
addrlen specifies the size, in bytes, of the address structure pointed to by addr. Traditionally, this operation
is called 「assigning a name to a socket」.
It is normally necessary to assign a local address using bind() before a SOCK_STREAM socket may receive connec‐
tions (see accept(2)).
可見bind()的另外一個意思是爲所建立的socket分配一個名字(相應與地址的名字)
netlink socket 的地址結構以下:
struct sockaddr_nl { sa_family_t nl_family; unsigned short nl_pad; __u32 nl_pid; __u32 nl_groups; };
字段 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 需以下設置:
struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr);
其中 nladdr 爲消息接收者的 netlink 地址。
struct nlmsghdr 爲 netlink socket 本身的消息頭,這用於多路複用和多路分解 netlink 定義的全部協議類型以及其它一些控制,netlink 的內核實現將利用這個消息頭來多路複用和多路分解已經其它的一些控制,所以它也被稱爲netlink 控制塊。所以,應用在發送 netlink 消息時必須提供該消息頭。
內核會利用nlmsghdr頭路由netlink消息包,
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};
字段 nlmsg_len 指定消息的總長度,包括緊跟該結構的數據部分長度以及該結構的大小,
字段 nlmsg_type 用於應用內部定義消息的類型,它對 netlink 內核實現是透明的,所以大部分狀況下設置爲 0,
字段 nlmsg_flags 用於設置消息標誌,可用的標誌包括:
/* Flags values */ #define NLM_F_REQUEST 1 /* It is request message. */ #define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */ #define NLM_F_ACK 4 /* Reply with ack, with zero or error code */ #define NLM_F_ECHO 8 /* Echo this request */ /* Modifiers to GET request */ #define NLM_F_ROOT 0x100 /* specify tree root */ #define NLM_F_MATCH 0x200 /* return all matching */ #define NLM_F_ATOMIC 0x400 /* atomic GET */ #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) /* Modifiers to NEW request */ #define NLM_F_REPLACE 0x100 /* Override existing */ #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */
標誌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。下面是一個示例:
#define MAX_MSGSIZE 1024 char buffer[] = "An example message"; struct nlmsghdr nlhdr; nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE)); strcpy(NLMSG_DATA(nlhdr),buffer); nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer)); nlhdr->nlmsg_pid = getpid(); /* self pid */ nlhdr->nlmsg_flags = 0;
結構 struct iovec 用於把多個消息經過一次系統調用來發送,下面是該結構使用示例:
struct iovec iov; iov.iov_base = (void *)nlhdr; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1;
在完成以上步驟後,消息就能夠經過下面語句直接發送:
sendmsg(fd, &msg, 0);
應用接收消息時須要首先分配一個足夠大的緩存來保存消息頭以及消息的數據部分,而後填充消息頭,添完後就能夠直接調用函數 recvmsg() 來接收。
#define MAX_NL_MSG_LEN 1024 struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; struct nlmsghdr * nlhdr; nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN); iov.iov_base = (void *)nlhdr; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; 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。
在發送的時候只使用了nlmsghdr消息頭,可是在接收的時候使用了struct msghdr msg;
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/socket.h> 4 #include <string.h> 5 #include <linux/netlink.h> 6 7 #define NETLINK_TEST 17 8 #define MSG_LEN 100 9 10 struct msg_to_kernel 11 { 12 struct nlmsghdr hdr; 13 char data[MSG_LEN]; 14 }; 15 struct u_packet_info 16 { 17 struct nlmsghdr hdr; 18 char msg[MSG_LEN]; 19 }; 20 21 int main(int argc, char* argv[]) 22 { 23 char *data = "This message is from eric's space"; 24 //初始化 25 struct sockaddr_nl local; 26 struct sockaddr_nl kpeer; 27 int skfd, ret, kpeerlen = sizeof(struct sockaddr_nl); 28 struct nlmsghdr *message; 29 struct u_packet_info info; 30 char *retval; 31 message = (struct nlmsghdr *)malloc(1); 32 33 skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST); 34 if(skfd < 0){ 35 printf("can not create a netlink socket\n"); 36 return -1; 37 } 38 memset(&local, 0, sizeof(local)); 39 local.nl_family = AF_NETLINK; 40 local.nl_pid = getpid(); 41 local.nl_groups = 0; 42 if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){ 43 printf("bind() error\n"); 44 return -1; 45 } 46 memset(&kpeer, 0, sizeof(kpeer)); 47 kpeer.nl_family = AF_NETLINK; 48 kpeer.nl_pid = 0; 49 kpeer.nl_groups = 0; 50 51 memset(message, '\0', sizeof(struct nlmsghdr)); 52 message->nlmsg_len = NLMSG_SPACE(strlen(data)); 53 message->nlmsg_flags = 0; 54 message->nlmsg_type = 0; 55 message->nlmsg_seq = 0; 56 message->nlmsg_pid = local.nl_pid; 57 58 retval = memcpy(NLMSG_DATA(message), data, strlen(data)); 59 60 printf("message sendto kernel are:%s, len:%d\n", (char *)NLMSG_DATA(message), message->nlmsg_len); 61 ret = sendto(skfd, message, message->nlmsg_len, 0,(struct sockaddr *)&kpeer, sizeof(kpeer)); 62 if(!ret){ 63 perror("send pid:"); 64 exit(-1); 65 } 66 67 //接受內核態確認信息 68 ret = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen); 69 if(!ret){ 70 perror("recv form kerner:"); 71 exit(-1); 72 } 73 74 printf("message receive from kernel:%s\n",(char *)info.msg); 75 //內核和用戶進行通訊 76 77 close(skfd); 78 return 0; 79 }
如下是內核中關於netlink的具體實現過程:
1 #include <linux/kernel.h> 2 #include <linux/module.h> 3 #include <linux/types.h> 4 #include <linux/sched.h> 5 #include <net/sock.h> 6 #include <linux/netlink.h> 7 8 #define NETLINK_TEST 17 9 struct { 10 __u32 pid; 11 }user_process; 12 13 static struct sock *netlinkfd = NULL; 14 15 int send_to_user(char *info) //發送到用戶空間 16 { 17 int size; 18 struct sk_buff *skb; 19 unsigned char *old_tail; 20 struct nlmsghdr *nlh; //報文頭 21 22 int retval; 23 24 size = NLMSG_SPACE(strlen(info)); //報文大小 25 skb = alloc_skb(size, GFP_ATOMIC); //分配一個新的套接字緩存,使用GFP_ATOMIC標誌進程不>會被置爲睡眠 26 27 //初始化一個netlink消息首部 28 nlh = nlmsg_put(skb, 0, 0, 0, NLMSG_SPACE(strlen(info))-sizeof(struct nlmsghdr), 0); 29 old_tail = skb->tail; 30 memcpy(NLMSG_DATA(nlh), info, strlen(info)); //填充數據區 31 nlh->nlmsg_len = skb->tail - old_tail; //設置消息長度 32 33 //設置控制字段 34 NETLINK_CB(skb).pid = 0; 35 NETLINK_CB(skb).dst_group = 0; 36 37 printk(KERN_DEBUG "[kernel space] skb->data:%s\n", (char *)NLMSG_DATA((struct nlmsghdr *)skb->data)); 38 39 //發送數據 40 retval = netlink_unicast(netlinkfd, skb, user_process.pid, MSG_DONTWAIT); 41 printk(KERN_DEBUG "[kernel space] netlink_unicast return: %d\n", retval); 42 return 0; 43 } 44 45 void kernel_receive(struct sk_buff *__skb) //內核從用戶空間接收數據 46 { 47 struct sk_buff *skb; 48 struct nlmsghdr *nlh = NULL; 49 50 char *data = "This is eric's test message from kernel"; 51 52 printk(KERN_DEBUG "[kernel space] begin kernel_receive\n"); 53 skb = skb_get(__skb); 54 55 if(skb->len >= sizeof(struct nlmsghdr)){ 56 nlh = (struct nlmsghdr *)skb->data; 57 if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) 58 && (__skb->len >= nlh->nlmsg_len)){ 59 user_process.pid = nlh->nlmsg_pid; 60 printk(KERN_DEBUG "[kernel space] data receive from user are:%s\n", (char *)NLMSG_DATA(nlh)); 61 printk(KERN_DEBUG "[kernel space] user_pid:%d\n", user_process.pid); 62 send_to_user(data); 63 } 64 }else{ 65 printk(KERN_DEBUG "[kernel space] data receive from user are:%s\n",(char *)NLMSG_DATA(nlmsg_hdr(__skb))); 66 send_to_user(data); 67 } 68 69 kfree_skb(skb); 70 } 71 72 int __init test_netlink_init(void) 73 { 74 75 netlinkfd = netlink_kernel_create(&init_net, NETLINK_TEST, 0, kernel_receive, NULL, THIS_MODULE); 76 if(!netlinkfd){ 77 printk(KERN_ERR "can not create a netlink socket\n"); 78 return -1; 79 } 80 return 0; 81 } 82 83 void __exit test_netlink_exit(void) 84 { 85 sock_release(netlinkfd->sk_socket); 86 printk(KERN_DEBUG "test_netlink_exit!!\n"); 87 } 88 89 module_init(test_netlink_init); 90 module_exit(test_netlink_exit); 91 MODULE_LICENSE("GPL"); 92 MODULE_AUTHOR("eric.hu");
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) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; data = NLMSG_DATA(nlh); /* process netlink message with header pointed by * nlh and data pointed by data */ } }
函數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()的返回值。
在源代碼包中給出了一個使用 netlink 的示例,它包括一個內核模塊 netlink-exam-kern.c 和兩個應用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。內核模塊必須先插入到內核,而後在一個終端上運行用戶態接收程序,在另外一個終端上運行用戶態發送程序,發送程序讀取參數指定的文本文件並把它做爲 netlink 消息的內容發送給內核模塊,內核模塊接受該消息保存到內核緩存中,它也經過proc接口出口到 procfs,所以用戶也可以經過 /proc/netlink_exam_buffer 看到所有的內容,同時內核也把該消息發送給用戶態接收程序,用戶態接收程序將把接收到的內容輸出到屏幕上。