在上一篇博文中我们所遇到的情况都是用户空间作为消息进程的发起者,Netlink还支持内核作为消息的发送方的情况。这一般用于内核主动向用户空间报告一些内核状态,例如我们在用户空间看到的USB的热插拔事件的通告就是这样的应用。

先说一下我们的目标,内核线程每个一秒钟往一个多播组里发送一条消息,然后用户空间所以加入了该组的进程都会收到这样的消息,并将消息内容打印出来。

Netlink地址结构体中的nl_groups是32位,也就是说每种Netlink协议最多支持32个多播组。如何理解这里所说的每种Netlink协议?在里预定义的如下协议都是Netlink协议簇的具体协议,还有我们添加的NETLINK_TEST也是一种Netlink协议。

#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;// 创建Socketsock_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标识,如果是内核发的则置0NETLINK_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];… …
}

当内核态的Netlink发送数据到用户空间时一般需要填充skbuff的控制块,填充的方式是通过强制类型转换,将其转换成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协议簇里提到的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传递大数据时的处理等等都是开发人员需要考虑的问题。

Netlink0004 --- 多播机制的用法相关推荐

  1. Android消息机制Handler用法

    这篇文章介绍了Android消息机制Handler用法总结,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 1.简述 Handler消息机制主要包括: Messa ...

  2. Redis Sentinel机制与用法说明【转】

    本文来自:https://segmentfault.com/a/1190000002680804 概述 Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Ma ...

  3. Redis Sentinel 机制与用法(二)

    本专栏与Redis相关的文章 Redis Sentinel机制与用法(一) Redis Sentinel机制与用法(二) Jedis的JedisSentinelPool源代码分析 Jedis的Shar ...

  4. Redis Sentinel机制与用法

    概述 Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都 ...

  5. Redis基础进阶--Sentinel 机制与用法(二)

    概述 Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都 ...

  6. Signal:python用信号处理程序的机制及用法举例

    1.Signal模块简介 提供了在 Python 中使用信号处理程序的机制 2.signal.signal() signal.signal(signalnum, handler): signalnum ...

  7. Android 广播机制以及用法详解 (转)

    转:http://blog.sina.com.cn/s/blog_5da93c8f010178zl.html 参考:http://blog.sina.com.cn/s/blog_80723de8010 ...

  8. QT中Model-View-Delegate委托代理机制用法介绍

    文章目录 本地数据加载(Data) 添加数据模型(Model) 添加代理模型(Proxy) 添加元素的代理(Delegate) 添加视图层(View) 使用效果 之前的一篇文章中介绍过QT的委托代理机 ...

  9. 清除浏览器缓存之后为什么还是显示旧的html页面_H5缓存机制浅析-移动端Web加载性能优化...

    1 H5缓存机制介绍 H5,即HTML5,是新一代的HTML标准,加入很多新的特性.离线存储(也可称为缓存机制)是其中一个非常重要的特性.H5引入的离线存储,这意味着 web 应用可进行缓存,并可在没 ...

  10. aidl使用_借助 AIDL 理解 Android Binder 机制——Binder 来龙去脉

    AIDL 是 Android Interface Definition Language(Android 接口定义语言)的缩写,它是 Android 进程间通信的接口语言.由于 Android 系统的 ...

最新文章

  1. 算法设计思想(4)— 分治法
  2. 互斥锁、死锁、递归锁、信号量、Event
  3. leetcode —— 200. 岛屿数量
  4. Flutter自定义相机,Flutter相册选择照片
  5. java网络爬虫基础学习(二)
  6. JavaScript正则表达式与注册验证
  7. 署名用by还是from_介词专题|工作“到7点”是By 7pm还是Until 7pm?
  8. USB转TTL|mcuisp使用
  9. OpenCV中的width与widthStep
  10. 大学计算机基础发送邮件,东南大学《大学计算机基础》实验16邮件合并.pdf
  11. acm题库及答案 java_ACM试题及答案.docx
  12. 大数据千亿级离线数仓项目第三天 维度数据分析与业务开发
  13. python 调用HEG工具批量处理modis数据将hdf转为tif
  14. 老鹰酒馆 dna_酒吧和酒馆的21个最佳WordPress主题
  15. 学位计算机考试成绩怎么查询时间,湖北省自考00019计算机实践考试成绩在哪里查询...
  16. 小米为什么不怕iPhone降价?
  17. android中高级面试题,Android高级工程师必看系列
  18. 刷题-整数和罗马数字转换
  19. JNLP文件详细说明
  20. 删除之后在计算机操作中快捷键,电脑删除快捷键有哪些|电脑删除快捷键大全...

热门文章

  1. 高程三(1):简介、使用、严格模式
  2. ubuntu安装mysql后不能远程访问的方法
  3. 进程和线程的理解(转)
  4. JS的IE和FF兼容性问题汇总
  5. 主动FTP vs. 被动FTP 权威解释
  6. 记于开学两个星期...十九岁快乐!
  7. 网站如何经过身份验证_如何配置TLS?
  8. webpack入门总结1
  9. CentOS 6.6安装配置LAMP服务器(Apache+PHP5+MySQL)
  10. 备忘录 | 当我每晚闲暇时我在干什么(1)