用户空间和内核空间通讯之【Netlink 中】
原文地址:用户空间和内核空间通讯之【Netlink 中】 作者:wjlkoorey258
今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的。我们依旧是在2.6.21的内核环境下进行开发。
在</usr/include/linux/netlink.h>文件里包含了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协议簇里开发一个新的协议,只要在该文件中定义协议号即可,例如我们定义一种基于Netlink协议簇的、协议号是20的自定义协议,如上所示。同时记得,将内核头文件目录中的netlink.h也做对应的修改,在我的系统中它的路径是:/usr/src/linux-2.6.21/include/linux/netlink.h
接下来我们在用户空间以及内核空间模块的开发过程中就可以使用这种协议了,一共分为三个阶段。
Stage 1:
我们首先实现的功能是用户->内核的单向数据通信,即用户空间发送一个消息给内核,然后内核将其打印输出,就这么简单。用户空间的示例代码如下【mynlusr.c】
点击(此处)折叠或打开
- #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>
- #define MAX_PAYLOAD 1024 /*消息最大负载为1024字节*/
- int main(int argc, char* argv[])
- {
- struct sockaddr_nl dest_addr;
- struct nlmsghdr *nlh = NULL;
- struct iovec iov;
- int sock_fd=-1;
- struct msghdr msg;
- if(-1 == (sock_fd=socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST))){ //创建套接字
- perror("can't create netlink socket!");
- return 1;
- }
- memset(&dest_addr, 0, sizeof(dest_addr));
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0; /*我们的消息是发给内核的*/
- dest_addr.nl_groups = 0; /*在本示例中不存在使用该值的情况*/
- //将套接字和Netlink地址结构体进行绑定
- if(-1 == bind(sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr))){
- perror("can't bind sockfd with sockaddr_nl!");
- return 1;
- }
- if(NULL == (nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)))){
- perror("alloc mem failed!");
- return 1;
- }
- memset(nlh,0,MAX_PAYLOAD);
- /* 填充Netlink消息头部 */
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = 0;
- nlh->nlmsg_type = NLMSG_NOOP; //指明我们的Netlink是消息负载是一条空消息
- nlh->nlmsg_flags = 0;
- /*设置Netlink的消息内容,来自我们命令行输入的第一个参数*/
- strcpy(NLMSG_DATA(nlh), argv[1]);
- /*这个是模板,暂时不用纠结为什么要这样用。有时间详细讲解socket时再说*/
- memset(&iov, 0, sizeof(iov));
- iov.iov_base = (void *)nlh;
- iov.iov_len = nlh->nlmsg_len;
- memset(&msg, 0, sizeof(msg));
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- sendmsg(sock_fd, &msg, 0); //通过Netlink socket向内核发送消息
- /* 关闭netlink套接字 */
- close(sock_fd);
- free(nlh);
- return 0;
- }
上面的代码逻辑已经非常清晰了,都是socket编程的API,唯一不同的是我们这次编程是针对Netlink协议簇的。这里我们提前引入了BSD层的消息结构体struct msghdr{},定义在<include/linux/socket.h>文件里,以及其数据块struct iovec{}定义在<include/linux/uio.h>头文件里。这里就不展开了,大家先记住这个用法就行。以后有时间再深入到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消息中数据部分的首地址,在写入和读取消息数据部分时会用到它。
它们之间的关系如下:
内核空间的示例代码如下【mynlkern.c】:
点击(此处)折叠或打开
- #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 <linux/netlink.h>
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Koorey King");
- struct sock *nl_sk = NULL;
- static void nl_data_ready (struct sock *sk, int len)
- {
- struct sk_buff *skb;
- struct nlmsghdr *nlh = NULL;
- while((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL)
- {
- nlh = (struct nlmsghdr *)skb->data;
- printk("%s: received netlink message payload: %s \n", __FUNCTION__, (char*)NLMSG_DATA(nlh));
- kfree_skb(skb);
- }
- printk("recvied finished!\n");
- }
- static int __init myinit_module()
- {
- printk("my netlink in\n");
- nl_sk = netlink_kernel_create(NETLINK_TEST,0,nl_data_ready,THIS_MODULE);
- return 0;
- }
- static void __exit mycleanup_module()
- {
- printk("my netlink out!\n");
- sock_release(nl_sk->sk_socket);
- }
- module_init(myinit_module);
- module_exit(mycleanup_module);
在内核模块的初始化函数里我们用
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的消息负载。
将上述代码编译后测试结果如下:
Stage 2:
我们将上面的代码稍加改造就可以实现用户<->内核的双向数据通信。
首先是改造用户空间的代码:
点击(此处)折叠或打开
- #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>
- #define MAX_PAYLOAD 1024 /*消息最大负载为1024字节*/
- int main(int argc, char* argv[])
- {
- struct sockaddr_nl dest_addr;
- struct nlmsghdr *nlh = NULL;
- struct iovec iov;
- int sock_fd=-1;
- struct msghdr msg;
- if(-1 == (sock_fd=socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST))){
- perror("can't create netlink socket!");
- return 1;
- }
- memset(&dest_addr, 0, sizeof(dest_addr));
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0; /*我们的消息是发给内核的*/
- dest_addr.nl_groups = 0; /*在本示例中不存在使用该值的情况*/
- if(-1 == bind(sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr))){
- perror("can't bind sockfd with sockaddr_nl!");
- return 1;
- }
- if(NULL == (nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)))){
- perror("alloc mem failed!");
- return 1;
- }
- memset(nlh,0,MAX_PAYLOAD);
- /* 填充Netlink消息头部 */
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = getpid();//我们希望得到内核回应,所以得告诉内核我们ID号
- nlh->nlmsg_type = NLMSG_NOOP; //指明我们的Netlink是消息负载是一条空消息
- nlh->nlmsg_flags = 0;
- /*设置Netlink的消息内容,来自我们命令行输入的第一个参数*/
- strcpy(NLMSG_DATA(nlh), argv[1]);
- /*这个是模板,暂时不用纠结为什么要这样用。*/
- memset(&iov, 0, sizeof(iov));
- iov.iov_base = (void *)nlh;
- iov.iov_len = nlh->nlmsg_len;
- memset(&msg, 0, sizeof(msg));
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- sendmsg(sock_fd, &msg, 0); //通过Netlink socket向内核发送消息
- //接收内核消息的消息
- printf("waiting message from kernel!\n");
- memset((char*)NLMSG_DATA(nlh),0,1024);
- recvmsg(sock_fd,&msg,0);
- printf("Got response: %s\n",NLMSG_DATA(nlh));
- /* 关闭netlink套接字 */
- close(sock_fd);
- free(nlh);
- return 0;
- }
内核空间的修改如下:
点击(此处)折叠或打开
- #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> /*该文头文件里包含了linux/netlink.h,因为我们要用到net/netlink.h中的某些API函数,nlmsg_pug()*/
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Koorey King");
- struct sock *nl_sk = 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 = 0; //如果目的组为内核或某一进程,该字段也置0
- message[slen] = '\0';
- memcpy(NLMSG_DATA(nlh), message, slen+1);
- //通过netlink_unicast()将消息发送用户空间由dstPID所指定了进程号的进程
- netlink_unicast(nl_sk,skb,dstPID,0);
- printk("send OK!\n");
- return;
- }
- static void nl_data_ready (struct sock *sk, int len)
- {
- struct sk_buff *skb;
- struct nlmsghdr *nlh = NULL;
- while((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL)
- {
- nlh = (struct nlmsghdr *)skb->data;
- printk("%s: received netlink message payload: %s \n", __FUNCTION__, (char*)NLMSG_DATA(nlh));
- kfree_skb(skb);
- sendnlmsg("I see you",nlh->nlmsg_pid); //发送者的进程ID我们已经将其存储在了netlink消息头部里的nlmsg_pid字段里,所以这里可以拿来用。
- }
- printk("recvied finished!\n");
- }
- static int __init myinit_module()
- {
- printk("my netlink in\n");
- nl_sk = netlink_kernel_create(NETLINK_TEST,0,nl_data_ready,THIS_MODULE);
- return 0;
- }
- static void __exit mycleanup_module()
- {
- printk("my netlink out!\n");
- sock_release(nl_sk->sk_socket);
- }
- module_init(myinit_module);
- module_exit(mycleanup_module);
重新编译后,测试结果如下:
Stage 3:
前面我们提到过,如果用户进程希望加入某个多播组时才需要调用bind()函数。前面的示例中我们没有这个需求,可还是调了bind(),心头有些不爽。在前几篇博文里有关于socket编程时几个常见API的详细解释和说明,不明白的童鞋可以回头去复习一下。
因为Netlink是面向无连接的数据报的套接字,所以我们还可以用sendto()和recvfrom()来实现数据的收发,这次我们不再调用bind()。将Stage 2的例子稍加改造一下,用户空间的修改如下:
点击(此处)折叠或打开
- #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>
- #define MAX_PAYLOAD 1024 /*消息最大负载为1024字节*/
- int main(int argc, char* argv[])
- {
- struct sockaddr_nl dest_addr;
- struct nlmsghdr *nlh = NULL;
- //struct iovec iov;
- int sock_fd=-1;
- //struct msghdr msg;
- if(-1 == (sock_fd=socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST))){
- perror("can't create netlink socket!");
- return 1;
- }
- memset(&dest_addr, 0, sizeof(dest_addr));
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0; /*我们的消息是发给内核的*/
- dest_addr.nl_groups = 0; /*在本示例中不存在使用该值的情况*/
- /*不再调用bind()函数了
- if(-1 == bind(sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr))){
- perror("can't bind sockfd with sockaddr_nl!");
- return 1;
- }*/
- if(NULL == (nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)))){
- perror("alloc mem failed!");
- return 1;
- }
- memset(nlh,0,MAX_PAYLOAD);
- /* 填充Netlink消息头部 */
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = getpid();//我们希望得到内核回应,所以得告诉内核我们ID号
- nlh->nlmsg_type = NLMSG_NOOP; //指明我们的Netlink是消息负载是一条空消息
- nlh->nlmsg_flags = 0;
- /*设置Netlink的消息内容,来自我们命令行输入的第一个参数*/
- strcpy(NLMSG_DATA(nlh), argv[1]);
- /*这个模板就用不上了。*/
- /*
- memset(&iov, 0, sizeof(iov));
- iov.iov_base = (void *)nlh;
- iov.iov_len = nlh->nlmsg_len;
- memset(&msg, 0, sizeof(msg));
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- */
- //sendmsg(sock_fd, &msg, 0); //不再用这种方式发消息到内核
- sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr));
- //接收内核消息的消息
- printf("waiting message from kernel!\n");
- //memset((char*)NLMSG_DATA(nlh),0,1024);
- memset(nlh,0,MAX_PAYLOAD); //清空整个Netlink消息头包括消息头和负载
- //recvmsg(sock_fd,&msg,0);
- recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),NULL);
- printf("Got response: %s\n",NLMSG_DATA(nlh));
- /* 关闭netlink套接字 */
- close(sock_fd);
- free(nlh);
- return 0;
- }
内核空间的代码完全不用修改,我们仍然用netlink_unicast()从内核空间发送消息到用户空间。
重新编译后,测试结果如下:
和Stage 2中代码运行效果完全一样。也就是说,在开发Netlink程序过程中,如果没牵扯到多播机制,那么用户空间的socket代码其实是不用执行bind()系统调用的,但此时就需要用sendto()和recvfrom()完成数据的发送和接收的任务;如果执行了bind()系统调用,当然也可以继续用sendto()和recvfrom(),但给它们传递的参数就有所区别。这时候一般使用sendmsg()和recvmsg()来完成数据的发送和接收。大家根据自己的实际情况灵活选择。
未完,待续…
用户空间和内核空间通讯之【Netlink 中】的更多相关文章
- 用户空间和内核空间通讯之【Netlink 上】
原文地址:用户空间和内核空间通讯之[Netlink 上] 作者:wjlkoorey258 引言 Alan Cox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动接 ...
- 如何看待Linux操作系统的用户空间和内核空间
作为中央核心处理单元的CPU,除了生产工艺的不断革新进步外,在处理数据和响应速度方面也需要有权衡.稍有微机原理基础的人都知道Intel X86体系的CPU提供了四种特权模式ring0~ring3,其中 ...
- Linux用户空间与内核空间(理解高端内存)
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...
- Linux用户空间与内核空间
源:http://blog.csdn.net/f22jay/article/details/7925531 Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针 ...
- linux 用户空间与内核空间——高端内存详解
摘要:Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对 ...
- Linux内存管理--用户空间和内核空间【转】
本文转载自:http://blog.csdn.net/yusiguyuan/article/details/12045255 关于虚拟内存有三点需要注意: 4G的进程地址空间被人为的分为两个部分--用 ...
- Operating System-Thread(3)用户空间和内核空间实现线程
http://www.cnblogs.com/Brake/archive/2015/12/02/Operating_System_Thread_Part3.html 本文主要内容: 操作系统用户空间和 ...
- linux内存管理--用户空间和内核空间
关于虚拟内存有三点需要注意: 4G的进程地址空间被人为的分为两个部分--用户空间与内核空间.用户空间从0到3G(0xc0000000),内核空间占据3G到4G.用户进程通常情况下只能访问用户空间的虚拟 ...
- linux用户空间和内核空间(内核高端内存)_转
转自:Linux用户空间与内核空间(理解高端内存) 参考: 1. 进程内核栈.用户栈 2. 解惑-Linux内核空间 3. linux kernel学习笔记-5 内存管理 Linux 操作系统和驱 ...
随机推荐
- 【数据结构与算法】Fibonacci Sequence
学计算机的对 Fibonacci 都并不陌生,在课堂上一讲到递归几乎都会提到 Fibonacci 数列.不久前,我对 Fibonacci 产生了一些兴趣,就在这里把自己的想法给记录下来. 递推公式: ...
- editrules
editrules editrules是用来设置一些可用于可编辑列的colModel的额外属性的.大多数的时候是用来在提交到服务器之前验证用户的输入合法性的.比如editrules:{edith ...
- 微信小程序--问题汇总及详解之picker 增、删
<block wx:for="{{salesList}}" wx:for-index="index" wx:key="id" wx:f ...
- 历史Linux镜像的问题修复方案
历史Linux镜像创建的ECS云服务器,可能存在NTP没有配置,YUM没有配置,还可能存在最近暴漏较高的安全漏洞,请按照以下步骤进行修复,可以让您的云服务器更加安全,还可以使用阿里云提供的YUM服务进 ...
- ZOJ 2112 Dynamic Rankings(带修改的区间第K大,分块+二分搜索+二分答案)
Dynamic Rankings Time Limit: 10 Seconds Memory Limit: 32768 KB The Company Dynamic Rankings has ...
- [hdu5307] He is Flying [FFT+数学推导]
题面 传送门 思路 看到这道题,我的第一想法是前缀和瞎搞,说不定能$O\left(n\right)$? 事实证明我的确是瞎扯...... 题目中的提示 这道题的数据中告诉了我们: $sum\left( ...
- [NOI2009] 植物大战僵尸 [网络流]
题面: 传送门 思路: 这道题明显可以看出来有依赖关系 那么根据依赖(保护)关系建图:如果a保护b则连边(a,b) 这样,首先所有在环上的植物都吃不到,被它们间接保护的也吃不到 把这些植物去除以后,剩 ...
- BZOJ3260 跳 【组合数】
题目 邪教喜欢在各种各样空间内跳.现在,邪教来到了一个二维平面. 在这个平面内,如果邪教当前跳到了(x,y),那么他下一步可以选择跳到以下4个点: (x-1,y),(x+1,y),(x,y-1),(x ...
- 觉醒力量 (hidpower)
觉醒力量 (hidpower) 题目描述 [题目背景] 从前有一款非常火的游戏被人们称为pokemon,从最初的红绿蓝黄版直到现在的XY版,都受到世界各地小朋友和大朋友们的喜爱. [题意描述] 作为一 ...
- Java简明教程 11.异常
异常的感性认识 在没有异常机制的语言中,经常通过返回值来表示调用该函数产生的各种问题(异常),比如c语言. divide.c #include <stdio.h> int main() { ...