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