- static u32 netlink_group_mask(u32 group)
- {
- return group ? 1 << (group - 1) : 0;
- }
也就是說,在用戶空間的代碼里,如果我們要加入到多播組1,需要設(shè)置nl_groups設(shè)置為1;多播組2的掩碼為2;多播組3的掩碼為4,依次類推。為0表示我們不希望加入任何多播組。理解這一點很重要。所以我們可以在用戶空間也定義一個類似于netlink_group_mask()的功能函數(shù),完成從多播組號到多播組掩碼的轉(zhuǎn)換。最終用戶空間的代碼如下:
- #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;
- // 創(chuàng)建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; // 表示我們要從內(nèi)核接收多播消息。注意:該字段為0有雙重意義,另一個意義是表示我們發(fā)送的數(shù)據(jù)的目的地址是內(nèi)核。
- src_addr.nl_groups = netlink_group_mask(atoi(argv[1])); // 多播組的掩碼,組號來自我們執(zhí)行程序時輸入的第一個參數(shù)
- // 因為我們要加入到一個多播組,所以必須調(diào)用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;
- // 從內(nèi)核接收消息
- printf("waitinf for...\n");
- recvmsg(sock_fd, &msg, 0);
- printf("Received message: %s \n", NLMSG_DATA(nlh));
-
- close(sock_fd);
- return 0;
- }
可以看到,用戶空間的程序基本沒什么變化,唯一需要格外注意的就是Netlink地址結(jié)構(gòu)體中的nl_groups的設(shè)置。由于對它的解釋很少,加之沒有有效的文檔,所以我也是一邊看源碼,一邊在網(wǎng)上搜集資料。有分析不當(dāng)之處,還請大家?guī)臀抑赋觥?/font>
內(nèi)核空間我們添加了內(nèi)核線程和內(nèi)核線程同步方法completion的使用。內(nèi)核空間修改后的代碼如下:- #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; //內(nèi)核線程對象
- //向用戶空間發(fā)送消息的接口
- 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()來設(shè)置netlink消息頭部
- nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
- // 設(shè)置Netlink的控制塊里的相關(guān)信息
- NETLINK_CB(skb).pid = 0; // 消息發(fā)送者的id標(biāo)識,如果是內(nèi)核發(fā)的則置0
- NETLINK_CB(skb).dst_group = 5; //多播組號為5,但置成0好像也可以。
- message[slen] = '\0';
- memcpy(NLMSG_DATA(nlh), message, slen+1);
- //通過netlink_unicast()將消息發(fā)送用戶空間由dstPID所指定了進程號的進程
- //netlink_unicast(nl_sk,skb,dstPID,0);
- netlink_broadcast(nl_sk, skb, 0,5, GFP_KERNEL); //發(fā)送多播消息到多播組5,這里我故意沒有用1之類的“常見”值,目的就是為了證明我們上面提到的多播組號和多播組號掩碼之間的對應(yīng)關(guān)系
- printk("send OK!\n");
- return;
- }
- //每隔1秒鐘發(fā)送一條“I am from kernel!”消息,共發(fā)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);
關(guān)于內(nèi)核中netlink_kernel_create(int unit, unsigned int groups,…)函數(shù)里的第二個參數(shù)指的是我們內(nèi)核進程最多能處理的多播組的個數(shù),如果該值小于32,則默認(rèn)按32處理,所以在調(diào)用netlink_kernel_create()函數(shù)時可以不用糾結(jié)第二個參數(shù),一般將其置為0就可以了。
在skbuff{}結(jié)構(gòu)體中,有個成員叫做"控制塊",源碼對它的解釋如下:
- 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];
- … …
- }
當(dāng)內(nèi)核態(tài)的Netlink發(fā)送數(shù)據(jù)到用戶空間時一般需要填充skbuff的控制塊,填充的方式是通過強制類型轉(zhuǎn)換,將其轉(zhuǎn)換成struct netlink_skb_parms{}之后進行填充賦值的:- 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協(xié)議簇里提到的skbuff的cb控制塊里保存的是屬于Netlink的私有信息。怎么講,就是Netlink會用該控制塊里的信息來完成它所提供的一些功能,只是完成Netlink功能所必需的一些私有數(shù)據(jù)。打個比方,以開車為例,開車的時候我們要做的就是打火、控制方向盤、適當(dāng)?shù)乜刂朴烷T和剎車,車就開動了,這就是汽車提供給我們的“功能”。汽車的發(fā)動機,輪胎,傳動軸,以及所用到的螺絲螺栓等都屬于它的“私有”數(shù)據(jù)cb。汽車要運行起來這些東西是不可或缺的,但它們之間的協(xié)作和交互對用戶來說又是透明的。就好比我們Netlink的私有控制結(jié)構(gòu)struct netlink_skb_parms{}一樣。
目前我們的例子中,將NETLINK_CB(skb).dst_group設(shè)置為相應(yīng)的多播組號和0效果都是一樣,用戶空間都可以收到該多播消息,原因還不是很清楚,還請Netlink的大蝦們幫我點撥點撥。
編譯后重新運行,最后的測試結(jié)果如下:
注意,這里一定要先執(zhí)行insmod加載內(nèi)核模塊,然后再運行用戶空間的程序。如果沒有加載mynlkern.ko而直接執(zhí)行./test 5在bind()系統(tǒng)調(diào)用時會報如下的錯誤:
bind failed: No such file or directory
因為網(wǎng)上有寫文章在講老版本Netlink的多播時用法時先執(zhí)行了用戶空間的程序,然后才加載內(nèi)核模塊,現(xiàn)在(2.6.21)已經(jīng)行不通了,這一點請大家注意。
小結(jié):通過這三篇博文我們對Netlink有了初步的認(rèn)識,并且也可以開發(fā)基于Netlink的基本應(yīng)用程序。但這只是冰山一角,要想寫出高質(zhì)量、高效率的軟件模塊還有些差距,特別是對Netlink本質(zhì)的理解還需要提高一個層次,當(dāng)然這其中牽扯到內(nèi)核編程的很多基本功,如臨界資源的互斥、線程安全性保護、用Netlink傳遞大數(shù)據(jù)時的處理等等都是開發(fā)人員需要考慮的問題。 完。