作者: Kevin He on wed,2005-01-05 02:00
原文: Kernel Korner – Why and How to Use Netlink Socket
翻译: Ricky Goo (http://cn.iventor.org/people.html#ricky)
网址: http://cn.iventor.org/forum/viewtopic.php?t=715
使用这个双向、多用的方法来解决内核空间――用户空间的数据传递问题。
由于内核的不断发展和维护的复杂性,只有大部分的基础性的、临界执行的代码植入内核中,而其他的,例如GUI、管理和控制模块都是在用户空间执行的。Linux中,内核空间和用户空间的数据交换是相当频繁的,其主要问题是如何让内核代码和用户空间代码进行相互通信。
众所周知,解决这个问题的方发,是利用各种不同的内核-用户进程间通信方法。例如系统调用、ioctl、proc文件系统和Netlink套接字。本文介绍了netlink套接字并且展示了这一具有友好特型的进程间通信套接字的优点。
简介:
Netlink套接字是一种特殊的应用于那和空间和用户空间进行进程间数据传输的进程间通信方法,进程间通信方法在内核空间和用户空间提供了一种全双工通信方式,具体方法是在用户空间使用标准API,在内核空间使用特殊API来实现的。Netlink套接字使用的协议族是AF_NETLINK,其功能就像TCP/IP协议族中的AF_INET一样。在include/linux/netlink.h头文件中,定义了各种不同风格的netlink套接字。
下面是一些netlink套接字所支持的协议类型和特征。
? NETLINK_ROUTE:用户空间路由信息交流渠道。例如BGP、OSPF、RIP以及内核包中的推进模块。内核空间通过本Netlink协议类型对内核空间的路由表进行更新。
? NETLINK_FIREWALL:接收从IPv4防火墙代码发送来的数据包。
? NETLINK_NFLOG:是用户空间iptable工具和内核Netfilter模块之间的通信渠道。
? NETLINK_ARPD:从用户空间来维护ARP表。
在实现内核-用户空间信息通信时,为何使用以上提到的netlink套接字来代替传统的系统调用、ioctl和proc文件系统呢?这是因为使用系统调用、ioctl和proc文件来实现某些功能并不是一件简单的事情,而且我们使用他们的时候,是冒着影响内核正常工作和破坏系统的风险来做的。而Netlink套接字是简单易用的,其实:我们只需要在netlink.h中添加一个我们所需要的协议类型,一个固定的数值(通常是17—32之间的整数),然后,内核空间和用户空间就可以马上来实现通信了。简单吧?
Netlink是一种异步模式,和其他套接字API相比,它提供了一个套接字队列用来缓冲消息的迅速膨胀。发送一个Netlink消息的系统调用将消息放入接收者的netlink队列中,然后调用接收者的接收句柄。接收方,结合接收句柄,可以判断出是应该立即对其作出处理还是将其闲置于队列中等待其他进程上下文对其处理。系统调用不同于netlink,它是一种同步模式。因此,如果我们使用系统调用从用户空间发送一个消息到内核空间的话,如果处理该消息的时间较长,则会影响到内核进程安排的处理粒度。
当一段代码在内核中执行一次系统调用时,这段代码在编辑的时候就已经连接到内核中去了;因此,在一个可加载模块中,使用系统调用是不合适的,例如某些设备的驱动程序模块。使用Netlink套接字就有所不同,并没有什么编辑时依赖于Linux内核中的netlink核心,并且netlink套接字可以在可加载模块中处于活动状态。
Netlink套接字是支持多播的,这使其相对于系统调用、ioctl、proc的又一个优势。一个进程可以将消息一多播的形式发到一个netlink地址组中,并且其他的任何进程可以对这个netlink地址组进行监听。这中方式为内核发送消息到用户提供了一种几乎是完美的机制。
系统调用和ioctl是一种简单的进程间通信,他们只能有用户空间的应用发起会话,这种方法不适合于当内核进程有紧急事件信息需要发送到用户空间的情况。通常,应用程序是通过周期性的对内核进行轮讯来取得状态的转换的,而太强的轮讯的消费是相当昂贵的。Netlink套接字解决了这一难题,它可以让内核空间主动发起会话。我们将这种方法称为netlink套接字的全双工特性。
总之,Netlink套接字提供了一个BSD套接字类型的API,这种类型是软件发展团体中的通用模式,是一种容易理解的类型。因此,相对于系统调用和ioctl的API的熟悉过程也较为短暂,训练花销较小。
涉及到BSD路由套接字
在BSDTCP/IP协议栈执行过程中,有一个特殊的套接字我们称之为路由套接字,其地址族定义为AF_ROUTE、协议族为PF_ROUTE、套接字类型为SOCK_RAW。BSD系统中的路由套接字用于对内核路由表内容进行添加或者删除路由信息。
在linux系统中,与上面提到的路由套接字有异曲同工之用的是netlink套接字提供的协议类型NETLINK_ROUTE。这是Netlink套接字提供的一个BSD路由套接字的父类。
Netlink套接字API
标准的套接字API有:socket(),sendmsg(),recvmsg()和close()。这些都可以用在用户空间来进入netlink套接字中。读者可用通过linux中的手册来对这些函数进行进一步的了解。这里我们只讨论在Netlink套接字环境中如何为这些函数选择参数。对于大多数从事过linux套接字编程的人们来说,这些函数并不陌生。
建立一个套接字。Socket()
int socket(ini domain, int type, int protocol)
这里套接字domain是指地址族,我们在Netlink中使用AF_NETLINK,套接字类型我们使用SOCK_RAW或者SOCK_DGRAM,因为netlink是一个基于数据包传输的套接字。
协议类型我们用netlink所提供的几种特型,例如NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_ARPD,NETLINK_ROUTE6,NETLINK_IP6_FW。我们在使用中可以根据需要选择其中的一个。
Netlink支持多点传送,最多可以定义32个不同的传送组。每一个组有一个比特掩码来表示。1<
绑定:bind()
和其他TCP/IP套接字相同,netlink套接字也需要使用bind()函数来进行原地址和套接字的绑定工作。Netlink套接字的地址类型如下:
代码: |
struct sodkaddr_nl { sa_family_t nl_family; /*AF_NETLINK*/ unsigned short nl_pad; /*zero*/ __u32 nl_pid; /*process pid*/ __u32 nl_groups; /*mcast groups mask*/ }nladdr; |
代码: |
struct nlmsghdr { __u32 nlmsg_len; /* Length of message */ __u16 nlmsg_type; /* Message type*/ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process PID */ }; |
代码: |
struct iovec iov; iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; |
代码: |
struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; iov.iov_base = (void *)nlh; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; recvmsg(fd, &msg, 0); |
引用: |
#define NETLINK_TEST 17 |
代码: |
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len)); |
代码: |
void input (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *payload = NULL; while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; payload = NLMSG_DATA(nlh); /* process netlink message with header pointed by * nlh and payload pointed by payload */ } |
代码: |
void input (struct sock *sk, int len) { wake_up_interruptible(sk->sleep); } |
代码: |
NETLINK_CB(skb).groups = local_groups; NETLINK_CB(skb).pid = 0; /* from kernel */ |
代码: |
NETLINK_CB(skb).dst_groups = dst_groups; NETLINK_CB(skb).dst_pid = dst_pid; |
代码: |
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock); |
代码: |
void netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, int allocation); |
代码: |
sock_release(nl_sk->socket); |
代码: |
#i nclude #i nclude #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; void main() { sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST); memset(&src_addr, 0, sizeof(src_addr)); src__addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /* self pid */ src_addr.nl_groups = 0; /* not in mcast groups */ bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; /* For Linux Kernel */ dest_addr.nl_groups = 0; /* unicast */ nlh=(struct nlmsghdr *)malloc( NLMSG_SPACE(MAX_PAYLOAD)); /* Fill the netlink message header */ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); /* self pid */ nlh->nlmsg_flags = 0; /* Fill in the netlink message payload */ strcpy(NLMSG_DATA(nlh), "Hello you!"); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); /* Read message from kernel */ memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); recvmsg(fd, &msg, 0); printf(" Received message payload: %s\n", NLMSG_DATA(nlh)); /* Close Netlink Socket */ close(sock_fd); } |
代码: |
#i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude struct sock *nl_sk = NULL; void nl_data_ready (struct sock *sk, int len) { wake_up_interruptible(sk->sleep); } void netlink_test() { struct sk_buff *skb = NULL; struct nlmsghdr *nlh = NULL; int err; u32 pid; nl_sk = netlink_kernel_create(NETLINK_TEST, nl_data_ready); /* wait for message coming down from user-space */ skb = skb_recv_datagram(nl_sk, 0, 0, &err); nlh = (struct nlmsghdr *)skb->data; printk("%s: received netlink message payload:%s\n", __FUNCTION__, NLMSG_DATA(nlh)); pid = nlh->nlmsg_pid; /*pid of sending process */ NETLINK_CB(skb).groups = 0; /* not in mcast group */ NETLINK_CB(skb).pid = 0; /* from kernel */ NETLINK_CB(skb).dst_pid = pid; NETLINK_CB(skb).dst_groups = 0; /* unicast */ netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT); sock_release(nl_sk->socket); } static int my_module_init(void){ printk(KERN_INFO "initializing Netlink Socket!\n"); netlink_test(); return 0; } static void netlink_clear(void) { printk(KERN_INFO"GOod Bye!\n"); } module_init(my_module_init); module_exit(netlink_clear); |
引用: |
Received message payload: Hello you! |
引用: |
netlink_test: received netlink message payload: Hello you! |
代码: |
#i nclude #i nclude #i nclude #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct msghdr msg; struct iovec iov; int sock_fd; int ret = 1; void main() { sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST); if (sock_fd == -1){ perror("socket error"); exit(1); } memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /*self pid */ /* interested in group 1<<0 */ src_addr.nl_groups = 35; if(bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr))== -1) { perror("bind error"); exit(1); } memset(&dest_addr, 0, sizeof(dest_addr)); nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); iov.iov_base = (void *)nlh; iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD); msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf("\nWaiting for message from kernel\n"); /* Read message from kernel */ ret = recvmsg(sock_fd, &msg, 0); printf("%d\n",ret); printf("Received message payload: %s\n",NLMSG_DATA(nlh)); close(sock_fd); } |
代码: |
#ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #i nclude #define MAX_PAYLOAD 1024 struct sock *nl_sk = NULL; int bc; void nl_data_ready(struct sock *sk,int len) { wake_up_interruptible(sk->sleep); } void netlink_test() { struct sk_buff *skb = NULL; struct nlmsghdr *nlh = NULL; nl_sk = netlink_kernel_create(NETLINK_TEST,nl_data_ready); skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD),GFP_KERNEL); nlh = (struct nlmsghdr *)skb->data; nlh = (struct nlmsghdr *)skb->data; nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = 0; /*from kernel */ nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh), "Greeting From Kernel!"); /* sender is in group 1<<0 */ NETLINK_CB(skb).groups = 1; NETLINK_CB(skb).pid = 0; /* from kernel */ NETLINK_CB(skb).dst_pid = 0; /* multicast */ /* to mcast group 1<<0 */ NETLINK_CB(skb).dst_groups = 1; /*multicast the message to all listening processes*/ bc = netlink_broadcast(nl_sk, skb, 0, 1, GFP_KERNEL); /* bc = netlink_unicast(nl_sk,skb,0,MSG_DONTWAIT); */ printk(KERN_INFO"netlinkbroadast :%d\n",bc); /*sock_release(nl_sk->socket);*/ } static int my_module_init(void){ printk(KERN_INFO "initializing Netlink Socket!\n"); netlink_test(); return 0; } static void netlink_exit(void) { sock_release(nl_sk->socket); printk(KERN_INFO "Good Bye Netlink!\n"); } module_init(my_module_init); module_exit(netlink_exit); MODULE_LICENSE("GPL"); |
引用: |
./nl_recv & Waiting for message from kernel ./nl_recv & Waiting for message from kernel |
引用: |
Received message payload: Greeting from kernel! Received message payload: Greeting from kernel! |