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