天天看點

android 核心 netlink,android-kernel-samsung-dev

linux 核心與使用者空間通信之netlink使用方法

1 引言

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處于核心狀态時,可以分為有使用者上下文的狀态和執行硬體、軟體中斷兩種。其中當處于有使用者上下文時,由于核心态和使用者态的記憶體映射機制不同,不可直接将本地變量傳給使用者态的記憶體區;處于硬體、軟體中斷時,無法直接向使用者記憶體區傳遞資料,代碼執行不可中斷。針對傳統的程序間通信機制,他們均無法直接在核心态和使用者态之間使用,原因如下表:

通信方法

無法介于核心态與使用者态的原因

管道(不包括命名管道)

局限于父子程序間的通信。

消息隊列

在硬、軟中斷中無法無阻塞地接收資料。

信号量

無法介于核心态和使用者态使用。

記憶體共享

需要信号量輔助,而信号量又無法使用。

套接字

在硬、軟中斷中無法無阻塞地接收資料。

1*(引自 參考文獻5)

解決核心态和使用者态通信機制可分為兩類:

處于有使用者上下文時,可以使用Linux提供的copy_from_user()和copy_to_user()函數完成,但由于這兩個函數可能阻塞,是以不能在硬體、軟體的中斷過程中使用。

處于硬、軟體中斷時。

2.1 可以通過Linux核心提供的spinlock自旋鎖實作核心線程與中斷過程的同步,由于核心線程運作在有上下文的程序中,是以可以在核心線程中使用套接字或消息隊列來取得使用者空間的資料,然後再将資料通過臨界區傳遞給中斷過程.

2.2 通過Netlink機制實作。Netlink 套接字的通信依據是一個對應于程序的辨別,一般定為該程序的 ID。Netlink通信最大的特點是對對中斷過程的支援,它在核心空間接收使用者空間資料時不再需要使用者自行啟動一個核心線程,而是通過另一個軟中斷調用使用者事先指定的接收函數。通過軟中斷而不是自行啟動核心線程保證了資料傳輸的及時性。

3.2 Netlink優點

Netlink相對于其他的通信機制具有以下優點:

使用Netlink通過自定義一種新的協定并加入協定族即可通過socket API使用Netlink協定完成資料交換,而ioctl和proc檔案系統均需要通過程式加入相應的裝置或檔案。

Netlink使用socket緩存隊列,是一種異步通信機制,而ioctl是同步通信機制,如果傳輸的資料量較大,會影響系統性能。

Netlink支援多點傳播,屬于一個Netlink組的子產品和程序都能獲得該多點傳播消息。

Netlink允許核心發起會話,而ioctl和系統調用隻能由使用者空間程序發起。

在核心源碼有關Netlink協定的頭檔案中包含了核心預定義的協定類型,如下所示:

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

上述這些協定已經為不同的系統應用所使用,每種不同的應用都有特有的傳輸資料的格式,是以如果使用者不使用這些協定,需要加入自己定義的協定号。對于每一個Netlink協定類型,可以有多達 32多點傳播組,每一個多點傳播組用一個位表示,Netlink 的多點傳播特性使得發送消息給同一個組僅需要一次系統調用,因而對于需要多撥消息的應用而言,大大地降低了系統調用的次數。

建立Netlink會話過程如下:

核心使用與标準socket API類似的一套API完成通信過程。首先通過netlink_kernel_create()建立套接字,該函數的原型如下:

struct sock *netlink_kernel_create(struct net *net, int unit,unsigned int groups, void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, 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調試使用者空間的程式。

使用者空間程式關鍵代碼如下:

int send_pck_to_kern(u8 op, const u8 *data, u16 data_len){ struct user_data_ *pck; int ret; pck = (struct user_data_*)calloc(1, sizeof(*pck) + data_len); if(!pck) { printf("calloc in %s failed!!!\n", __FUNCTION__); return -1; } pck->magic_num = MAGIC_NUM_RNQ; pck->op = op; pck->data_len = data_len; memcpy(pck->data, data, data_len); ret = send_to_kern((const u8*)pck, sizeof(*pck) + data_len); if(ret) printf("send_to_kern in %s failed!!!\n", __FUNCTION__); free(pck); return ret ? -1 : 0;} static void recv_from_nl(){ char buf[1000]; int len; struct iovec iov = {buf, sizeof(buf)}; struct sockaddr_nl sa; struct msghdr msg; struct nlmsghdr *nh; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; msg.msg_iovlen = 1; //len = recvmsg(nl_sock, &msg, 0); len = recvmsg(nl_sock, &msg, 0); for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT (nh, len)) { // The end of multipart message. if (nh->nlmsg_type == NLMSG_DONE) { puts("nh->nlmsg_type == NLMSG_DONE"); return; } if (nh->nlmsg_type == NLMSG_ERROR) { // Do some error handling. puts("nh->nlmsg_type == NLMSG_ERROR"); return; } #if 1 puts("Data received from kernel:"); hex_dump((u8*)NLMSG_DATA(nh), NLMSG_PAYLOAD(nh, 0));#endif }}

核心子產品需要防止資源搶占,保證Netlink資源互斥占有,核心子產品部分關鍵代碼如下:

static void nl_rcv(struct sk_buff *skb){ mutex_lock(&nl_mtx); netlink_rcv_skb(skb, &nl_rcv_msg); mutex_unlock(&nl_mtx);} static int nl_send_msg(const u8 *data, int data_len){ struct nlmsghdr *rep; u8 *res; struct sk_buff *skb; if(g_pid < 0 || g_nl_sk == NULL) { printk("Invalid parameter, g_pid = %d, g_nl_sk = %p\n", g_pid, g_nl_sk); return -1; } skb = nlmsg_new(data_len, GFP_KERNEL); if(!skb) { printk("nlmsg_new failed!!!\n"); return -1; } if(g_debug_level > 0) { printk("Data to be send to user space:\n"); hex_dump((void*)data, data_len); } rep = __nlmsg_put(skb, g_pid, 0, NLMSG_NOOP, data_len, 0); res = nlmsg_data(rep); memcpy(res, data, data_len); netlink_unicast(g_nl_sk, skb, g_pid, MSG_DONTWAIT); return 0;} static int nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh){ const u8 res_data[] = "Hello, user"; size_t data_len; u8 *buf; struct user_data_ *pck; struct user_req *req, *match = NULL; g_pid = NETLINK_CB(skb).pid; buf = (u8*)NLMSG_DATA(nlh); data_len = nlmsg_len(nlh); if(data_len < sizeof(struct user_data_)) { printk("Too short data from user space!!!\n"); return -1; } pck = (struct user_data_ *)buf; if(pck->magic_num != MAGIC_NUM_RNQ) { printk("Magic number not matched!!!\n"); return -1; } if(g_debug_level > 0) { printk("Data from user space:\n"); hex_dump(buf, data_len); } req = user_reqs; while(req->op) { if(req->op == pck->op) { match = req; break; } req++; } if(match) { match->handler(buf, data_len); } nl_send_msg(res_data, sizeof(res_data)); return 0;}

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是一個通用的協定類型,它是專門為使用者使用的,是以,使用者可以直接使用它,而不必再添加新的協定類型。核心預定義的協定類型有:

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

對于每一個netlink協定類型,可以有多達 32多點傳播組,每一個多點傳播組用一個位表示,netlink 的多點傳播特性使得發送消息給同一個組僅需要一次系統調用,因而對于需要多撥消息的應用而言,大大地降低了系統調用的次數。

函數 bind() 用于把一個打開的 netlink socket 與 netlink 源 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 消息時必須提供該消息頭。

struct nlmsghdr { __u32 nlmsg_len; __u16 nlmsg_type; __u16 nlmsg_flags; __u32 nlmsg_seq; __u32 nlmsg_pid;};

字段 nlmsg_len 指定消息的總長度,包括緊跟該結構的資料部分長度以及該結構的大小,字段 nlmsg_type 用于應用内部定義消息的類型,它對 netlink 核心實作是透明的,是以大部分情況下設定為 0,字段 nlmsg_flags 用于設定消息标志,可用的标志包括:

#define NLM_F_REQUEST 1 #define NLM_F_MULTI 2 #define NLM_F_ACK 4 #define NLM_F_ECHO 8 #define NLM_F_ROOT 0x100 #define NLM_F_MATCH 0x200 #define NLM_F_ATOMIC 0x400 #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) #define NLM_F_REPLACE 0x100 #define NLM_F_EXCL 0x200 #define NLM_F_CREATE 0x400 #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。下面是一個示例:

#define MAX_MSGSIZE 1024char 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(); 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。

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 看到全部的内容,同時核心也把該消息發送給使用者态接收程式,使用者态接收程式将把接收到的内容輸出到螢幕上。