af_netlink_2、netlink简介
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 更加混亂。
問題:
增加了netlink協議后,需要重新編譯內核嗎?還是僅僅修改了頭文件netlink.h就可以了呢?在后期的實驗中,需要對這一問題進行驗證。
2. netlink是一種異步通信機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不需要等待接收者收到消息,但系統調用與 ioctl 則是同步通信機制,如果傳遞的數據太長,將影響調度粒度。
異步通信與同步通信的區別:看接受者的響應方式。異步通訊是不需要接受者立即響應的
3.使用 netlink 的內核部分可以采用模塊的方式實現,使用 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是一個通用的協議類型,它是專門為用戶使用的,因此,用戶可以直接使用它,而不必再添加新的協議類型。內核預定義的協議類型有:
#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
2 #include
3 #include
4 #include
5 #include
6
7 #define NETLINK_TEST 17
8 #define MSG_LEN 100
9
10 structmsg_to_kernel11 {12 structnlmsghdr hdr;13 chardata[MSG_LEN];14 };15 structu_packet_info16 {17 structnlmsghdr hdr;18 charmsg[MSG_LEN];19 };20
21 int main(int argc, char*argv[])22 {23 char *data = "This message is from eric's space";24 //初始化
25 structsockaddr_nl local;26 structsockaddr_nl kpeer;27 int skfd, ret, kpeerlen = sizeof(structsockaddr_nl);28 struct nlmsghdr *message;29 structu_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(structnlmsghdr));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
2 #include
3 #include
4 #include
5 #include
6 #include
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 intsize;18 struct sk_buff *skb;19 unsigned char *old_tail;20 struct nlmsghdr *nlh; //報文頭
21
22 intretval;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(structnlmsghdr)){56 nlh = (struct nlmsghdr *)skb->data;57 if((nlh->nlmsg_len >= sizeof(structnlmsghdr))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內核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) {
/* 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 看到全部的內容,同時內核也把該消息發送給用戶態接收程序,用戶態接收程序將把接收到的內容輸出到屏幕上。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的af_netlink_2、netlink简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 论文阅读笔记(一)【Journal of
- 下一篇: pthread_create函数阻塞了主