使用者空間和核心空間通訊之【Netlink 下】
關於Netlink多播機制的用法
在上一篇博文中我們所遇到的情況都是使用者空間作為訊息程序的發起者,Netlink還支援核心作為訊息的傳送方的情況。這一般用於核心主動向使用者空間報告一些核心狀態,例如我們在使用者空間看到的USB的熱插拔事件的通告就是這樣的應用。
先說一下我們的目標,核心執行緒每個一秒鐘往一個多播組裡傳送一條訊息,然後使用者空間所以加入了該組的程序都會收到這樣的訊息,並將訊息內容打印出來。
Netlink地址結構體中的nl_groups是32位,也就是說每種Netlink協議最多支援32個多播組。如何理解這裡所說的每種Netlink
點選(此處)摺疊或開啟
- #define NETLINK_ROUTE 0 /* Routing/device hook */
- #define NETLINK_UNUSED 1 /* Unused number */
- #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
- /* leave room for NETLINK_DM (DM Events) */
- #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
- #define NETLINK_ECRYPTFS 19
- #define NETLINK_TEST 20 /* 使用者新增的自定義協議 */
在我們自己新增的NETLINK_TEST協議裡,同樣地,最多允許我們設定32個多播組,每個多播組用1個位元表示,所以不同的多播組不可能出現重複。你可以根據自己的實際需求,決定哪個多播組是用來做什麼的。使用者空間的程序如果對某個多播組感興趣,那麼它就加入到該組中,當核心空間的程序往該組傳送多播訊息時,所有已經加入到該多播組的使用者程序都會收到該訊息。
再回到我們Netlink地址結構體裡的nl_groups成員,它是多播組的地址掩碼,注意是掩碼不是多播組的組號。如何根據多播組號取得多播組號的掩碼呢?在af_netlink.c中有個函式:點選(此處)摺疊或開啟
- static u32 netlink_group_mask(u32 group)
- {
- return group ? 1 << (group - 1) : 0;
- }
也就是說,在使用者空間的程式碼裡,如果我們要加入到多播組1,需要設定nl_groups設定為1;多播組2的掩碼為2;多播組3的掩碼為4,依次類推。為0表示我們不希望加入任何多播組。理解這一點很重要。所以我們可以在使用者空間也定義一個類似於netlink_group_mask()的功能函式,完成從多播組號到多播組掩碼的轉換。終端使用者空間的程式碼如下:
點選(此處)摺疊或開啟
- #include <sys/stat.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <string.h>
- #include <asm/types.h>
- #include <linux/netlink.h>
- #include <linux/socket.h>
- #include <errno.h>
- #define MAX_PAYLOAD 1024 // Netlink訊息的最大載荷的長度
- unsigned int netlink_group_mask(unsigned int group)
- {
- return group ? 1 << (group - 1) : 0;
- }
- int main(int argc, char* argv[])
- {
- struct sockaddr_nl src_addr;
- struct nlmsghdr *nlh = NULL;
- struct iovec iov;
- struct msghdr msg;
- int sock_fd, retval;
- // 建立Socket
- sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
- if(sock_fd == -1){
- printf("error getting socket: %s", strerror(errno));
- return -1;
- }
- memset(&src_addr, 0, sizeof(src_addr));
- src_addr.nl_family = PF_NETLINK;
- src_addr.nl_pid = 0; // 表示我們要從核心接收多播訊息。注意:該欄位為0有雙重意義,另一個意義是表示我們傳送的資料的目的地址是核心。
- src_addr.nl_groups = netlink_group_mask(atoi(argv[1])); // 多播組的掩碼,組號來自我們執行程式時輸入的第一個引數
- // 因為我們要加入到一個多播組,所以必須呼叫bind()。
- retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
- if(retval < 0){
- printf("bind failed: %s", strerror(errno));
- close(sock_fd);
- return -1;
- }
- // 為接收Netlink訊息申請儲存空間
- nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
- if(!nlh){
- printf("malloc nlmsghdr error!\n");
- close(sock_fd);
- return -1;
- }
- memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
- iov.iov_base = (void *)nlh;
- iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
- memset(&msg, 0, sizeof(msg));
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- // 從核心接收訊息
- printf("waitinf for...\n");
- recvmsg(sock_fd, &msg, 0);
- printf("Received message: %s \n", NLMSG_DATA(nlh));
- close(sock_fd);
- return 0;
- }
可以看到,使用者空間的程式基本沒什麼變化,唯一需要格外注意的就是Netlink地址結構體中的nl_groups的設定。由於對它的解釋很少,加之沒有有效的文件,所以我也是一邊看原始碼,一邊在網上搜集資料。有分析不當之處,還請大家幫我指出。
核心空間我們添加了核心執行緒和核心執行緒同步方法completion的使用。核心空間修改後的程式碼如下:點選(此處)摺疊或開啟
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/skbuff.h>
- #include <linux/init.h>
- #include <linux/ip.h>
- #include <linux/types.h>
- #include <linux/sched.h>
- #include <net/sock.h>
- #include <net/netlink.h>
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Koorey King");
- struct sock *nl_sk = NULL;
- static struct task_struct *mythread = NULL; //核心執行緒物件
- //向用戶空間傳送訊息的介面
- void sendnlmsg(char *message/*,int dstPID*/)
- {
- struct sk_buff *skb;
- struct nlmsghdr *nlh;
- int len = NLMSG_SPACE(MAX_MSGSIZE);
- int slen = 0;
- if(!message || !nl_sk){
- return;
- }
- // 為新的 sk_buffer申請空間
- skb = alloc_skb(len, GFP_KERNEL);
- if(!skb){
- printk(KERN_ERR "my_net_link: alloc_skb Error./n");
- return;
- }
- slen = strlen(message)+1;
- //用nlmsg_put()來設定netlink訊息頭部
- nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
- // 設定Netlink的控制塊裡的相關資訊
- NETLINK_CB(skb).pid = 0; // 訊息傳送者的id標識,如果是核心發的則置0
- NETLINK_CB(skb).dst_group = 5; //多播組號為5,但置成0好像也可以。
- message[slen] = '\0';
- memcpy(NLMSG_DATA(nlh), message, slen+1);
- //通過netlink_unicast()將訊息傳送使用者空間由dstPID所指定了程序號的程序
- //netlink_unicast(nl_sk,skb,dstPID,0);
- netlink_broadcast(nl_sk, skb, 0,5, GFP_KERNEL); //傳送多播訊息到多播組5,這裡我故意沒有用1之類的“常見”值,目的就是為了證明我們上面提到的多播組號和多播組號掩碼之間的對應關係
- printk("send OK!\n");
- return;
- }
- //每隔1秒鐘傳送一條“I am from kernel!”訊息,共發10個報文
- static int sending_thread(void *data)
- {
- int i = 10;
- struct completion cmpl;
- while(i--){
- init_completion(&cmpl);
- wait_for_completion_timeout(&cmpl, 1 * HZ);
- sendnlmsg("I am from kernel!");
- }
- printk("sending thread exited!");
- return 0;
- }
- static int __init myinit_module()
- {
- printk("my netlink in\n");
- nl_sk = netlink_kernel_create(NETLINK_TEST,0,NULL,THIS_MODULE);
- if(!nl_sk){
- printk(KERN_ERR "my_net_link: create netlink socket error.\n");
- return 1;
- }
- printk("my netlink: create netlink socket ok.\n");
- mythread = kthread_run(sending_thread,NULL,"thread_sender");
- return 0;
- }
- static void __exit mycleanup_module()
- {
- if(nl_sk != NULL){
- sock_release(nl_sk->sk_socket);
- }
- printk("my netlink out!\n");
- }
- module_init(myinit_module);
- module_exit(mycleanup_module);
關於核心中netlink_kernel_create(int unit, unsigned int groups,…)函式裡的第二個引數指的是我們核心程序最多能處理的多播組的個數,如果該值小於32,則預設按32處理,所以在呼叫netlink_kernel_create()函式時可以不用糾結第二個引數,一般將其置為0就可以了。
在skbuff{}結構體中,有個成員叫做"控制塊",原始碼對它的解釋如下:
點選(此處)摺疊或開啟
- struct sk_buff {
- /* These two members must be first. */
- struct sk_buff *next;
- struct sk_buff *prev;
- … …
- /*
- * This is the control buffer. It is free to use for every
- * layer. Please put your private variables there. If you
- * want to keep them across layers you have to do a skb_clone()
- * first. This is owned by whoever has the skb queued ATM.
- */
- char cb[48];
- … …
- }
點選(此處)摺疊或開啟
- struct netlink_skb_parms
- {
- struct ucred creds; /* Skb credentials */
- __u32 pid;
- __u32 dst_group;
- kernel_cap_t eff_cap;
- __u32 loginuid; /* Login (audit) uid */
- __u32 sid; /* SELinux security id */
- };
填充時的模板程式碼如下:
點選(此處)摺疊或開啟
- NETLINK_CB(skb).pid=xx;
- NETLINK_CB(skb).dst_group=xx;
這裡要注意的是在Netlink協議簇裡提到的skbuff的cb控制塊裡儲存的是屬於Netlink的私有資訊。怎麼講,就是Netlink會用該控制塊裡的資訊來完成它所提供的一些功能,只是完成Netlink功能所必需的一些私有資料。打個比方,以開車為例,開車的時候我們要做的就是打火、控制方向盤、適當地控制油門和剎車,車就開動了,這就是汽車提供給我們的“功能”。汽車的發動機,輪胎,傳動軸,以及所用到的螺絲螺栓等都屬於它的“私有”資料cb。汽車要執行起來這些東西是不可或缺的,但它們之間的協作和互動對使用者來說又是透明的。就好比我們Netlink的私有控制結構struct netlink_skb_parms{}一樣。
目前我們的例子中,將NETLINK_CB(skb).dst_group設定為相應的多播組號和0效果都是一樣,使用者空間都可以收到該多播訊息,原因還不是很清楚,還請Netlink的大蝦們幫我點撥點撥。
編譯後重新執行,最後的測試結果如下:注意,這裡一定要先執行insmod載入核心模組,然後再執行使用者空間的程式。如果沒有載入mynlkern.ko而直接執行./test 5在bind()系統呼叫時會報如下的錯誤:
bind failed: No such file or directory
因為網上有寫文章在講老版本Netlink的多播時用法時先執行了使用者空間的程式,然後才載入核心模組,現在(2.6.21)已經行不通了,這一點請大家注意。
小結:通過這三篇博文我們對Netlink有了初步的認識,並且也可以開發基於Netlink的基本應用程式。但這只是冰山一角,要想寫出高質量、高效率的軟體模組還有些差距,特別是對Netlink本質的理解還需要提高一個層次,當然這其中牽扯到核心程式設計的很多基本功,如臨界資源的互斥、執行緒安全性保護、用Netlink傳遞大資料時的處理等等都是開發人員需要考慮的問題。完。
<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script> 閱讀(4948) | 評論(4) | 轉發(27) | 給主人留下些什麼吧!~~xitry2016-01-24 15:36:45
膜拜,這篇文章幫了我大忙了
回覆 | 舉報2015-10-17 11:25:09
看好貼不會不禮貌,大哥你的部落格寫的真好,通俗易懂!!!!多謝。。。
回覆 | 舉報2014-02-25 22:10:56
真的是,好文章呀。看來向koorey兄看齊的話,還有很長很長的路要走
回覆 | 舉報2012-11-20 14:52:53
mark
回覆 | 舉報 評論熱議