由于開發(fā)和維護(hù)內(nèi)核的復(fù)雜性,只用最為關(guān)鍵同時(shí)對性能要求最高的代碼才會(huì)放在內(nèi)核中。其他的諸如GUI,管理和控制代碼,通常放在用戶空間運(yùn)行。這種將實(shí)現(xiàn)分離在內(nèi)核和用戶空間的思想在Linux中非常常見。現(xiàn)在的問題是內(nèi)核代碼和用戶代碼如果彼此通信。
答案是內(nèi)核空間和用戶空間存在的各種IPC方法,例如系統(tǒng)調(diào)用,ioctl,proc文件系統(tǒng)和netlink socket。這篇文章討論netlink socket和討論其作為一種網(wǎng)絡(luò)特征IPC的優(yōu)勢。
簡介
Netlink socket是用于內(nèi)核和用戶空間之間交換信息的特殊的IPC機(jī)制。它提供了一種全復(fù)用的通信鏈路。和TCP/IP使用的地址族AF_INET相 比,Netlink socket使用地址族AF_NETLINK,每個(gè)的netlink socket特征定義協(xié)議類型在內(nèi)核頭文件中include/linux/netlink.h
以下是netlink socket當(dāng)前支持的特征和他們的協(xié)議類型的子集:
- NETLINK_ROUTE:用戶空間路由damon,如BGP,OSPF,RIP和內(nèi)核包轉(zhuǎn)發(fā)模塊的通信信道。用戶空間路由damon通過此種netlink協(xié)議類型更新內(nèi)核路由表
- NETLINK_FIREWALL:接收IPv4防火墻代碼發(fā)送的包
- NETLINK_NFLOG:用戶空間iptable管理工具和內(nèi)核空間Netfilter模塊的通信信道
- NETLINK_ARPD:用戶空間管理arp表
為 什么以上的特征使用netlink而不是系統(tǒng)調(diào)用,ioctl或者proc文件系統(tǒng)來完成通信?為新特性添加系統(tǒng)調(diào)用,ioctl和proc文件系統(tǒng)相對 而言是一項(xiàng)比較復(fù)雜的工作,我們冒著污染內(nèi)核和損害系統(tǒng)穩(wěn)定性的風(fēng)險(xiǎn)。netlink socket相對簡單:只有一個(gè)常量,協(xié)議類型,需要加入到netlink.h中。然后,內(nèi)核模塊和用戶程序可以通過socket類型的API進(jìn)行通信。
和 其他socket API一樣,Netlink是異步的,它提供了一個(gè)socket隊(duì)列來平滑突發(fā)的信息。發(fā)送一個(gè)netlink消息的系統(tǒng)調(diào)用將消息排列到接受者的 netlink隊(duì)列中然后調(diào)用接收者的接收處理函數(shù)。接收者,在接收處理函數(shù)的上下文中,可以決定是否立即處理該消息還是等待在另一個(gè)上下文中處理。不想 netlink,系統(tǒng)調(diào)用需要同步處理。因此,如果我們使用了一個(gè)系統(tǒng)來傳遞一條消息到內(nèi)核,如果需要處理該條信息的時(shí)間很長,那么內(nèi)核調(diào)度粒度可以會(huì)受 影響。
在內(nèi)核中實(shí)現(xiàn)的系統(tǒng)調(diào)用代碼在編譯時(shí)被靜態(tài)的鏈接到內(nèi)核中,因此在一個(gè)可以動(dòng)態(tài)加載的模塊中包括系統(tǒng)調(diào)用代碼是不合適的。在netlink socket中,內(nèi)核中的netlink核心和在一個(gè)可加載的模塊中沒有編譯時(shí)的相互依賴。
netlink socket支持多播,這也是其與其他交互手段相比較的優(yōu)勢之一。一個(gè)進(jìn)程可以將一條消息廣播到一個(gè)netlink組地址。任意多的進(jìn)程可以監(jiān)聽那個(gè)組地址。這提供了一種從內(nèi)核到用戶空間進(jìn)行事件分發(fā)接近完美的機(jī)制。
從 會(huì)話只能由用戶空間應(yīng)用發(fā)起的角度來看,系統(tǒng)調(diào)用和ioctl是單一的IPC。但是,如果一個(gè)內(nèi)核模塊有一個(gè)用戶空間應(yīng)用的緊急消息,沒有一種直接的方法 來實(shí)現(xiàn)這些功能。通常,應(yīng)用需要階段性的輪詢內(nèi)核來獲取狀態(tài)變化,盡管密集的輪詢會(huì)有很大的開銷。netlink通過允許內(nèi)核初始化一個(gè)對話來優(yōu)雅的解決 這個(gè)問題。我們稱之為netlink的復(fù)用特性。
最后,netlink提供了bsd socket風(fēng)格的API,而這些API是被軟件開發(fā)社區(qū)所熟知。因此,培訓(xùn)費(fèi)用相較較小。
和BSD路由socket的關(guān)系
在BSC TCP/IP的棧實(shí)現(xiàn)中,有一種叫做路由套接字的特殊的socket。它有AF_ROUTE地址族,PF_ROUTE協(xié)議族和SOCK_RAWsocket類型。在bsd中,路由套接字用于在內(nèi)核路由表中添加和刪除路由。
在Linux中,路由套接字的實(shí)現(xiàn)通過netlink套接字的NETLINK_ROUTE協(xié)議類型來支持。netlink套接字提供了bsd路由套接字的功能的超集。
Netlink套接字API
標(biāo) 準(zhǔn)的套接字API,socket(),sendmsg(),recvmsg()和close(),可以被用戶態(tài)程序使用。可以通過查詢man手冊頁來看這 些函數(shù)的具體定義。這兒,我們討論在netlink上下文中為這些API選擇參數(shù)。對于寫過TCP/IP套接字程序的人對這些API都應(yīng)該非常熟悉。
創(chuàng)建一個(gè)套接字,
int socket(int domain,int type, int protocol)
domain指代地址族,AF_NETLINK,套接字類型不是SOCK_RAW就是SOCK_DGRAM,因?yàn)閚etlink是一個(gè)面向數(shù)據(jù)報(bào)的服務(wù)。
protocol選擇該套接字使用那種netlink特征。以下是幾種預(yù)定義的協(xié)議類型:NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_APRD,NETLINK_ROUTE6_FW。你也可以非常容易的添加自己的netlink協(xié)議。
為 每一個(gè)協(xié)議類型最多可以定義32個(gè)多播組。每一個(gè)多播組用一個(gè)bit mask來表示,1<<i(0<= i<= 31),這在一組進(jìn)程和內(nèi)核進(jìn)程協(xié)同完成一項(xiàng)任務(wù)時(shí)非常有用。發(fā)送多播netlink消息可以減少系統(tǒng)調(diào)用的數(shù)量,同時(shí)減少用來維護(hù)多播組成員信息的負(fù) 擔(dān)。
bind()
和TCP/IP套接字一樣,netlink bind()API用來將一個(gè)本地socket地址和一個(gè)打開的socket關(guān)聯(lián)。一個(gè)netlink地址結(jié)構(gòu)如下所示:
1: struct sockaddr_nl
2: {
3: sa_family_t nl_family; /* AF_NETLINK */
4: unsigned short nl_pad; /* zero */
5: __u32 nl_pid; /* process pid */
6: __u32 nl_groups; /* mcast groups mask */
7: } nladdr;
當(dāng)使用bind()調(diào)用的時(shí)候,nl_pid域可以被賦值為調(diào)用進(jìn)程的pid。nl_pid在這兒被當(dāng)做該netlink套接字的本地地址。程序負(fù)責(zé)找一個(gè)獨(dú)一無二的32位整數(shù)在填充該域。一種常見的做法是:
1: NL_PID Formula 1: nl_pid = getpid();
公式一使用進(jìn)程ID號(hào)作為nl_pid的值,如果說該進(jìn)程只需要一個(gè)netlink套接字,這是一個(gè)自然的選擇。當(dāng)一個(gè)進(jìn)程中的不同線程需要同一個(gè)netlink協(xié)議多個(gè)netlink套接字。公式而可以用來產(chǎn)生nl_pid號(hào):
1: NL_PID Formula 2: pthread_self() << 16 | getpid();
通過這種方法,同一個(gè)進(jìn)程中的不同線程可以有同一種netlink協(xié)議類型的netlink套接字。事實(shí)上,即使在同一個(gè)線程中,在可能使用同一種協(xié)議類型的多個(gè)套接字。開發(fā)者必須要更有創(chuàng)造力來產(chǎn)生一個(gè)唯一的nl_pid。
如果應(yīng)用想接受發(fā)送給特定多播組的netlink消息,所有感興趣的多播組bit應(yīng)該or在一起,并填充到nl_groups域。否則, nl_groups應(yīng)該被顯式至零,說明該應(yīng)用只接受到該應(yīng)用的消息,填充完上述域, 使用如下方式進(jìn)行綁定:
1: bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr));
發(fā)送一條netlink消息
為了發(fā)送一條netlink消息到內(nèi)核或者其他的用戶空間進(jìn)程,另外一個(gè)struct sockaddr_nl nladdr需要作為目的地址,這和使用sendmsg()發(fā)送一個(gè)UDP包是一樣的。如果該消息是發(fā)送至內(nèi)核的,那么nl_pid和nl_groups 都置為0.
如果說消息時(shí)發(fā)送給另一個(gè)進(jìn)程的單播消息,nl_pid是另外一個(gè)進(jìn)程的pid值而nl_groups為零。
如果消息是發(fā)送給一個(gè)或多個(gè)多播組的多播消息,所有的目的多播組必須bitmask必須or起來從而形成nl_groups域。當(dāng)我們填充struct msghdr結(jié)構(gòu)用于sendmsg時(shí),使用如下:
1: struct msghdr msg;
2: msg.msg_name = (void *)&(nladdr);
3: msg.msg_namelen = sizeof(nladdr);
netlink套接字也需要它自己本身的消息頭,這是為了給所有協(xié)議類型的netlink消息提供一個(gè)統(tǒng)一的平臺(tái)。
因?yàn)長inux內(nèi)核netlink核心假設(shè)每個(gè)netlink消息中存在著以下的頭,所有應(yīng)用也必須在其發(fā)送的消息中提供這些頭信息:
1: struct nlmsghdr
2: {
3: __u32 nlmsg_len; /* Length of message */
4: __u16 nlmsg_type; /* Message type*/
5: __u16 nlmsg_flags; /* Additional flags */
6: __u32 nlmsg_seq; /* Sequence number */
7: __u32 nlmsg_pid; /* Sending process PID */
8: };
nlmsg_len指整個(gè)netlink消息的長度,包括頭信息,這也是netlink核心所必須的。nlmsg_type用于應(yīng)用但是對于 netlink核心而言其是透明的。nlmsg_flags用于給定附加的控制信息,其被netlink核心讀取和更新。nlmsg_seq和 mlmsg_pid,應(yīng)用用來跟蹤消息,這些對于netlink核心也是透明的。
所以一個(gè)netlink消息由消息頭和消息負(fù)載組成。一旦一個(gè)消息被加入,它就加入到一個(gè)通過nlh指針指向的緩沖區(qū)。我們也可以將消息發(fā)送到struct msghdr msg:
1: struct iovec iov;
2:
3: iov.iov_base = (void *)nlh;
4: iov.iov_len = nlh->nlmsg_len;
5:
6: msg.msg_iov = &iov;
7: msg.msg_iovlen = 1;
經(jīng)過以上步驟,調(diào)用sendmsg()函數(shù)來發(fā)送netlink消息:
接收netlink消息
一個(gè)接收程序必須分配一個(gè)足夠大的內(nèi)存用于保存netlink消息頭和消息負(fù)載。然后其填充struct msghdr msg,然后使用標(biāo)準(zhǔn)的recvmsg()函數(shù)來接收netlink消息,假設(shè)緩存通過nlh指針指向:
1: struct sockaddr_nl nladdr;
2: struct msghdr msg;
3: struct iovec iov;
4:
5: iov.iov_base = (void *)nlh;
6: iov.iov_len = MAX_NL_MSG_LEN;
7: msg.msg_name = (void *)&(nladdr);
8: msg.msg_namelen = sizeof(nladdr);
9:
10: msg.msg_iov = &iov;
11: msg.msg_iovlen = 1;
12: recvmsg(fd, &msg, 0);
當(dāng)消息被正確的接收之后,nlh應(yīng)該指向剛剛接收到的netlink消息的頭。nladdr應(yīng)該包含接收消息的目的地址,其中包括了消息發(fā)送者的 pid和多播組。同時(shí),宏NLMSG_DATA(nlh),定義在netlink.h中,返回一個(gè)指向netlink消息的負(fù)載的指針。 close(fd)調(diào)用關(guān)閉fd描述符所標(biāo)識(shí)的socket。
內(nèi)核空間netlink API
內(nèi)核空間的netlinkAPI在內(nèi)核中被netlink核心支持,即net/core/af_netlink.c。從內(nèi)核角度看,這些API不同 于用戶空間的API。這些API可以被內(nèi)核模塊使用從而存取netlink套接字與用戶空間程序通信。除非你使用現(xiàn)存的netlink套接字協(xié)議類型,否 則你必須通過在netlink.h中定義一個(gè)常量來添加你自己的協(xié)議類型。例如,我們需要添加一個(gè)netlink協(xié)議類型用于測試,則在 netlink.h中加入下面的語句:
1: #define NETLINK_TEST 17
之后,亦可以在linux內(nèi)核中的任何地方引用添加的協(xié)議類型。
在用戶空間,我們使用socket()來創(chuàng)建一個(gè)netlink套接字,但是在內(nèi)核空間,我們使用如下的API:
1: truct sock *
2: netlink_kernel_create(int unit,
3: void (*input)(struct sock *sk, int len));
參數(shù)unit,即為netlink協(xié)議類型,如NETLINK_TEST,回調(diào)函數(shù)會(huì)在消息到達(dá)netlink套接字時(shí)調(diào)用。當(dāng)用戶態(tài)程序發(fā)送一個(gè)NETLINK_TEST協(xié)議類型的消息給內(nèi)核時(shí),input()函數(shù)被調(diào)用。下面是一個(gè)實(shí)現(xiàn)回調(diào)函數(shù)的例子:
1: void input (struct sock *sk, int len)
2: {
3: struct sk_buff *skb;
4: struct nlmsghdr *nlh = NULL;
5: u8 *payload = NULL;
6:
7: while ((skb = skb_dequeue(&sk->receive_queue))
8: != NULL) {
9: /* process netlink message pointed by skb->data */
10: nlh = (struct nlmsghdr *)skb->data;
11: payload = NLMSG_DATA(nlh);
12: /* process netlink message with header pointed by
13: * nlh and payload pointed by payload
14: */
15: }
16: }
input()函數(shù)在發(fā)送進(jìn)程的sendmsg()系統(tǒng)調(diào)用上下文執(zhí)行。如果在input中處理netlink消息非常快,那是沒有問題的。如果處 理netlink消息需要很長的時(shí)間,我們希望在input()外面處理消息來避免阻塞其他系統(tǒng)調(diào)用進(jìn)入內(nèi)核。事實(shí)上,我們可以使用一個(gè)指定的內(nèi)核線程來 來不斷執(zhí)行以下的步驟。使用skb=skb_recv_datagram(nl_sk),其中nl_sk是 netlink_kernel_create()返回的netlink套接字。然后,處理由skb->data指向的netlink消息。
以下的內(nèi)核線程在沒有netlink消息在nl_sk中時(shí)睡眠,在回調(diào)函數(shù)input中,我們只要喚醒睡眠的內(nèi)核線程,如下所示:
1: void input (struct sock *sk, int len)
2: {
3: wake_up_interruptible(sk->sleep);
4: }
這是一個(gè)更具有擴(kuò)展性的用戶和內(nèi)核通信的模型。其也提高了上下文交換的粒度。
在內(nèi)核中發(fā)送netlink消息
真如在用戶空間中一樣,在發(fā)送一個(gè)netlink消息時(shí)需要設(shè)置源和目的netlink消息地址。假設(shè)socket緩存中包含了將要發(fā)送的netlink消息,本地地址可以通過以下方式設(shè)置:
1: NETLINK_CB(skb).groups = local_groups;
2: NETLINK_CB(skb).pid = 0; /* from kernel */
目的地址可以如下設(shè)置:
1: NETLINK_CB(skb).dst_groups = dst_groups;
2: NETLINK_CB(skb).dst_pid = dst_pid;
這些信息不是存儲(chǔ)在skb->data,而是存儲(chǔ)在skb中的netlink控制塊中。發(fā)送一個(gè)消息,使用:
1: int
2: netlink_unicast(struct sock *ssk, struct sk_buff
3: *skb, u32 pid, int nonblock);
其中ssk是netlink_kernel_create返回的netlink套接字,skb->data指向netlink將要發(fā)送的消息而pid是接受該消息的用戶程序id。nonblock用于標(biāo)識(shí)在接收緩存不可用時(shí),API是阻塞還是立即返回失敗。
你也可以發(fā)送一個(gè)多播消息。以下的API用于將消息傳送到指定的進(jìn)程,同時(shí)多播至指定的多播組。
1: void
2: netlink_broadcast(struct sock *ssk, struct sk_buff
3: *skb, u32 pid, u32 group, int allocation);
group是所有接收多播組的bitmask。allocation是內(nèi)核內(nèi)存分配的類型。通常,GFP_ATOMIC用于中斷上下文而在其他情況下是用GFP_KERNEL.只是因?yàn)锳PI可能需要分配一個(gè)或者多個(gè)套接字緩存來克隆多播消息。
在內(nèi)核中關(guān)閉一個(gè)netlink套接字
給定了netlink_kernel_create()函數(shù)返回的struct sock *nl_sk,我們可以通過調(diào)用以下的API關(guān)閉netlink套接字。
1: sock_release(nl_sk->socket);
在內(nèi)核和用戶態(tài)使用單播通信
1: #include <sys/socket.h>
2: #include <linux/netlink.h>
3:
4: #define MAX_PAYLOAD 1024 /* maximum payload size*/
5: struct sockaddr_nl src_addr, dest_addr;
6: struct nlmsghdr *nlh = NULL;
7: struct iovec iov;
8: int sock_fd;
9:
10: void main() {
11: sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST);
12:
13: memset(&src_addr, 0, sizeof(src_addr));
14: src__addr.nl_family = AF_NETLINK;
15: src_addr.nl_pid = getpid(); /* self pid */
16: src_addr.nl_groups = 0; /* not in mcast groups */
17: bind(sock_fd, (struct sockaddr*)&src_addr,
18: sizeof(src_addr));
19:
20: memset(&dest_addr, 0, sizeof(dest_addr));
21: dest_addr.nl_family = AF_NETLINK;
22: dest_addr.nl_pid = 0; /* For Linux Kernel */
23: dest_addr.nl_groups = 0; /* unicast */
24:
25: nlh=(struct nlmsghdr *)malloc(
26: NLMSG_SPACE(MAX_PAYLOAD));
27: /* Fill the netlink message header */
28: nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
29: nlh->nlmsg_pid = getpid(); /* self pid */
30: nlh->nlmsg_flags = 0;
31: /* Fill in the netlink message payload */
32: strcpy(NLMSG_DATA(nlh), "Hello you!");
33:
34: iov.iov_base = (void *)nlh;
35: iov.iov_len = nlh->nlmsg_len;
36: msg.msg_name = (void *)&dest_addr;
37: msg.msg_namelen = sizeof(dest_addr);
38: msg.msg_iov = &iov;
39: msg.msg_iovlen = 1;
40:
41: sendmsg(fd, &msg, 0);
42:
43: /* Read message from kernel */
44: memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
45: recvmsg(fd, &msg, 0);
46: printf(" Received message payload: %s\n",
47: NLMSG_DATA(nlh));
48:
49: /* Close Netlink Socket */
50: close(sock_fd);
51: }
1: struct sock *nl_sk = NULL;
2:
3: void nl_data_ready (struct sock *sk, int len)
4: {
5: wake_up_interruptible(sk->sleep);
6: }
7:
8: void netlink_test() {
9: struct sk_buff *skb = NULL;
10: struct nlmsghdr *nlh = NULL;
11: int err;
12: u32 pid;
13:
14: nl_sk = netlink_kernel_create(NETLINK_TEST,
15: nl_data_ready);
16: /* wait for message coming down from user-space */
17: skb = skb_recv_datagram(nl_sk, 0, 0, &err);
18:
19: nlh = (struct nlmsghdr *)skb->data;
20: printk("%s: received netlink message payload:%s\n",
21: __FUNCTION__, NLMSG_DATA(nlh));
22:
23: pid = nlh->nlmsg_pid; /*pid of sending process */
24: NETLINK_CB(skb).groups = 0; /* not in mcast group */
25: NETLINK_CB(skb).pid = 0; /* from kernel */
26: NETLINK_CB(skb).dst_pid = pid;
27: NETLINK_CB(skb).dst_groups = 0; /* unicast */
28: netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
29: sock_release(nl_sk->socket);
30: }
在內(nèi)核和用戶態(tài)使用單播通信
1: #include <sys/socket.h>
2: #include <linux/netlink.h>
3:
4: #define MAX_PAYLOAD 1024 /* maximum payload size*/
5: struct sockaddr_nl src_addr, dest_addr;
6: struct nlmsghdr *nlh = NULL;
7: struct iovec iov;
8: int sock_fd;
9:
10: void main() {
11: sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
12:
13: memset(&src_addr, 0, sizeof(local_addr));
14: src_addr.nl_family = AF_NETLINK;
15: src_addr.nl_pid = getpid(); /* self pid */
16: /* interested in group 1<<0 */
17: src_addr.nl_groups = 1;
18: bind(sock_fd, (struct sockaddr*)&src_addr,
19: sizeof(src_addr));
20:
21: memset(&dest_addr, 0, sizeof(dest_addr));
22:
23: nlh = (struct nlmsghdr *)malloc(
24: NLMSG_SPACE(MAX_PAYLOAD));
25: memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
26:
27: iov.iov_base = (void *)nlh;
28: iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
29: msg.msg_name = (void *)&dest_addr;
30: msg.msg_namelen = sizeof(dest_addr);
31: msg.msg_iov = &iov;
32: msg.msg_iovlen = 1;
33:
34: printf("Waiting for message from kernel\n");
35:
36: /* Read message from kernel */
37: recvmsg(fd, &msg, 0);
38: printf(" Received message payload: %s\n",
39: NLMSG_DATA(nlh));
40: close(sock_fd);
41: }
1: #define MAX_PAYLOAD 1024
2: struct sock *nl_sk = NULL;
3:
4: void netlink_test() {
5: sturct sk_buff *skb = NULL;
6: struct nlmsghdr *nlh;
7: int err;
8:
9: nl_sk = netlink_kernel_create(NETLINK_TEST,
10: nl_data_ready);
11: skb=alloc_skb(NLMSG_SPACE(MAX_PAYLOAD),GFP_KERNEL);
12: nlh = (struct nlmsghdr *)skb->data;
13: nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
14: nlh->nlmsg_pid = 0; /* from kernel */
15: nlh->nlmsg_flags = 0;
16: strcpy(NLMSG_DATA(nlh), "Greeting from kernel!");
17: /* sender is in group 1<<0 */
18: NETLINK_CB(skb).groups = 1;
19: NETLINK_CB(skb).pid = 0; /* from kernel */
20: NETLINK_CB(skb).dst_pid = 0; /* multicast */
21: /* to mcast group 1<<0 */
22: NETLINK_CB(skb).dst_groups = 1;
23:
24: /*multicast the message to all listening processes*/
25: netlink_broadcast(nl_sk, skb, 0, 1, GFP_KERNEL);
26: sock_release(nl_sk->socket);
27: }
Netlink可靠性機(jī)制
在基于netlink的通信中,有兩種可能的情形會(huì)導(dǎo)致消息丟失:
- 內(nèi)存耗盡,沒有足夠多的內(nèi)存分配給消息
- 緩存復(fù)寫,接收隊(duì)列中沒有空間存儲(chǔ)消息,這在內(nèi)核空間和用戶空間之間通信時(shí)可能會(huì)發(fā)生
緩存復(fù)寫在以下情況很可能會(huì)發(fā)生:
- 內(nèi)核子系統(tǒng)以一個(gè)恒定的速度發(fā)送netlink消息,但是用戶態(tài)監(jiān)聽者處理過慢
- 用戶存儲(chǔ)消息的空間過小
如果netlink傳送消息失敗,那么recvmsg()函數(shù)會(huì)返回No buffer space available(ENOBUFS)錯(cuò)誤。那么,用戶空間進(jìn)程知道它丟失了信息,如果內(nèi)核子系統(tǒng)支持dump操作,它可以重新同步來獲取最新的消息。在 dump操作中,netlink通過在每次調(diào)用recvmsg()函數(shù)時(shí)傳輸一個(gè)包的流控機(jī)制來防止接收隊(duì)列的復(fù)寫。改包消耗一個(gè)內(nèi)存頁,其包含了幾個(gè)多 部分netlink消息。圖6中的序列圖顯示了在一個(gè)重新同步的過程中所使用的dump操作。
另一方面,緩存復(fù)寫不會(huì)發(fā)生在用戶和內(nèi)核空間的通信中,因?yàn)閟endmsg()同步的將netlink消息發(fā)送到內(nèi)核子系統(tǒng)。如果使用的是阻塞套接字,那么netlink在從用戶空間到內(nèi)核空間的通信時(shí)完全可靠的,因?yàn)閮?nèi)存分配可以等待,所以沒有內(nèi)存耗盡的可能。
netlink也可以提供應(yīng)答機(jī)制。所以如果用戶空間進(jìn)程發(fā)送了一個(gè)設(shè)置了NLM_F_ACK標(biāo)志的請求,netlink會(huì)在netlink錯(cuò)誤消息中報(bào)告給用戶空間剛才請求操作的結(jié)果。
從用戶空間的角度來看,Netlink套接字在通用的BSD套接字接口之上實(shí)現(xiàn)。因此,netlink套接字編程與通用的TCP/IP編程類似。但是,我們也應(yīng)該考慮幾個(gè)與netlink相關(guān)的特殊問題:
- netlink套接字沒有像其他協(xié)議一樣對用戶空間隱藏協(xié)議細(xì)節(jié)。事實(shí)上,netlink傳遞的是整個(gè)消息,包括netlink頭和其他信 息。因此,這就導(dǎo)致了數(shù)據(jù)處理函數(shù)與通用的TCP/IP套接字不同,所以用戶態(tài)程序必須根據(jù)其格式解析和構(gòu)建netlink信息。然而,沒有標(biāo)準(zhǔn)的工具來 完成這些工作,所以你必須實(shí)現(xiàn)自己的函數(shù)或者使用一些現(xiàn)成的庫。
- 來自netlink和內(nèi)核子系統(tǒng)的錯(cuò)誤不是通過recvmsg()函數(shù)返回的整數(shù)值來表現(xiàn)的。事實(shí)上,錯(cuò)誤信息時(shí)被包裝在netlink錯(cuò)誤 消息中的。唯一的例外是(ENOBUFS)錯(cuò)誤,該錯(cuò)誤不是包裝在netlink消息中,因?yàn)閳?bào)告該錯(cuò)誤的原因就是我們沒有足夠的空間來緩存新的 netlink消息。標(biāo)準(zhǔn)的通用套接字錯(cuò)誤,如(EAGAIN),通常和其他輪詢原語,例如poll()和select(),也是通過recvmsg() 返回整數(shù)值。