1?引言
Linux中的進(jìn)程間通信機(jī)制源自于Unix平臺(tái)上的進(jìn)程通信機(jī)制。Unix的兩大分支AT&T Unix和BSD Unix在進(jìn)程通信實(shí)現(xiàn)機(jī)制上的各有所不同,前者形成了運(yùn)行在單個(gè)計(jì)算機(jī)上的System V IPC,后者則實(shí)現(xiàn)了基于socket的進(jìn)程間通信機(jī)制。同時(shí)Linux也遵循IEEE制定的Posix IPC標(biāo)準(zhǔn),在三者的基礎(chǔ)之上實(shí)現(xiàn)了以下幾種主要的IPC機(jī)制:管道(Pipe)及命名管道(Named Pipe),信號(hào)(Signal),消息隊(duì)列(Message queue),共享內(nèi)存(Shared Memory),信號(hào)量(Semaphore),套接字(Socket)。通過這些IPC機(jī)制,用戶空間進(jìn)程之間可以完成互相通信。為了完成內(nèi)核空間與用戶空間通信,Linux提供了基于socket的Netlink通信機(jī)制,可以實(shí)現(xiàn)內(nèi)核與用戶空間數(shù)據(jù)的及時(shí)交換。
本文第2節(jié)概述相關(guān)研究工作,第3節(jié)與其他IPC機(jī)制對(duì)比,詳細(xì)介紹Netlink機(jī)制及其關(guān)鍵技術(shù),第4節(jié)使用KGDB+GDB組合調(diào)試,通過一個(gè)示例程序演示Netlink通信過程。第5節(jié)做總結(jié)并指出Netlink通信機(jī)制的不足之處。
2?相關(guān)研究
到目前Linux提供了9種機(jī)制完成內(nèi)核與用戶空間的數(shù)據(jù)交換,分別是內(nèi)核啟動(dòng)參數(shù)、模塊參數(shù)與 sysfs、sysctl、系統(tǒng)調(diào)用、netlink、procfs、seq_file、debugfs和relayfs,其中模塊參數(shù)與sysfs、procfs、debugfs、relayfs是基于文件系統(tǒng)的通信機(jī)制,用于內(nèi)核空間向用戶控件輸出信息;sysctl、系統(tǒng)調(diào)用是由用戶空間發(fā)起的通信機(jī)制。由此可見,以上均為單工通信機(jī)制,在內(nèi)核空間與用戶空間的雙向互動(dòng)數(shù)據(jù)交換上略顯不足。Netlink是基于socket的通信機(jī)制,由于socket本身的雙共性、突發(fā)性、不阻塞特點(diǎn),因此能夠很好的滿足內(nèi)核與用戶空間小量數(shù)據(jù)的及時(shí)交互,因此在Linux 2.6內(nèi)核中廣泛使用,例如SELinux,Linux系統(tǒng)的防火墻分為內(nèi)核態(tài)的netfilter和用戶態(tài)的iptables,netfilter與iptables的數(shù)據(jù)交換就是通過Netlink機(jī)制完成。?
3 Netlink機(jī)制及其關(guān)鍵技術(shù)
3.1 Netlink機(jī)制
Linux操作系統(tǒng)中當(dāng)CPU處于內(nèi)核狀態(tài)時(shí),可以分為有用戶上下文的狀態(tài)和執(zhí)行硬件、軟件中斷兩種。其中當(dāng)處于有用戶上下文時(shí),由于內(nèi)核態(tài)和用戶態(tài)的內(nèi)存映射機(jī)制不同,不可直接將本地變量傳給用戶態(tài)的內(nèi)存區(qū);處于硬件、軟件中斷時(shí),無(wú)法直接向用戶內(nèi)存區(qū)傳遞數(shù)據(jù),代碼執(zhí)行不可中斷。針對(duì)傳統(tǒng)的進(jìn)程間通信機(jī)制,他們均無(wú)法直接在內(nèi)核態(tài)和用戶態(tài)之間使用,原因如下表:
通信方法
無(wú)法介于內(nèi)核態(tài)與用戶態(tài)的原因
管道(不包括命名管道)
局限于父子進(jìn)程間的通信。
消息隊(duì)列
在硬、軟中斷中無(wú)法無(wú)阻塞地接收數(shù)據(jù)。
信號(hào)量
無(wú)法介于內(nèi)核態(tài)和用戶態(tài)使用。
內(nèi)存共享
需要信號(hào)量輔助,而信號(hào)量又無(wú)法使用。
套接字
在硬、軟中斷中無(wú)法無(wú)阻塞地接收數(shù)據(jù)。
1*(引自 參考文獻(xiàn)5)
??? 解決內(nèi)核態(tài)和用戶態(tài)通信機(jī)制可分為兩類:
處于有用戶上下文時(shí),可以使用Linux提供的copy_from_user()和copy_to_user()函數(shù)完成,但由于這兩個(gè)函數(shù)可能阻塞,因此不能在硬件、軟件的中斷過程中使用。
處于硬、軟件中斷時(shí)。
2.1?? 可以通過Linux內(nèi)核提供的spinlock自旋鎖實(shí)現(xiàn)內(nèi)核線程與中斷過程的同步,由于內(nèi)核線程運(yùn)行在有上下文的進(jìn)程中,因此可以在內(nèi)核線程中使用套接字或消息隊(duì)列來取得用戶空間的數(shù)據(jù),然后再將數(shù)據(jù)通過臨界區(qū)傳遞給中斷過程.
2.2?? 通過Netlink機(jī)制實(shí)現(xiàn)。Netlink 套接字的通信依據(jù)是一個(gè)對(duì)應(yīng)于進(jìn)程的標(biāo)識(shí),一般定為該進(jìn)程的 ID。Netlink通信最大的特點(diǎn)是對(duì)對(duì)中斷過程的支持,它在內(nèi)核空間接收用戶空間數(shù)據(jù)時(shí)不再需要用戶自行啟動(dòng)一個(gè)內(nèi)核線程,而是通過另一個(gè)軟中斷調(diào)用用戶事先指定的接收函數(shù)。通過軟中斷而不是自行啟動(dòng)內(nèi)核線程保證了數(shù)據(jù)傳輸?shù)募皶r(shí)性。
3.2 Netlink優(yōu)點(diǎn)
Netlink相對(duì)于其他的通信機(jī)制具有以下優(yōu)點(diǎn):
使用Netlink通過自定義一種新的協(xié)議并加入?yún)f(xié)議族即可通過socket API使用Netlink協(xié)議完成數(shù)據(jù)交換,而ioctl和proc文件系統(tǒng)均需要通過程序加入相應(yīng)的設(shè)備或文件。
Netlink使用socket緩存隊(duì)列,是一種異步通信機(jī)制,而ioctl是同步通信機(jī)制,如果傳輸?shù)臄?shù)據(jù)量較大,會(huì)影響系統(tǒng)性能。
Netlink支持多播,屬于一個(gè)Netlink組的模塊和進(jìn)程都能獲得該多播消息。
Netlink允許內(nèi)核發(fā)起會(huì)話,而ioctl和系統(tǒng)調(diào)用只能由用戶空間進(jìn)程發(fā)起。
在內(nèi)核源碼有關(guān)Netlink協(xié)議的頭文件中包含了內(nèi)核預(yù)定義的協(xié)議類型,如下所示:
#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
上述這些協(xié)議已經(jīng)為不同的系統(tǒng)應(yīng)用所使用,每種不同的應(yīng)用都有特有的傳輸數(shù)據(jù)的格式,因此如果用戶不使用這些協(xié)議,需要加入自己定義的協(xié)議號(hào)。對(duì)于每一個(gè)Netlink協(xié)議類型,可以有多達(dá) 32多播組,每一個(gè)多播組用一個(gè)位表示,Netlink 的多播特性使得發(fā)送消息給同一個(gè)組僅需要一次系統(tǒng)調(diào)用,因而對(duì)于需要多撥消息的應(yīng)用而言,大大地降低了系統(tǒng)調(diào)用的次數(shù)。
建立Netlink會(huì)話過程如下:
內(nèi)核使用與標(biāo)準(zhǔn)socket API類似的一套API完成通信過程。首先通過netlink_kernel_create()創(chuàng)建套接字,該函數(shù)的原型如下:
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參數(shù)是網(wǎng)絡(luò)設(shè)備命名空間指針,input函數(shù)是netlink socket在接受到消息時(shí)調(diào)用的回調(diào)函數(shù)指針,module默認(rèn)為THIS_MODULE.
然后用戶空間進(jìn)程使用標(biāo)準(zhǔn)Socket API來創(chuàng)建套接字,將進(jìn)程ID發(fā)送至內(nèi)核空間,用戶空間創(chuàng)建使用socket()創(chuàng)建套接字,該函數(shù)的原型如下:
int socket(int domain, int type, int protocol);
其中domain值為PF_NETLINK,即Netlink使用協(xié)議族。protocol為Netlink提供的協(xié)議或者是用戶自定義的協(xié)議,Netlink提供的協(xié)議包括NETLINK_ROUTE, NETLINK_FIREWALL, NETLINK_ARPD, NETLINK_ROUTE6和 NETLINK_IP6_FW。
接著使用bind函數(shù)綁定。Netlink的bind()函數(shù)把一個(gè)本地socket地址(源socket地址)與一個(gè)打開的socket進(jìn)行關(guān)聯(lián)。完成綁定,內(nèi)核空間接收到用戶進(jìn)程ID之后便可以進(jìn)行通訊。
用戶空間進(jìn)程發(fā)送數(shù)據(jù)使用標(biāo)準(zhǔn)socket API中sendmsg()函數(shù)完成,使用時(shí)需添加struct msghdr消息和nlmsghdr消息頭。一個(gè)netlink消息體由nlmsghdr和消息的payload部分組成,輸入消息后,內(nèi)核會(huì)進(jìn)入nlmsghdr指向的緩沖區(qū)。
內(nèi)核空間發(fā)送數(shù)據(jù)使用獨(dú)立創(chuàng)建的sk_buff緩沖區(qū),Linux定義了如下宏方便對(duì)于緩沖區(qū)地址的設(shè)置,如下所示:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
在對(duì)緩沖區(qū)設(shè)置完成消息地址之后,可以使用netlink_unicast()來發(fā)布單播消息,netlink_unicast()原型如下:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
參數(shù)sk為函數(shù)netlink_kernel_create()返回的socket,參數(shù)skb存放消息,它的data字段指向要發(fā)送的netlink消息結(jié)構(gòu),而skb的控制塊保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便設(shè)置該控制塊,參數(shù)pid為接收消息進(jìn)程的pid,參數(shù)nonblock表示該函數(shù)是否為非阻塞,如果為1,該函數(shù)將在沒有接收緩存可利用時(shí)立即返回,而如果為0,該函數(shù)在沒有接收緩存可利用時(shí)睡眠。
內(nèi)核模塊或子系統(tǒng)也可以使用函數(shù)netlink_broadcast來發(fā)送廣播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);
前面的三個(gè)參數(shù)與netlink_unicast相同,參數(shù)group為接收消息的多播組,該參數(shù)的每一個(gè)代表一個(gè)多播組,因此如果發(fā)送給多個(gè)多播組,就把該參數(shù)設(shè)置為多個(gè)多播組組ID的位或。參數(shù)allocation為內(nèi)核內(nèi)存分配類型,一般地為GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。
接收數(shù)據(jù)時(shí)程序需要申請(qǐng)足夠大的空間來存儲(chǔ)netlink消息頭和消息的payload部分。然后使用標(biāo)準(zhǔn)函數(shù)接口recvmsg()來接收netlink消息
4 Netlink通信過程
調(diào)試平臺(tái):Vmware 5.5 + Fedora Core 10(兩臺(tái),一臺(tái)作為host機(jī),一臺(tái)作為target機(jī))。
調(diào)試程序:分為內(nèi)核模塊和用戶空間程序兩部分,當(dāng)內(nèi)核模塊被加載后,運(yùn)行用戶空間程序,由用戶空間發(fā)起Netlink會(huì)話,和內(nèi)核模塊進(jìn)行數(shù)據(jù)交換。
被加載的內(nèi)核模塊無(wú)法通過外加的調(diào)試器進(jìn)行調(diào)試,KGDB提供了一種內(nèi)核源碼級(jí)別的調(diào)試機(jī)制。Linux內(nèi)核自2.6.26版本之后在內(nèi)核中內(nèi)置了KGDB選項(xiàng),編譯內(nèi)核時(shí)需要選擇與之相關(guān)的選項(xiàng),調(diào)試時(shí)host端需使用帶有符號(hào)表的vmlinz內(nèi)核,target端使用gdb調(diào)試用戶空間的程序。
用戶空間程序關(guān)鍵代碼如下:
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 }}
內(nèi)核模塊需要防止資源搶占,保證Netlink資源互斥占有,內(nèi)核模塊部分關(guān)鍵代碼如下:
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.其他相關(guān)說明
????Netlink 是一種特殊的 socket,它是 Linux 所特有的,類似于 BSD 中的AF_ROUTE 但又遠(yuǎn)比它的功能強(qiáng)大,目前在最新的 Linux 內(nèi)核(2.6.14)中使用netlink 進(jìn)行應(yīng)用與內(nèi)核通信的應(yīng)用很多,包括:路由 daemon(NETLINK_ROUTE),1-wire 子系統(tǒng)(NETLINK_W1),用戶態(tài) socket 協(xié)議(NETLINK_USERSOCK),防火墻(NETLINK_FIREWALL),socket 監(jiān)視(NETLINK_INET_DIAG),netfilter 日志(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系統(tǒng)(NETLINK_ISCSI),進(jìn)程審計(jì)(NETLINK_AUDIT),轉(zhuǎn)發(fā)信息表查詢(NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系統(tǒng)(NETLINK_NETFILTER),IPv6 防火墻(NETLINK_IP6_FW),DECnet 路由信息(NETLINK_DNRTMSG),內(nèi)核事件向用戶態(tài)通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)。
????Netlink 是一種在內(nèi)核與用戶應(yīng)用間進(jìn)行雙向數(shù)據(jù)傳輸?shù)姆浅:玫姆绞剑脩魬B(tài)應(yīng)用使用標(biāo)準(zhǔn)的 socket API 就可以使用 netlink 提供的強(qiáng)大功能,內(nèi)核態(tài)需要使用專門的內(nèi)核 API 來使用 netlink。
Netlink 相對(duì)于系統(tǒng)調(diào)用,ioctl 以及 /proc 文件系統(tǒng)而言具有以下優(yōu)點(diǎn):
????1,為了使用 netlink,用戶僅需要在 include/linux/netlink.h 中增加一個(gè)新類型的 netlink 協(xié)議定義即可, 如 #define NETLINK_MYTEST 17 然后,內(nèi)核和用戶態(tài)應(yīng)用就可以立即通過 socket API 使用該 netlink 協(xié)議類型進(jìn)行數(shù)據(jù)交換。但系統(tǒng)調(diào)用需要增加新的系統(tǒng)調(diào)用,ioctl 則需要增加設(shè)備或文件, 那需要不少代碼,proc 文件系統(tǒng)則需要在 /proc 下添加新的文件或目錄,那將使本來就混亂的 /proc 更加混亂。
????2. netlink是一種異步通信機(jī)制,在內(nèi)核與用戶態(tài)應(yīng)用之間傳遞的消息保存在socket緩存隊(duì)列中,發(fā)送消息只是把消息保存在接收者的socket的接收隊(duì)列,而不需要等待接收者收到消息,但系統(tǒng)調(diào)用與 ioctl 則是同步通信機(jī)制,如果傳遞的數(shù)據(jù)太長(zhǎng),將影響調(diào)度粒度。
????3.使用 netlink 的內(nèi)核部分可以采用模塊的方式實(shí)現(xiàn),使用 netlink 的應(yīng)用部分和內(nèi)核部分沒有編譯時(shí)依賴,但系統(tǒng)調(diào)用就有依賴,而且新的系統(tǒng)調(diào)用的實(shí)現(xiàn)必須靜態(tài)地連接到內(nèi)核中,它無(wú)法在模塊中實(shí)現(xiàn),使用新系統(tǒng)調(diào)用的應(yīng)用在編譯時(shí)需要依賴內(nèi)核。
????4.netlink 支持多播,內(nèi)核模塊或應(yīng)用可以把消息多播給一個(gè)netlink組,屬于該neilink 組的任何內(nèi)核模塊或應(yīng)用都能接收到該消息,內(nèi)核事件向用戶態(tài)的通知機(jī)制就使用了這一特性,任何對(duì)內(nèi)核事件感興趣的應(yīng)用都能收到該子系統(tǒng)發(fā)送的內(nèi)核事件,在后面的文章中將介紹這一機(jī)制的使用。
????5.內(nèi)核可以使用 netlink 首先發(fā)起會(huì)話,但系統(tǒng)調(diào)用和 ioctl 只能由用戶應(yīng)用發(fā)起調(diào)用。
????6.netlink 使用標(biāo)準(zhǔn)的 socket API,因此很容易使用,但系統(tǒng)調(diào)用和 ioctl則需要專門的培訓(xùn)才能使用。
用戶態(tài)使用 netlink
???用戶態(tài)應(yīng)用使用標(biāo)準(zhǔn)的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket,查詢手冊(cè)頁(yè)可以了解這些函數(shù)的使用細(xì)節(jié),本文只是講解使用 netlink 的用戶應(yīng)該如何使用這些函數(shù)。注意,使用 netlink 的應(yīng)用必須包含頭文件 linux/netlink.h。當(dāng)然 socket 需要的頭文件也必不可少,sys/socket.h。
???為了創(chuàng)建一個(gè) netlink socket,用戶需要使用如下參數(shù)調(diào)用 socket():
socket(AF_NETLINK, SOCK_RAW, netlink_type)
???第一個(gè)參數(shù)必須是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它們倆實(shí)際為一個(gè)東西,它表示要使用netlink,第二個(gè)參數(shù)必須是SOCK_RAW或SOCK_DGRAM,第三個(gè)參數(shù)指定netlink協(xié)議類型,如前面講的用戶自定義協(xié)議類型NETLINK_MYTEST, NETLINK_GENERIC是一個(gè)通用的協(xié)議類型,它是專門為用戶使用的,因此,用戶可以直接使用它,而不必再添加新的協(xié)議類型。內(nèi)核預(yù)定義的協(xié)議類型有:
#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
???對(duì)于每一個(gè)netlink協(xié)議類型,可以有多達(dá) 32多播組,每一個(gè)多播組用一個(gè)位表示,netlink 的多播特性使得發(fā)送消息給同一個(gè)組僅需要一次系統(tǒng)調(diào)用,因而對(duì)于需要多撥消息的應(yīng)用而言,大大地降低了系統(tǒng)調(diào)用的次數(shù)。
???函數(shù) bind() 用于把一個(gè)打開的 netlink socket 與 netlink 源 socket 地址綁定在一起。netlink socket 的地址結(jié)構(gòu)如下:
struct sockaddr_nl { sa_family_t nl_family; unsigned short nl_pad; __u32 nl_pid; __u32 nl_groups; };
???字段 nl_family 必須設(shè)置為 AF_NETLINK 或著 PF_NETLINK,字段 nl_pad 當(dāng)前沒有使用,因此要總是設(shè)置為 0,字段 nl_pid 為接收或發(fā)送消息的進(jìn)程的 ID,如果希望內(nèi)核處理消息或多播消息,就把該字段設(shè)置為 0,否則設(shè)置為處理消息的進(jìn)程 ID。字段 nl_groups 用于指定多播組,bind 函數(shù)用于把調(diào)用進(jìn)程加入到該字段指定的多播組,如果設(shè)置為 0,表示調(diào)用者不加入任何多播組。
???傳遞給 bind 函數(shù)的地址的 nl_pid 字段應(yīng)當(dāng)設(shè)置為本進(jìn)程的進(jìn)程 ID,這相當(dāng)于 netlink socket 的本地地址。但是,對(duì)于一個(gè)進(jìn)程的多個(gè)線程使用 netlink socket 的情況,字段 nl_pid 則可以設(shè)置為其它的值,如:
pthread_self() << 16 | getpid();
???因此字段 nl_pid 實(shí)際上未必是進(jìn)程 ID,它只是用于區(qū)分不同的接收者或發(fā)送者的一個(gè)標(biāo)識(shí),用戶可以根據(jù)自己需要設(shè)置該字段。函數(shù) bind 的調(diào)用方式如下:
bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));
???fd為前面的 socket 調(diào)用返回的文件描述符,參數(shù) nladdr 為 struct sockaddr_nl 類型的地址。為了發(fā)送一個(gè) netlink 消息給內(nèi)核或其他用戶態(tài)應(yīng)用,需要填充目標(biāo) netlink socket 地址,此時(shí),字段 nl_pid 和 nl_groups 分別表示接收消息者的進(jìn)程 ID 與多播組。如果字段 nl_pid 設(shè)置為 0,表示消息接收者為內(nèi)核或多播組,如果 nl_groups為 0,表示該消息為單播消息,否則表示多播消息。使用函數(shù) sendmsg 發(fā)送 netlink 消息時(shí)還需要引用結(jié)構(gòu) struct msghdr、struct nlmsghdr 和 struct iovec,結(jié)構(gòu) struct msghdr 需如下設(shè)置:
struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr);
其中 nladdr 為消息接收者的 netlink 地址。
???struct nlmsghdr 為 netlink socket 自己的消息頭,這用于多路復(fù)用和多路分解 netlink 定義的所有協(xié)議類型以及其它一些控制,netlink 的內(nèi)核實(shí)現(xiàn)將利用這個(gè)消息頭來多路復(fù)用和多路分解已經(jīng)其它的一些控制,因此它也被稱為netlink 控制塊。因此,應(yīng)用在發(fā)送 netlink 消息時(shí)必須提供該消息頭。
struct nlmsghdr { __u32 nlmsg_len; __u16 nlmsg_type; __u16 nlmsg_flags; __u32 nlmsg_seq; __u32 nlmsg_pid;};
字段 nlmsg_len 指定消息的總長(zhǎng)度,包括緊跟該結(jié)構(gòu)的數(shù)據(jù)部分長(zhǎng)度以及該結(jié)構(gòu)的大小,字段 nlmsg_type 用于應(yīng)用內(nèi)部定義消息的類型,它對(duì) netlink 內(nèi)核實(shí)現(xiàn)是透明的,因此大部分情況下設(shè)置為 0,字段 nlmsg_flags 用于設(shè)置消息標(biāo)志,可用的標(biāo)志包括:
#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
標(biāo)志NLM_F_REQUEST用于表示消息是一個(gè)請(qǐng)求,所有應(yīng)用首先發(fā)起的消息都應(yīng)設(shè)置該標(biāo)志。
標(biāo)志NLM_F_MULTI 用于指示該消息是一個(gè)多部分消息的一部分,后續(xù)的消息可以通過宏NLMSG_NEXT來獲得。
宏NLM_F_ACK表示該消息是前一個(gè)請(qǐng)求消息的響應(yīng),順序號(hào)與進(jìn)程ID可以把請(qǐng)求與響應(yīng)關(guān)聯(lián)起來。
標(biāo)志NLM_F_ECHO表示該消息是相關(guān)的一個(gè)包的回傳。
標(biāo)志NLM_F_ROOT 被許多 netlink 協(xié)議的各種數(shù)據(jù)獲取操作使用,該標(biāo)志指示被請(qǐng)求的數(shù)據(jù)表應(yīng)當(dāng)整體返回用戶應(yīng)用,而不是一個(gè)條目一個(gè)條目地返回。有該標(biāo)志的請(qǐng)求通常導(dǎo)致響應(yīng)消息設(shè)置NLM_F_MULTI標(biāo)志。注意,當(dāng)設(shè)置了該標(biāo)志時(shí),請(qǐng)求是協(xié)議特定的,因此,需要在字段 nlmsg_type 中指定協(xié)議類型。
標(biāo)志 NLM_F_MATCH 表示該協(xié)議特定的請(qǐng)求只需要一個(gè)數(shù)據(jù)子集,數(shù)據(jù)子集由指定的協(xié)議特定的過濾器來匹配。
標(biāo)志 NLM_F_ATOMIC 指示請(qǐng)求返回的數(shù)據(jù)應(yīng)當(dāng)原子地收集,這預(yù)防數(shù)據(jù)在獲取期間被修改。
標(biāo)志 NLM_F_DUMP 未實(shí)現(xiàn)。
標(biāo)志 NLM_F_REPLACE 用于取代在數(shù)據(jù)表中的現(xiàn)有條目。
標(biāo)志 NLM_F_EXCL_ 用于和 CREATE 和 APPEND 配合使用,如果條目已經(jīng)存在,將失敗。
標(biāo)志 NLM_F_CREATE 指示應(yīng)當(dāng)在指定的表中創(chuàng)建一個(gè)條目。
標(biāo)志 NLM_F_APPEND 指示在表末尾添加新的條目。
內(nèi)核需要讀取和修改這些標(biāo)志,對(duì)于一般的使用,用戶把它設(shè)置為 0 就可以,只是一些高級(jí)應(yīng)用(如 netfilter 和路由 daemon 需要它進(jìn)行一些復(fù)雜的操作),字段 nlmsg_seq 和 nlmsg_pid 用于應(yīng)用追蹤消息,前者表示順序號(hào),后者為消息來源進(jìn)程 ID。下面是一個(gè)示例:
#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;
結(jié)構(gòu) struct iovec 用于把多個(gè)消息通過一次系統(tǒng)調(diào)用來發(fā)送,下面是該結(jié)構(gòu)使用示例:
struct iovec iov; iov.iov_base = (void *)nlhdr; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1;
在完成以上步驟后,消息就可以通過下面語(yǔ)句直接發(fā)送:
sendmsg(fd, &msg, 0);
應(yīng)用接收消息時(shí)需要首先分配一個(gè)足夠大的緩存來保存消息頭以及消息的數(shù)據(jù)部分,然后填充消息頭,添完后就可以直接調(diào)用函數(shù) 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調(diào)用打開的netlink socket描述符。
在消息接收后,nlhdr指向接收到的消息的消息頭,nladdr保存了接收到的消息的目標(biāo)地址,宏NLMSG_DATA(nlhdr)返回指向消息的數(shù)據(jù)部分的指針。
在linux/netlink.h中定義了一些方便對(duì)消息進(jìn)行處理的宏,這些宏包括:
#define NLMSG_ALIGNTO 4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
宏NLMSG_ALIGN(len)用于得到不小于len且字節(jié)對(duì)齊的最小數(shù)值。
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
宏NLMSG_LENGTH(len)用于計(jì)算數(shù)據(jù)部分長(zhǎng)度為len時(shí)實(shí)際的消息長(zhǎng)度。它一般用于分配消息緩存。
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字節(jié)對(duì)齊的最小數(shù)值,它也用于分配消息緩存。
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
宏NLMSG_DATA(nlh)用于取得消息的數(shù)據(jù)部分的首地址,設(shè)置和讀取消息數(shù)據(jù)部分時(shí)需要使用該宏。
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
宏NLMSG_NEXT(nlh,len)用于得到下一個(gè)消息的首地址,同時(shí)len也減少為剩余消息的總長(zhǎng)度,該宏一般在一個(gè)消息被分成幾個(gè)部分發(fā)送或接收時(shí)使用。
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
宏NLMSG_OK(nlh,len)用于判斷消息是否有l(wèi)en這么長(zhǎng)。
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
宏NLMSG_PAYLOAD(nlh,len)用于返回payload的長(zhǎng)度。
函數(shù)close用于關(guān)閉打開的netlink socket。
netlink內(nèi)核API
netlink的內(nèi)核實(shí)現(xiàn)在.c文件net/core/af_netlink.c中,內(nèi)核模塊要想使用netlink,也必須包含頭文件linux/netlink.h。內(nèi)核使用netlink需要專門的API,這完全不同于用戶態(tài)應(yīng)用對(duì)netlink的使用。如果用戶需要增加新的netlink協(xié)議類型,必須通過修改linux/netlink.h來實(shí)現(xiàn),當(dāng)然,目前的netlink實(shí)現(xiàn)已經(jīng)包含了一個(gè)通用的協(xié)議類型NETLINK_GENERIC以方便用戶使用,用戶可以直接使用它而不必增加新的協(xié)議類型。前面講到,為了增加新的netlink協(xié)議類型,用戶僅需增加如下定義到linux/netlink.h就可以:
#define NETLINK_MYTEST 17
只要增加這個(gè)定義之后,用戶就可以在內(nèi)核的任何地方引用該協(xié)議。
在內(nèi)核中,為了創(chuàng)建一個(gè)netlink socket用戶需要調(diào)用如下函數(shù):
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));
參數(shù)unit表示netlink協(xié)議類型,如NETLINK_MYTEST,參數(shù)input則為內(nèi)核模塊定義的netlink消息處理函數(shù),當(dāng)有消息到達(dá)這個(gè)netlink socket時(shí),該input函數(shù)指針就會(huì)被引用。函數(shù)指針input的參數(shù)sk實(shí)際上就是函數(shù)netlink_kernel_create返回的struct sock指針,sock實(shí)際是socket的一個(gè)內(nèi)核表示數(shù)據(jù)結(jié)構(gòu),用戶態(tài)應(yīng)用創(chuàng)建的socket在內(nèi)核中也會(huì)有一個(gè)struct sock結(jié)構(gòu)來表示。下面是一個(gè)input函數(shù)的示例:
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); } }
函數(shù)input()會(huì)在發(fā)送進(jìn)程執(zhí)行sendmsg()時(shí)被調(diào)用,這樣處理消息比較及時(shí),但是,如果消息特別長(zhǎng)時(shí),這樣處理將增加系統(tǒng)調(diào)用sendmsg()的執(zhí)行時(shí)間,對(duì)于這種情況,可以定義一個(gè)內(nèi)核線程專門負(fù)責(zé)消息接收,而函數(shù)input的工作只是喚醒該內(nèi)核線程,這樣sendmsg將很快返回。
函數(shù)skb = skb_dequeue(&sk->receive_queue)用于取得socket sk的接收隊(duì)列上的消息,返回為一個(gè)struct sk_buff的結(jié)構(gòu),skb->data指向?qū)嶋H的netlink消息。
函數(shù)skb_recv_datagram(nl_sk)也用于在netlink socket nl_sk上接收消息,與skb_dequeue的不同指出是,如果socket的接收隊(duì)列上沒有消息,它將導(dǎo)致調(diào)用進(jìn)程睡眠在等待隊(duì)列nl_sk->sk_sleep,因此它必須在進(jìn)程上下文使用,剛才講的內(nèi)核線程就可以采用這種方式來接收消息。
下面的函數(shù)input就是這種使用的示例:
void input (struct sock *sk, int len) { wake_up_interruptible(sk->sk_sleep); }
當(dāng)內(nèi)核中發(fā)送netlink消息時(shí),也需要設(shè)置目標(biāo)地址與源地址,而且內(nèi)核中消息是通過struct sk_buff來管理的, linux/netlink.h中定義了一個(gè)宏:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
來方便消息的地址設(shè)置。下面是一個(gè)消息地址設(shè)置的例子:
NETLINK_CB(skb).pid = 0; NETLINK_CB(skb).dst_pid = 0; NETLINK_CB(skb).dst_group = 1;
字段pid表示消息發(fā)送者進(jìn)程ID,也即源地址,對(duì)于內(nèi)核,它為 0, dst_pid 表示消息接收者進(jìn)程 ID,也即目標(biāo)地址,如果目標(biāo)為組或內(nèi)核,它設(shè)置為 0,否則 dst_group 表示目標(biāo)組地址,如果它目標(biāo)為某一進(jìn)程或內(nèi)核,dst_group 應(yīng)當(dāng)設(shè)置為 0。
在內(nèi)核中,模塊調(diào)用函數(shù) netlink_unicast 來發(fā)送單播消息:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
參數(shù)sk為函數(shù)netlink_kernel_create()返回的socket,參數(shù)skb存放消息,它的data字段指向要發(fā)送的netlink消息結(jié)構(gòu),而skb的控制塊保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便設(shè)置該控制塊,參數(shù)pid為接收消息進(jìn)程的pid,參數(shù)nonblock表示該函數(shù)是否為非阻塞,如果為1,該函數(shù)將在沒有接收緩存可利用時(shí)立即返回,而如果為0,該函數(shù)在沒有接收緩存可利用時(shí)睡眠。
內(nèi)核模塊或子系統(tǒng)也可以使用函數(shù)netlink_broadcast來發(fā)送廣播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);
前面的三個(gè)參數(shù)與netlink_unicast相同,參數(shù)group為接收消息的多播組,該參數(shù)的每一個(gè)代表一個(gè)多播組,因此如果發(fā)送給多個(gè)多播組,就把該參數(shù)設(shè)置為多個(gè)多播組組ID的位或。參數(shù)allocation為內(nèi)核內(nèi)存分配類型,一般地為GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。
在內(nèi)核中使用函數(shù)sock_release來釋放函數(shù)netlink_kernel_create()創(chuàng)建的netlink socket:
void sock_release(struct socket * sock);
注意函數(shù)netlink_kernel_create()返回的類型為struct sock,因此函數(shù)sock_release應(yīng)該這種調(diào)用:
sock_release(sk->sk_socket);
sk為函數(shù)netlink_kernel_create()的返回值。
sk為函數(shù)netlink_kernel_create()的返回值。在源代碼包中給出了一個(gè)使用 netlink 的示例,它包括一個(gè)內(nèi)核模塊 netlink-exam-kern.c 和兩個(gè)應(yīng)用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。內(nèi)核模塊必須先插入到內(nèi)核,然后在一個(gè)終端上運(yùn)行用戶態(tài)接收程序,在另一個(gè)終端上運(yùn)行用戶態(tài)發(fā)送程序,發(fā)送程序讀取參數(shù)指定的文本文件并把它作為 netlink 消息的內(nèi)容發(fā)送給內(nèi)核模塊,內(nèi)核模塊接受該消息保存到內(nèi)核緩存中,它也通過proc接口出口到 procfs,因此用戶也能夠通過 /proc/netlink_exam_buffer 看到全部的內(nèi)容,同時(shí)內(nèi)核也把該消息發(fā)送給用戶態(tài)接收程序,用戶態(tài)接收程序?qū)呀邮盏降膬?nèi)容輸出到屏幕上。
?
評(píng)論
查看更多