用户空间和内核空间通讯之Netlink 下 用户空间与内核空间

在上一篇博文中我们所遇到的情况都是用户空间作为消息进程的发起者,Netlink还支持内核作为消息的发送方的情况。这一般用于内核主动向用

户空间报告一些内核状态,例如我们在用户空间看到的USB的热插拔事件的通告就是这样的应用。

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

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

#define NETLINK_ROUTE0/* Routing/device hook*/

#define NETLINK_UNUSED1/* Unused number*/

#define NETLINK_USERSOCK2/* Reserved for user mode socket protocols */

#define NETLINK_FIREWALL3/* Firewalling hook*/

#define NETLINK_INET_DIAG4/* INET socket monitoring*/

#define NETLINK_NFLOG5/* netfilter/iptables ULOG */

#define NETLINK_XFRM6/* ipsec */

#define NETLINK_SELINUX7/* SELinux event notifications */

#define NETLINK_ISCSI8/* Open-iSCSI */

#define NETLINK_AUDIT9/* auditing */

#define NETLINK_FIB_LOOKUP10

#define NETLINK_CONNECTOR11

#define NETLINK_NETFILTER12/* netfilter subsystem */

#define NETLINK_IP6_FW13

#define NETLINK_DNRTMSG14/* DECnet routing messages */

#define NETLINK_KOBJECT_UEVENT15/* Kernel messages to userspace */

#define NETLINK_GENERIC16

/* leave room for NETLINK_DM (DM Events) */

#define NETLINK_SCSITRANSPORT18/* SCSI Transports */

#define NETLINK_ECRYPTFS19

#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;

用户空间和内核空间通讯之【Netlink 下】 用户空间与内核空间
}

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] = '';

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 inn");

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.

*/

charcb[48];

… …

}

当内核态的Netlink发送数据到用户空间时一般需要填充skbuff的控制块,填充的方式是通过强制类型转换,将其转换成struct netlink_skb_parms{}之后进行填充赋值的:

点击(此处)折叠或打开

struct netlink_skb_parms

{

struct ucredcreds;/* Skb credentials*/

__u32pid;

__u32dst_group;

kernel_cap_teff_cap;

__u32loginuid;/* Login (audit) uid */

__u32sid;/* 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传递大数据时的处理等等都是开发人员需要考虑的问题。 完。

关于Netlink多播机制的用法

博客推荐文章

linux (2011-12-06 15:03:57)

linux (2012-03-30 13:29:15)

eLinux (2012-03-28 15:39:56)

SELinux (2012-06-19 08:43:25)

linux起步 (2012-04-17 12:39:16)

分享到:新浪微博QQ空间开心网豆瓣人人网twitterfb

0



热门推荐

IBM System x3100 M4(...

IBM X3650 M4(7915I01...

IBM System x3650 M4(...

惠普 ProLiant ML110 G...

无法选择打开方式

udhcpc

perl 删除空格行

虚拟机virtual pc

阅读(108)┊ 评论 (0)┊收藏(0)┊举报┊打印

前一篇:用户空间和内核空间通讯之【Netlink 中】

亲,您还没有登录,请[登录]或[注册]后再进行评论

关于我们 | 关于IT168 | 联系方式 | 广告合作 | 法律声明 | 免费注册 Copyright ? 2001-2010 ChinaUnix.net All Rights Reserved 北京皓辰网域网络信息技术有限公司. 版权所有

感谢所有关心和支持过ChinaUnix的朋友们

京ICP证041476号 京ICP证060528号

  

爱华网本文地址 » http://www.aihuau.com/a/25101013/180189.html

更多阅读

我和丈母娘的相处之道 朋友之间的相处之道

我和丈母娘的相处之道饮者_无名第1节 我和老婆是相亲认识的,之前也相过两次,匪夷所思的经历让我基本上对相亲是不报什么希望了,第三次,我遇到了当时年轻漂亮又娴静温柔的老婆,从各方面看她都具备做我未来妻子的潜质,我不相信这样的美眉

《三峡之秋》课堂实录与评析 三峡之秋教学实录

《三峡之秋》课堂实录与评析富裕县友谊小学 张敏设计理念:《新课程标准》明确指出:阅读教学是学生个性化的行为,应让学生在主动积极的思维和情感活动中有所感悟,受到情感的熏陶。本课设计以“读”为主线,在读中理解,在读中想象,在读中体会,

系统安全之各类脱壳方法与注意事项 双系统注意事项

系统安全之各类脱壳方法与注意事项最近一个月才接触壳这样东西,虽然俺是个新手,不过俺很努力,也会脱很多软件的壳。下面就让我介绍一下俺所知道的各种脱壳方法!先介绍一下脱壳的基本知识吧!常见脱壳知识:1.PUSHAD (压栈) 代表程序的入口点2.

声明:《用户空间和内核空间通讯之Netlink 下 用户空间与内核空间》为网友雾涣风月分享!如侵犯到您的合法权益请联系我们删除