linux netlink的使用及示例

  netlink内核及用户态使用实例

 

1netlink的用途及其优点

 

         由于开发和维护内核的复杂性,只用最为关键同时对性能要求最高的代码才会放在内核中。其他的诸如GUI,管理和控制代码,通常放在用户空间运行。这种将实现分离在内核和用户空间的思想在Linux中非常常见。现在的问题是内核代码和用户代码如果彼此通信。

   答案是内核空间和用户空间存在的各种IPC方法,例如系统调用,ioctl,proc文件系统和netlink socket。

    那么netlink相对于系统调用,ioctl和proc文件系统有哪些优点呢?

    为新特性添加系统调用,ioctl和proc文件系统相对 而言是一项比较复杂的工作,我们冒着污染内核和损害系统稳定性的风险。netlinksocket相对简单:只有一个常量,协议类型,需要加入到netlink.h中。然后,内核模块和用户程序可以通过socket类型的API进行通信。

     和其他socket API一样,Netlink是异步的,它提供了一个socket队列来平滑突发的信息。发送一个netlink消息的系统调用将消息排列到接受者的 netlink队列中,然后调用接收者的接收处理函数。接收者,在接收处理函数的上下文中,可以决定是否立即处理该消息还是等待在另一个上下文中处理。不像ioctl(),系统调用需要同步处理。因此,如果我们使用了一个系统调用来传递一条消息到内核,如果需要处理该条信息的时间很长,那么内核调度粒度可以会受影响。

    在内核中实现的系统调用代码在编译时被静态的链接到内核中,因此在一个可以动态加载的模块中包括系统调用代码是不合适的。在netlink socket中,内核中的netlink核心和在一个可加载的模块中没有编译时的相互依赖。

    netlinksocket支持多播,这也是其与其他交互手段相比较的优势之一。一个进程可以将一条消息广播到一个netlink组地址。任意多的进程可以监听那个组地址。这提供了一种从内核到用户空间进行事件分发接近完美的机制。

    从会话只能由用户空间应用发起的角度来看,系统调用和ioctl是单一的IPC。但是,如果一个内核模块有一个用户空间应用的紧急消息,没有一种直接的方法来实现这些功能。通常,应用需要阶段性的轮询内核来获取状态变化,尽管密集的轮询会有很大的开销。netlink通过允许内核初始化一个对话来优雅的解决 这个问题。我们称之为netlink的复用特性。

 

2与netlink相关的数据结构

         2.1struct sockaddr_nl 

struct sockaddr_nl {

         sa_family_t      nl_family;          /*AF_NETLINK         */

         unsignedshort nl_pad;              /* zero               */

         __u32                nl_pid;               /* port ID          */

        __u32                nl_groups;        /*multicast groups mask */

         };

         在创建一个netlink socket后,需要使用该结构bind一下netlink套接字的本地地址,nf_family填AF_NETLINK,nl_pid可以填成getpid(),如果应用想接受发送给特定多播组的netlink消息,所有感兴趣的多播组bit应该被置位并填充到nl_groups域,否则nl_groups应该被显式清0.接收感兴趣多播组的数据包的情况下,nl_pid可以被置为0.

    bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr));

 

    当用户态要发送netlink消息给内核时,需要另外一个structsockaddr_nl作为目的地址,这时nl_pid和nl_groups需要置为0

 

         2.2struct iovec

         该结构用来记录用户态进程传递给内核态数据的nlmsghdr的相关信息。

         structiovec

         {

                  void __user *iov_base; //指向相关nlmsghdr的起始地址

                  __kernel_size_t iov_len;//记录了nlmsghdr及其负载数据的长度

         };

 

         2.3struct nlmsghdr

         structnlmsghdr {

                  __u32                nlmsg_len;        //负载及头部的长度

                  __u16                nlmsg_type;     //netlink消息的类型,用于区分同类型netlink消息的小分支

                  __u16                nlmsg_flags;    

                  __u32                nlmsg_seq;      

                  __u32                nlmsg_pid;        //一般填成getpid()

         };


3相关实例

3.1内核部分源码(下面内核模块代码基于linux2.6.38内核)

#include <linux/init.h> 
#include <linux/module.h> 
//#include <net/sock.h>
#include <net/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/kthread.h>
#include <linux/sched.h>


MODULE_LICENSE("Dual BSD/GPL"); 

static int  mode = 0;
module_param(mode, int, S_IRUGO);
#define MAX_PAYLOAD 128

extern struct net init_net;
static struct sock *testnetlink = NULL;
static struct task_struct * k_thread = NULL;

//netlink 消息的处理函数
static void testnetlink_rcv_skb(struct sk_buff *skb)
{
	struct nlmsghdr *nlh;
	struct sk_buff * reply_skb = NULL;
	char *data = NULL;
	char tmp[128];
	int rlen = 0, rc = 0, pid;

	if(!mode){
		printk("@@@@###############%s : %d!\n",
			__func__, __LINE__);
		memset(tmp ,0 ,sizeof(tmp));
		//receive the data from userspace
	    //解析来自用户态的数据
		if (skb->len >= NLMSG_SPACE(0)) {
			//获取用户态传来的nlmsghdr数据结构
			nlh  = nlmsg_hdr(skb);
			//real data length,len - header length
			rlen = nlmsg_len(nlh);
		    //获取nlmsghdr中的负载数据
			data = nlmsg_data(nlh);
	        //获取发送该netlink message的进程pid
			pid = nlh->nlmsg_pid;
				
			strcpy(tmp, data);
			printk("@@@@@receive data is %s \n", 
				 tmp);	
			//kfree_skb(skb);
		}

		//send packet to userspace from kernel
	    //分配一个数据包, 
		reply_skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC);

		if(!reply_skb){
			printk("@@@@####reply_skb alloc error!\n");
			return ;
		}
		
        //设置netlink消息的相关参数
		nlh = nlmsg_put(reply_skb, 0,0, 0, MAX_PAYLOAD,0);
	    
		NETLINK_CB(reply_skb).pid = 0;	
		//copy netlink消息的内容
		strcpy(nlmsg_data(nlh), "netlink received!\n");
		//进行发送
		rc = netlink_unicast(testnetlink, reply_skb, pid, MSG_DONTWAIT);
		if(rc < 0){
			printk("netlink unicast error!\n");
		}
	}
	
}


static int testnetlink_init(void) 
{
	printk("@@@@###############!\n");
    //在内核态创建一个netlink socket,注册相应的处理函数testnetlink_rcv_skb
	testnetlink = netlink_kernel_create(&init_net, 27,
			0, testnetlink_rcv_skb, NULL,
			THIS_MODULE);

	if(testnetlink == 0){
		printk("@@@@####can't create netlink socket.\n");
		return -1;
	}
	return 0; 
} 

static void testnetlink_exit(void) 
{ 
	netlink_kernel_release(testnetlink);
	printk("Goodbye, cruel world\n"); 
} 

module_init(testnetlink_init); 
module_exit(testnetlink_exit);

相关makefile:
obj-m := testnetlink.o 
KERNELDIR := /lib/modules/2.6.34.10-WR4.3.0.0_standard/build 
PWD := $(shell pwd) 

default: 
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 

modules_install: 
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 


3.2用户态源码

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/sockios.h>
#include <errno.h>
#include <linux/rtnetlink.h>
#define RTNL_RCV_BUF 16384
#define MAX_PAYLOAD 128
struct req{
	struct nlmsghdr nlh;
	char buf[MAX_PAYLOAD];
};

int main()
{
	int sk = 0,ret = 0;
	struct sockaddr_nl nladdr, to_nladdr;
	struct msghdr msg;
	struct nlmsghdr * nlh;
	struct iovec iov;
	struct req r;

	memset(&msg , 0 ,sizeof(struct msghdr));
	sk = socket(AF_NETLINK, SOCK_RAW, 27);
	
	if (sk == -1){
		printf("open socket error!\n");
		goto error;
	}

	memset(&nladdr, 0, sizeof(nladdr));
	//设置bind用的本地 struct sockaddr
    nladdr.nl_family = AF_NETLINK;
	nladdr.nl_groups = 0; 
	nladdr.nl_pid = getpid();
	
	//绑定本地socket
	if (bind(sk, (struct sockaddr *)&nladdr, sizeof(struct sockaddr_nl))< 0){
		printf("bind netlink socket error!\n");
		goto error;
	}
//send netlink message to kernel
	memset(&to_nladdr, 0, sizeof(to_nladdr));
	
	//设置发送用的目的sockaddr_nl
	to_nladdr.nl_family = AF_NETLINK;
	to_nladdr.nl_pid = 0;
	to_nladdr.nl_groups = 0;
	#if 0
	nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
	nlh->nlmsg_len =NLMSG_SPACE(MAX_PAYLOAD);
	nlh->nlmsg_pid = getpid();
	nlh->nlmsg_flags = 0;

	strcpy(NLMSG_DATA(nlh), "hello ,you!");
	#else
	//设置承载负荷数据的nlmsghdr
    //包含数据和头部长度的nlmsghdr的长度
	r.nlh.nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	r.nlh.nlmsg_pid = getpid();
	r.nlh.nlmsg_flags = 0;
	memset(r.buf, 0, MAX_PAYLOAD);
    //设置数据
	strcpy(NLMSG_DATA(&(r.nlh)), "hello ,you!");
	#endif
    //通过iov指向要使用的nlmsghdr
	iov.iov_base = &r;
	iov.iov_len = sizeof(r);

	//设置msghdr
	msg.msg_name = &to_nladdr;
	msg.msg_namelen = sizeof(to_nladdr);
	msg.msg_iov = &iov;
	msg.msg_iovlen =1;

	//发送数据
	ret  = sendmsg(sk, &msg, 0);

	printf("@@@@@#####ret is %d, errno is %d\n", 
		ret, errno);

//receive kernel netlink message
	memset(&(r.nlh), 0 , sizeof(r));
	//接受内核发来的数据
	recvmsg(sk, &msg, 0);
	printf("@@@@######receive data from kernel is %s\n",
		NLMSG_DATA(&(r.nlh)));
	close(sk);
	return 0;
error:
	return -1;
}


你可能感兴趣的:(linux,netlink,linux内核,用户态)