Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3171485
  • 博文数量: 685
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5303
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-19 14:17
个人简介

文章分类

全部博文(685)

文章存档

2015年(116)

2014年(569)

分类: LINUX

2014-09-05 11:02:53

原文地址:http://blog.csdn.net/sudochen/article/details/8949227

今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的。我们依旧是在2.6.21的内核环境下进行开发。      在文件里包含了Netlink协议簇已经定义好的一些预定义协议:

 

#define NETLINK_TEST    20 /* 用户添加的自定义协议 */
 

如果我们在Netlink协议簇里开发一个新的协议,只要在该文件中定义协议号即可,例如我们定义一种基于Netlink协议簇的、协议号是20的自定义协议,如上所示。同时记得,将内核头文件目录中的netlink.h也做对应的修改,在我的系统中它的路径是:/usr/src/linux-2.6.21/include/linux/netlink.h

接下来我们在用户空间以及内核空间模块的开发过程中就可以使用这种协议了,一共分为三个阶段。

1,第一阶段:

我们首先实现的功能是用户->内核的单向数据通信,即用户空间发送一个消息给内核,然后内核将其打印输出,就这么简单。用户空间的示例代码如下【mynlusr.c】

  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11.   
  12. #define MAX_PAYLOAD 1024  /*the max payload of the msg*/  
  13.   
  14. int main(int argc, char **argv)  
  15. {  
  16.     struct sockaddr_nl dest_addr;  
  17.     struct nlmsghdr *nlh = NULL;  
  18.     struct iovec iov;  
  19.     int sock_fd = -1;  
  20.     struct msghdr msg;  
  21.     int ret;  
  22.    
  23.     if((sock_fd=socket(PF_NETLNK,SOCK_RAW,NETLINK_TEST)) < 0)  
  24.     {  
  25.         perror("can not create netlink socket");  
  26.         exit(1);  
  27.     }  
  28.   
  29.     memset(&dest_addr,0,sizeof(dest_addr));  
  30.     dest_addr.nl_family = AF_NETLINK;  
  31.     dest_addr.nl_pid = 0; /*the msg si sent to the kernel*/  
  32.     dest_addr.nl_groups = 0; /*do not care about it in this example*/  
  33.   
  34.     /* bind the socket and address of the netlink */  
  35.     if((ret =bind(sock_fd,(struct sockaddr*)&dest_addr,sizeof(dest_addr))) < 0)  
  36.     {  
  37.         perror("can not bind sockfd with sockaddr_nl\n");  
  38.         exit(1);  
  39.     }     
  40.   
  41.     if(NULL == (nlh=(struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD))))  
  42.     {  
  43.         perror("alloc mem failed\n");  
  44.         exit(1);  
  45.     }  
  46.   
  47.     memset(nlh,0,MAX_PAYLOAD);  
  48.     /* full the netlink header with the specifid info */  
  49.     nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);  
  50.     nlh->nlmsg_pid = 0;  
  51.     nlh->nlmsg_type = NLMSG_NOOP;/* indentify that the netlink info is a empty info */  
  52.     nlh->nlmsg_flags = 0;  
  53.   
  54.     /* set the netlink infomation */  
  55.     strcpy(NLMSG_DATA(nlh),argv[1]);  
  56.   
  57.     memset(&iov,0,sizeof(iov));  
  58.     iov.iov_base = (void*)nlh;  
  59.     iov.iov_len  = nlh->nlmsg_len;  
  60.     memset(&msg,0,sizeof(msg));  
  61.     msg.msg_iov = &iov;  
  62.     msg.msg_iovlen = 1;  
  63.   
  64.     sendmsg(sock_fd,&msg,0);/* send the msg with NetLink Socket */  
  65.   
  66.     /* close the netlink socket_fd */  
  67.     close(sock_fd);  
  68.     free(nlh);  
  69.     return 0;  
  70. }  

 

上面的代码逻辑已经非常清晰了,都是socket编程的API,唯一不同的是我们这次编程是针对Netlink协议簇的。这里我们提前引入了BSD层的消息结构体struct msghdr{},定义在文件里,以及其数据块struct iovec{}定义在头文件里。这里就不展开了,大家先记住这个用法就行。以后有时间再深入到socket的骨子里去转悠一番。     
另外,需要格外注意的就是Netlink的地址结构体和其消息头结构中pid字段为0的情况,很容易让人产生混淆,再总结一下:

 

0

netlink地址结构体.nl_pid

1、内核发出的多播报文

2、消息的接收方是内核,即从用户空间发往内核的消息

netlink消息头体. nlmsg_pid

来自内核主动发出的消息


这个例子仅是从用户空间到内核空间的单向数据通信,所以Netlink地址结构体中我们设置了dest_addr.nl_pid = 0,说明我们的报文的目的地是内核空间;在填充Netlink消息头部时,我们做了nlh->nlmsg_pid = 0这样的设置。

需要注意几个宏的使用:

NLMSG_SPACE(MAX_PAYLOAD),该宏用于返回不小于MAX_PAYLOAD且4字节对齐的最小长度值,一般用于向内存系统申请空间是指定所申请的内存字节数,和NLMSG_LENGTH(len)所不同的是,前者所申请的空间里不包含Netlink消息头部所占的字节数,后者是消息负载和消息头加起来的总长度。

NLMSG_DATA(nlh),该宏用于返回Netlink消息中数据部分的首地址,在写入和读取消息数据部分时会用到它。

它们之间的关系如下:

内核空间代码如下,mynlkernel.c

  1. #inlcude   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10.   
  11. MODULE_LICENSE("GPL");  
  12.   
  13. struct sock *nl_sk = NULL;  
  14.   
  15. static void nl_data_ready(struct sock *sk, int len)  
  16. {  
  17.     struct sk_buff *skb;  
  18.     struct nlmsghdr *nlh = NULL;  
  19.   
  20.     while((skb=skb_dequeue(&sk->sk_receive_queue)) != NULL)  
  21.     {  
  22.         nlh=(struct nlmsghdr*)skb->data;  
  23.         printk("%s: receive netlink msg payload: %s\n",__func__,(char*)NLMSG_DATA(nlh));  
  24.         kfree_skb(skb);  
  25.     }  
  26.     printk("received finished!\n");  
  27. }  
  28.   
  29.   
  30. static __exit void testnl_exit(void)  
  31. {  
  32.     printk("test netlink exit\n");  
  33.     sock_release(nl_sk->sk_socket);  
  34. }  
  35.   
  36.   
  37. static __init int testnl_init(void)  
  38. {  
  39.     printk("test netlink init\n");  
  40.     nl_sk = netlink_kernel_create(NETLINK_TEST,0,nl_data_ready,THIS_MODULE);  
  41.   
  42.     return 0;  
  43. }  
  44.   
  45. module_init(testnl_init);  
  46. module_exit(testnl_exit);  


在内核模块的初始化函数里我们用

nl_sk = netlink_kernel_create(NETLINK_TEST,0,nl_data_ready,THIS_MODULE);

创建了一个内核态的socket,第一个参数我们扩展的协议号;第二个参数为多播组号,目前我们用不上,将其置为0;第三个参数是个回调函数,即当内核的Netlink socket套接字收到数据时的处理函数;第四个参数就不多说了。

在回调函数nl_data_ready()中,我们不断的从socket的接收队列去取数据,一旦拿到数据就将其打印输出。在协议栈的INET层,用于存储数据的是大名鼎鼎的sk_buff结构,所以我们通过nlh = (struct nlmsghdr *)skb->data;可以拿到netlink的消息体,然后通过NLMSG_DATA(nlh)定位到netlink的消息负载。

将上述代码编译后测试结果如下:

 

 

2,第二阶段:

我们将上面的代码稍加改造就可以实现用户<->内核双向数据通信。

首先是改造用户空间的代码:

  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11.   
  12. #define MAX_PAYLOAD 1024  /*the max payload of the msg*/  
  13.   
  14. int main(int argc, char **argv)  
  15. {  
  16.     struct sockaddr_nl dest_addr;  
  17.     struct nlmsghdr *nlh = NULL;  
  18.     struct iovec iov;  
  19.     int sock_fd = -1;  
  20.     struct msghdr msg;  
  21.     int ret;  
  22.    
  23.     if((sock_fd=socket(PF_NETLNK,SOCK_RAW,NETLINK_TEST)) < 0)  
  24.     {  
  25.         perror("can not create netlink socket");  
  26.         exit(1);  
  27.     }  
  28.   
  29.     memset(&dest_addr,0,sizeof(dest_addr));  
  30.     dest_addr.nl_family = AF_NETLINK;  
  31.     dest_addr.nl_pid = 0; /*the msg si sent to the kernel*/  
  32.     dest_addr.nl_groups = 0; /*do not care about it in this example*/  
  33.   
  34.     /* bind the socket and address of the netlink */  
  35.     if((ret =bind(sock_fd,(struct sockaddr*)&dest_addr,sizeof(dest_addr))) < 0)  
  36.     {  
  37.         perror("can not bind sockfd with sockaddr_nl\n");  
  38.         exit(1);  
  39.     }     
  40.   
  41.     if(NULL == (nlh=(struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD))))  
  42.     {  
  43.         perror("alloc mem failed\n");  
  44.         exit(1);  
  45.     }  
  46.   
  47.     memset(nlh,0,MAX_PAYLOAD);  
  48.     /* full the netlink header with the specifid info */  
  49.     nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);  
  50.     //nlh->nlmsg_pid = 0;  
  51.     //////////////////////////////////////////////////////////////////////////////////  
  52.     nlh->nlmsg_pid = getpid(); //我们希望得到内核的回应,所以我们得告诉内核我们得ID号,  
  53.     //////////////////////////////////////////////////////////////////////////////////  
  54.     nlh->nlmsg_type = NLMSG_NOOP;/* indentify that the netlink info is a empty info */  
  55.     nlh->nlmsg_flags = 0;  
  56.   
  57.     /* set the netlink infomation */  
  58.     strcpy(NLMSG_DATA(nlh),argv[1]);  
  59.   
  60.     memset(&iov,0,sizeof(iov));  
  61.     iov.iov_base = (void*)nlh;  
  62.     iov.iov_len  = nlh->nlmsg_len;  
  63.     memset(&msg,0,sizeof(msg));  
  64.     msg.msg_iov = &iov;  
  65.     msg.msg_iovlen = 1;  
  66.   
  67.     sendmsg(sock_fd,&msg,0);/* send the msg with NetLink Socket */  
  68.   
  69.     ///////////////////////////////////////////////////////////////////////////////////  
  70.     //receive the kernel infomation  
  71.     printf("waiting msg from kernel\n");  
  72.     memset((char*)MLMSG_DATA(nlh),0,1024);  
  73.     recvmsg(sock_fd,&msg,0);  
  74.     printf("got response : %s\n",NLMSG_DATA(nlh));  
  75.     ////////////////////////////////////////////////////////////////////////////////////  
  76.       
  77.     /* close the netlink socket_fd */  
  78.     close(sock_fd);  
  79.     free(nlh);  
  80.     return 0;  
  81. }  


内核修改代码:

  1. #inlcude   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10.   
  11. MODULE_LICENSE("GPL");  
  12.   
  13. struct sock *nl_sk = NULL;  
  14.   
  15. //////////////////////////////////////////////////////////////////////////////////////////////  
  16. //sendnlmsg function  
  17. void sendnlmsg(char* msg,int dst_pid)  
  18. {  
  19.     struct sk_buff *skb;  
  20.     struct nlmsghdr *nlh;  
  21.     int len = NLMSG_SPACE(MAX_MSGSIZE);  
  22.     int slen = 0;  
  23.   
  24.     if(!msg || !nl_sk)  
  25.     {  
  26.         return;  
  27.     }  
  28.       
  29.     skb = alloc_skb(len,GFP_KERNEL);  
  30.     if(!skb)  
  31.     {  
  32.         printk("alloc_skb error/n");  
  33.         return;  
  34.     }  
  35.   
  36.     slen = strlen(msg) + 1;  
  37.     /* set the netlink msg header use nlmsg_put() */  
  38.     nlh = nlmsg_put(skb,0,0,0,MAX_MSGSIZE,0);  
  39.   
  40.     /* 设置netlink控制块 */  
  41.     NETLINK_CB(skb).pid = 0; //消息发送者的id表示,如果是内核则为0  
  42.     NETLINK_CB(skb).dst_group = 0;//如果目的组为内核或者是某一进程,置0  
  43.       
  44.     msg[slen] = '\0';  
  45.     memcpy(MLMSG_DATA(nlh),msg,slen+1);  
  46.     //通过netlink_unicast()将消息发送用户控件由dst_pid所指定的进程  
  47.     netlink_unicase(nl_sk,skb,dst_pid,0);  
  48.     printk("send OK\n");  
  49.     return ;  
  50. }  
  51. /////////////////////////////////////////////////////////////////////////////////////////////  
  52. static void nl_data_ready(struct sock *sk, int len)  
  53. {  
  54.     struct sk_buff *skb;  
  55.     struct nlmsghdr *nlh = NULL;  
  56.   
  57.     while((skb=skb_dequeue(&sk->sk_receive_queue)) != NULL)  
  58.     {  
  59.         nlh=(struct nlmsghdr*)skb->data;  
  60.         printk("%s: receive netlink msg payload: %s\n",__func__,(char*)NLMSG_DATA(nlh));  
  61.         kfree_skb(skb);  
  62.         ////////////////////////////////////////////////////////////////////////////////  
  63.         sendnlmsg("I see you\n",nlh->nlmsg_pid);  
  64.         //发送者的进程ID,我们已经存储在netlink的消息头部nlmsg_pid里面,这里可以直接拿来用  
  65.         ////////////////////////////////////////////////////////////////////////////////  
  66.     }  
  67.     printk("received finished!\n");  
  68. }  
  69.   
  70.   
  71. static __exit void testnl_exit(void)  
  72. {  
  73.     printk("test netlink exit\n");  
  74.     sock_release(nl_sk->sk_socket);  
  75. }  
  76.   
  77.   
  78. static __init int testnl_init(void)  
  79. {  
  80.     printk("test netlink init\n");  
  81.     nl_sk = netlink_kernel_create(NETLINK_TEST,0,nl_data_ready,THIS_MODULE);  
  82.   
  83.     return 0;  
  84. }  
  85.   
  86. module_init(testnl_init);  
  87. module_exit(testnl_exit);  

 

 

3,第三阶段:

 

前面我们提到过,如果用户进程希望加入某个多播组时才需要调用bind()函数。前面的示例中我们没有这个需求,可还是调了bind(),心头有些不爽。在前几篇博文里有关于socket编程时几个常见API的详细解释和说明,不明白的童鞋可以回头去复习一下。

因为Netlink是面向无连接的数据报的套接字,所以我们还可以用sendto()和recvfrom()来实现数据的收发,这次我们不再调用bind()。将Stage 2的例子稍加改造一下,

用户空间的修改如下:

 

  1. "font-size:12px;">#include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11.   
  12. #define MAX_PAYLOAD 1024  /*the max payload of the msg*/  
  13.   
  14. int main(int argc, char **argv)  
  15. {  
  16.     struct sockaddr_nl dest_addr;  
  17.     struct nlmsghdr *nlh = NULL;  
  18.     struct iovec iov;  
  19.     int sock_fd = -1;  
  20.     struct msghdr msg;  
  21.     int ret;  
  22.    
  23.     if((sock_fd=socket(PF_NETLNK,SOCK_RAW,NETLINK_TEST)) < 0)  
  24.     {  
  25.         perror("can not create netlink socket");  
  26.         exit(1);  
  27.     }  
  28.   
  29.     memset(&dest_addr,0,sizeof(dest_addr));  
  30.     dest_addr.nl_family = AF_NETLINK;  
  31.     dest_addr.nl_pid = 0; /*the msg si sent to the kernel*/  
  32.     dest_addr.nl_groups = 0; /*do not care about it in this example*/  
  33.   
  34.     /////////////////////////////////////////////////////////////////////////////  
  35.     /* 
  36.     /* bind the socket and address of the netlink */  
  37.     if((ret =bind(sock_fd,(struct sockaddr*)&dest_addr,sizeof(dest_addr))) < 0)  
  38.     {  
  39.         perror("can not bind sockfd with sockaddr_nl\n");  
  40.         exit(1);  
  41.     }*/  
  42.     //不再调用bind()函数  
  43.     /////////////////////////////////////////////////////////////////////////////     
  44.   
  45.     if(NULL == (nlh=(struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD))))  
  46.     {  
  47.         perror("alloc mem failed\n");  
  48.         exit(1);  
  49.     }  
  50.   
  51.     memset(nlh,0,MAX_PAYLOAD);  
  52.     /* full the netlink header with the specifid info */  
  53.     nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);  
  54.     nlh->nlmsg_pid = get_pid();//我们希望得到回应,所以告诉内核我们得ID  
  55.     nlh->nlmsg_type = NLMSG_NOOP;/* indentify that the netlink info is a empty info */  
  56.     nlh->nlmsg_flags = 0;  
  57.   
  58.     /* set the netlink infomation */  
  59.     strcpy(NLMSG_DATA(nlh),argv[1]);  
  60.   
  61.     /////////////////////////////////////////////////////////////////////////////  
  62.     /* 
  63.     memset(&iov,0,sizeof(iov)); 
  64.     iov.iov_base = (void*)nlh; 
  65.     iov.iov_len  = nlh->nlmsg_len; 
  66.     memset(&msg,0,sizeof(msg)); 
  67.     msg.msg_iov = &iov; 
  68.     msg.msg_iovlen = 1; 
  69.     */  
  70.     //这个也用不上了  
  71.     /////////////////////////////////////////////////////////////////////////////  
  72.   
  73.     //sendmsg(sock_fd,&msg,0);/* send the msg with NetLink Socket */  
  74.     sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr));  
  75.       
  76.     printf("waiting message form kernel\n");  
  77.     memset((char*)NLMSG_DATA(nlh),0,1024);  
  78.     //recvmsg(nlh,0,MAX_PAYLOAD);  
  79.     recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),NULL);  
  80.     printf("got response : %s\n",NLMSG_DATA(nlh));  
  81.   
  82.     /* close the netlink socket_fd */  
  83.     close(sock_fd);  
  84.     free(nlh);  
  85.     return 0;  
  86. }  


内核代码完全不用修改;内核空间的代码完全不用修改,我们仍然用netlink_unicast()从内核空间发送消息到用户空间。

重新编译后,测试结果如下:


和第二部分中代码运行效果完全一样。也就是说,在开发Netlink程序过程中,如果没牵扯到多播机制,那么用户空间的socket代码其实是不用执行bind()系统调用的,但此时就需要用sendto()和recvfrom()完成数据的发送和接收的任务;如果执行了bind()系统调用,当然也可以继续用sendto()和recvfrom(),但给它们传递的参数就有所区别。这时候一般使用sendmsg()和recvmsg()来完成数据的发送和接收。大家根据自己的实际情况灵活选择。

 

本文为转载文章,对原文有所删改,请以原文为参考

原文:http://blog.chinaunix.net/uid-23069658-id-3405954.html



阅读(1137) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~