Netlink 是一种特殊的 socket,它是 Linux 所特有的,相似于 BSD 中的AF_ROUTE 但又远比它的功能强大,目前在最新的 Linux 内核(2.6.14)中使用netlink 进行应用与内核通讯的应用不少,包括:路由 daemon(NETLINK_ROUTE),1-wire 子系统(NETLINK_W1),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),socket 监视(NETLINK_INET_DIAG),netfilter 日志(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系统(NETLINK_ISCSI),进程审计(NETLINK_AUDIT),转发信息表查询(NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系统(NETLINK_NETFILTER),IPv6 防火墙(NETLINK_IP6_FW),DECnet 路由信息(NETLINK_DNRTMSG),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)。linux
Netlink 是一种在内核与用户应用间进行双向数据传输的很是好的方式,用户态应用使用标准的 socket API 就可使用 netlink 提供的强大功能,内核态须要使用专门的内核 API 来使用 netlink。缓存
Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具备如下优势:安全
1,为了使用 netlink,用户仅须要在 include/linux/netlink.h 中增长一个新类型的 netlink 协议定义便可, 如 #define NETLINK_MYTEST 17 而后,内核和用户态应用就能够当即经过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用须要增长新的系统调用,ioctl 则须要增长设备或文件, 那须要很多代码,proc 文件系统则须要在 /proc 下添加新的文件或目录,那将使原本就混乱的 /proc 更加混乱。数据结构
问题:异步
增长了netlink协议后,须要从新编译内核吗?仍是仅仅修改了头文件netlink.h就能够了呢?在后期的实验中,须要对这一问题进行验证。socket
2. netlink是一种异步通讯机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不须要等待接收者收到消息,但系统调用与 ioctl 则是同步通讯机制,若是传递的数据太长,将影响调度粒度。ide
异步通讯与同步通讯的区别:看接受者的响应方式。异步通信是不须要接受者当即响应的函数
3.使用 netlink 的内核部分能够采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,并且新的系统调用的实现必须静态地链接到内核中,它没法在模块中实现,使用新系统调用的应用在编译时须要依赖内核。this
能够像编写驱动模块同样的实现方式来实现netlink部分。atom
4.netlink 支持多播,内核模块或应用能够把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在后面的文章中将介绍这一机制的使用。
5.内核可使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
内核主动向用户应用发起数据
6.netlink 使用标准的 socket API,所以很容易使用,但系统调用和 ioctl则须要专门的培训才能使用。
用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket,查询手册页能够了解这些函数的使用细节,本文只是讲解使用 netlink 的用户应该如何使用这些函数。注意,使用 netlink 的应用必须包含头文件 linux/netlink.h。固然 socket 须要的头文件也必不可少,sys/socket.h。
为了建立一个 netlink socket,用户须要使用以下参数调用 socket():
socket(AF_NETLINK, SOCK_RAW, netlink_type)
第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,如前面讲的用户自定义协议类型NETLINK_MYTEST, NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,所以,用户能够直接使用它,而没必要再添加新的协议类型。内核预约义的协议类型有:
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_W1 1 /* 1-wire subsystem */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Firewalling hook */
#define NETLINK_INET_DIAG 4 /* INET socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
对于每个netlink协议类型,能够有多达 32多播组,每个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅须要一次系统调用,于是对于须要多拨消息的应用而言,大大地下降了系统调用的次数。
函数 bind() 用于把一个打开的 netlink socket 与 netlink 源 socket 地址绑定在一块儿。
补充一下bind()函数的意义:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to
it. bind() assigns the address specified to by addr to the socket referred to by the file descriptor sockfd.
addrlen specifies the size, in bytes, of the address structure pointed to by addr. Traditionally, this operation
is called “assigning a name to a socket”.
It is normally necessary to assign a local address using bind() before a SOCK_STREAM socket may receive connec‐
tions (see accept(2)).
可见bind()的另外一个意思是为所建立的socket分配一个名字(相应与地址的名字)
netlink socket 的地址结构以下:
struct sockaddr_nl { sa_family_t nl_family; unsigned short nl_pad; __u32 nl_pid; __u32 nl_groups; };
字段 nl_family 必须设置为 AF_NETLINK 或着 PF_NETLINK,
字段 nl_pad 当前没有使用,所以要老是设置为 0,
字段 nl_pid 为接收或发送消息的进程的 ID,若是但愿内核处理消息或多播消息,就把该字段设置为 0,不然设置为处理消息的进程 ID。
字段 nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,若是设置为 0,表示调用者不加入任何多播组。
传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这至关于 netlink socket 的本地地址。可是,对于一个进程的多个线程使用 netlink socket 的状况,字段 nl_pid 则能够设置为其它的值,如:
pthread_self() << 16 | getpid();
所以字段 nl_pid 实际上未必是进程 ID,它只是用于区分不一样的接收者或发送者的一个标识,用户能够根据本身须要设置该字段。函数 bind 的调用方式以下:
bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));
fd为前面的 socket 调用返回的文件描述符,参数 nladdr 为 struct sockaddr_nl 类型的地址。 为了发送一个 netlink 消息给内核或其余用户态应用,须要填充目标 netlink socket 地址 ,此时,字段 nl_pid 和 nl_groups 分别表示接收消息者的进程 ID 与多播组。若是字段 nl_pid 设置为 0,表示消息接收者为内核或多播组,若是 nl_groups为 0,表示该消息为单播消息,不然表示多播消息。 使用函数 sendmsg 发送 netlink 消息时还须要引用结构 struct msghdr、struct nlmsghdr 和 struct iovec,
这里有两个消息头
结构 struct msghdr 需以下设置:
struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr);
其中 nladdr 为消息接收者的 netlink 地址。
struct nlmsghdr 为 netlink socket 本身的消息头,这用于多路复用和多路分解 netlink 定义的全部协议类型以及其它一些控制,netlink 的内核实现将利用这个消息头来多路复用和多路分解已经其它的一些控制,所以它也被称为netlink 控制块。所以,应用在发送 netlink 消息时必须提供该消息头。
内核会利用nlmsghdr头路由netlink消息包,
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 */
};
字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,
字段 nlmsg_type 用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,所以大部分状况下设置为 0,
字段 nlmsg_flags 用于设置消息标志,可用的标志包括:
/* Flags values */ #define NLM_F_REQUEST 1 /* It is request message. */ #define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */ #define NLM_F_ACK 4 /* Reply with ack, with zero or error code */ #define NLM_F_ECHO 8 /* Echo this request */ /* Modifiers to GET request */ #define NLM_F_ROOT 0x100 /* specify tree root */ #define NLM_F_MATCH 0x200 /* return all matching */ #define NLM_F_ATOMIC 0x400 /* atomic GET */ #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) /* Modifiers to NEW request */ #define NLM_F_REPLACE 0x100 /* Override existing */ #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */
标志NLM_F_REQUEST用于表示消息是一个请求,全部应用首先发起的消息都应设置该标志。
标志NLM_F_MULTI 用于指示该消息是一个多部分消息的一部分,后续的消息能够经过宏NLMSG_NEXT来得到。
宏NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID能够把请求与响应关联起来。
标志NLM_F_ECHO表示该消息是相关的一个包的回传。
标志NLM_F_ROOT 被许多 netlink 协议的各类数据获取操做使用,该标志指示被请求的数据表应当总体返回用户应用,而不是一个条目一个条目地返回。有该标志的请求一般致使响应消息设置NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,所以,须要在字段 nlmsg_type 中指定协议类型。
标志 NLM_F_MATCH 表示该协议特定的请求只须要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。
标志 NLM_F_ATOMIC 指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。
标志 NLM_F_DUMP 未实现。
标志 NLM_F_REPLACE 用于取代在数据表中的现有条目。
标志 NLM_F_EXCL_ 用于和 CREATE 和 APPEND 配合使用,若是条目已经存在,将失败。
标志 NLM_F_CREATE 指示应当在指定的表中建立一个条目。
标志 NLM_F_APPEND 指示在表末尾添加新的条目。
内核须要读取和修改这些标志,对于通常的使用,用户把它设置为 0 就能够,只是一些高级应用(如 netfilter 和路由 daemon 须要它进行一些复杂的操做),
字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。下面是一个示例:
#define MAX_MSGSIZE 1024 char buffer[] = "An example message"; struct nlmsghdr nlhdr; nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE)); strcpy(NLMSG_DATA(nlhdr),buffer); nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer)); nlhdr->nlmsg_pid = getpid(); /* self pid */ nlhdr->nlmsg_flags = 0;
结构 struct iovec 用于把多个消息经过一次系统调用来发送,下面是该结构使用示例:
struct iovec iov; iov.iov_base = (void *)nlhdr; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1;
在完成以上步骤后,消息就能够经过下面语句直接发送:
sendmsg(fd, &msg, 0);
应用接收消息时须要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,而后填充消息头,添完后就能够直接调用函数 recvmsg() 来接收。
#define MAX_NL_MSG_LEN 1024 struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; struct nlmsghdr * nlhdr; nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN); iov.iov_base = (void *)nlhdr; 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);
注意:fd为socket调用打开的netlink socket描述符。
在消息接收后,nlhdr指向接收到的消息的消息头,nladdr保存了接收到的消息的目标地址,宏NLMSG_DATA(nlhdr)返回指向消息的数据部分的指针。
在linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:
#define NLMSG_ALIGNTO 4 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
宏NLMSG_ALIGN(len)用于获得不小于len且字节对齐的最小数值。
#define NLMSG_LENGTH(len) ((len)+
NLMSG_ALIGN(sizeof(struct nlmsghdr)))
宏NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它通常用于分配消息缓存。
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存。
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时须要使用该宏。
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
宏NLMSG_NEXT(nlh,len)用于获得下一个消息的首地址,同时len也减小为剩余消息的总长度,该宏通常在一个消息被分红几个部分发送或接收时使用。
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len <= (len))
宏NLMSG_OK(nlh,len)用于判断消息是否有len这么长。
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度。
函数close用于关闭打开的netlink socket。
在发送的时候只使用了nlmsghdr消息头,可是在接收的时候使用了struct msghdr msg;
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/socket.h> 4 #include <string.h> 5 #include <linux/netlink.h> 6 7 #define NETLINK_TEST 17 8 #define MSG_LEN 100 9 10 struct msg_to_kernel 11 { 12 struct nlmsghdr hdr; 13 char data[MSG_LEN]; 14 }; 15 struct u_packet_info 16 { 17 struct nlmsghdr hdr; 18 char msg[MSG_LEN]; 19 }; 20 21 int main(int argc, char* argv[]) 22 { 23 char *data = "This message is from eric's space"; 24 //初始化 25 struct sockaddr_nl local; 26 struct sockaddr_nl kpeer; 27 int skfd, ret, kpeerlen = sizeof(struct sockaddr_nl); 28 struct nlmsghdr *message; 29 struct u_packet_info info; 30 char *retval; 31 message = (struct nlmsghdr *)malloc(1); 32 33 skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST); 34 if(skfd < 0){ 35 printf("can not create a netlink socket\n"); 36 return -1; 37 } 38 memset(&local, 0, sizeof(local)); 39 local.nl_family = AF_NETLINK; 40 local.nl_pid = getpid(); 41 local.nl_groups = 0; 42 if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){ 43 printf("bind() error\n"); 44 return -1; 45 } 46 memset(&kpeer, 0, sizeof(kpeer)); 47 kpeer.nl_family = AF_NETLINK; 48 kpeer.nl_pid = 0; 49 kpeer.nl_groups = 0; 50 51 memset(message, '\0', sizeof(struct nlmsghdr)); 52 message->nlmsg_len = NLMSG_SPACE(strlen(data)); 53 message->nlmsg_flags = 0; 54 message->nlmsg_type = 0; 55 message->nlmsg_seq = 0; 56 message->nlmsg_pid = local.nl_pid; 57 58 retval = memcpy(NLMSG_DATA(message), data, strlen(data)); 59 60 printf("message sendto kernel are:%s, len:%d\n", (char *)NLMSG_DATA(message), message->nlmsg_len); 61 ret = sendto(skfd, message, message->nlmsg_len, 0,(struct sockaddr *)&kpeer, sizeof(kpeer)); 62 if(!ret){ 63 perror("send pid:"); 64 exit(-1); 65 } 66 67 //接受内核态确认信息 68 ret = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen); 69 if(!ret){ 70 perror("recv form kerner:"); 71 exit(-1); 72 } 73 74 printf("message receive from kernel:%s\n",(char *)info.msg); 75 //内核和用户进行通讯 76 77 close(skfd); 78 return 0; 79 }
如下是内核中关于netlink的具体实现过程:
1 #include <linux/kernel.h> 2 #include <linux/module.h> 3 #include <linux/types.h> 4 #include <linux/sched.h> 5 #include <net/sock.h> 6 #include <linux/netlink.h> 7 8 #define NETLINK_TEST 17 9 struct { 10 __u32 pid; 11 }user_process; 12 13 static struct sock *netlinkfd = NULL; 14 15 int send_to_user(char *info) //发送到用户空间 16 { 17 int size; 18 struct sk_buff *skb; 19 unsigned char *old_tail; 20 struct nlmsghdr *nlh; //报文头 21 22 int retval; 23 24 size = NLMSG_SPACE(strlen(info)); //报文大小 25 skb = alloc_skb(size, GFP_ATOMIC); //分配一个新的套接字缓存,使用GFP_ATOMIC标志进程不>会被置为睡眠 26 27 //初始化一个netlink消息首部 28 nlh = nlmsg_put(skb, 0, 0, 0, NLMSG_SPACE(strlen(info))-sizeof(struct nlmsghdr), 0); 29 old_tail = skb->tail; 30 memcpy(NLMSG_DATA(nlh), info, strlen(info)); //填充数据区 31 nlh->nlmsg_len = skb->tail - old_tail; //设置消息长度 32 33 //设置控制字段 34 NETLINK_CB(skb).pid = 0; 35 NETLINK_CB(skb).dst_group = 0; 36 37 printk(KERN_DEBUG "[kernel space] skb->data:%s\n", (char *)NLMSG_DATA((struct nlmsghdr *)skb->data)); 38 39 //发送数据 40 retval = netlink_unicast(netlinkfd, skb, user_process.pid, MSG_DONTWAIT); 41 printk(KERN_DEBUG "[kernel space] netlink_unicast return: %d\n", retval); 42 return 0; 43 } 44 45 void kernel_receive(struct sk_buff *__skb) //内核从用户空间接收数据 46 { 47 struct sk_buff *skb; 48 struct nlmsghdr *nlh = NULL; 49 50 char *data = "This is eric's test message from kernel"; 51 52 printk(KERN_DEBUG "[kernel space] begin kernel_receive\n"); 53 skb = skb_get(__skb); 54 55 if(skb->len >= sizeof(struct nlmsghdr)){ 56 nlh = (struct nlmsghdr *)skb->data; 57 if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) 58 && (__skb->len >= nlh->nlmsg_len)){ 59 user_process.pid = nlh->nlmsg_pid; 60 printk(KERN_DEBUG "[kernel space] data receive from user are:%s\n", (char *)NLMSG_DATA(nlh)); 61 printk(KERN_DEBUG "[kernel space] user_pid:%d\n", user_process.pid); 62 send_to_user(data); 63 } 64 }else{ 65 printk(KERN_DEBUG "[kernel space] data receive from user are:%s\n",(char *)NLMSG_DATA(nlmsg_hdr(__skb))); 66 send_to_user(data); 67 } 68 69 kfree_skb(skb); 70 } 71 72 int __init test_netlink_init(void) 73 { 74 75 netlinkfd = netlink_kernel_create(&init_net, NETLINK_TEST, 0, kernel_receive, NULL, THIS_MODULE); 76 if(!netlinkfd){ 77 printk(KERN_ERR "can not create a netlink socket\n"); 78 return -1; 79 } 80 return 0; 81 } 82 83 void __exit test_netlink_exit(void) 84 { 85 sock_release(netlinkfd->sk_socket); 86 printk(KERN_DEBUG "test_netlink_exit!!\n"); 87 } 88 89 module_init(test_netlink_init); 90 module_exit(test_netlink_exit); 91 MODULE_LICENSE("GPL"); 92 MODULE_AUTHOR("eric.hu");
netlink的内核实如今.c文件net/core/af_netlink.c中,内核模块要想使用netlink,也必须包含头文件linux/netlink.h。内核使用netlink须要专门的API,这彻底不一样于用户态应用对netlink的使用。若是用户须要增长新的netlink协议类型,必须经过修改linux/netlink.h来实现,固然,目前的netlink实现已经包含了一个通用的协议类型NETLINK_GENERIC以方便用户使用,用户能够直接使用它而没必要增长新的协议类型。前面讲到,为了增长新的netlink协议类型,用户仅需增长以下定义到linux/netlink.h就能够:
#define NETLINK_MYTEST 17
只要增长这个定义以后,用户就能够在内核的任何地方引用该协议。
在内核中,为了建立一个netlink socket用户须要调用以下函数:
struct sock *netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));
参数unit表示netlink协议类型,如NETLINK_MYTEST,
参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个netlink socket时,该input函数指针就会被引用。函数指针input的参数sk实际上就是函数netlink_kernel_create返回的struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用建立的socket在内核中也会有一个struct sock结构来表示。下面是一个input函数的示例:
void input (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *data = NULL; while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; data = NLMSG_DATA(nlh); /* process netlink message with header pointed by * nlh and data pointed by data */ } }
函数input()会在发送进程执行sendmsg()时被调用,这样处理消息比较及时,可是,若是消息特别长时,这样处理将增长系统调用sendmsg()的执行时间,对于这种状况,能够定义一个内核线程专门负责消息接收,而函数input的工做只是唤醒该内核线程,这样sendmsg将很快返回。
函数skb = skb_dequeue(&sk->receive_queue)用于取得socket sk的接收队列上的消息,返回为一个struct sk_buff的结构,skb->data指向实际的netlink消息。
函数skb_recv_datagram(nl_sk)也用于在netlink socket nl_sk上接收消息,与skb_dequeue的不一样指出是,若是socket的接收队列上没有消息,它将致使调用进程睡眠在等待队列nl_sk->sk_sleep,所以它必须在进程上下文使用,刚才讲的内核线程就能够采用这种方式来接收消息。
下面的函数input就是这种使用的示例:
void input (struct sock *sk, int len) { wake_up_interruptible(sk->sk_sleep); }
当内核中发送netlink消息时,也须要设置目标地址与源地址,并且内核中消息是经过struct sk_buff来管理的, linux/netlink.h中定义了一个宏:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
来方便消息的地址设置。下面是一个消息地址设置的例子:
NETLINK_CB(skb).pid = 0; NETLINK_CB(skb).dst_pid = 0; NETLINK_CB(skb).dst_group = 1;
字段pid表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,若是目标为组或内核,它设置为 0,不然 dst_group 表示目标组地址,若是它目标为某一进程或内核,dst_group 应当设置为 0。
在内核中,模块调用函数 netlink_unicast 来发送单播消息:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
参数sk为函数netlink_kernel_create()返回的socket,参数skb存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块, 参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,若是为1,该函数将在没有接收缓存可利用时当即返回,而若是为0,该函数在没有接收缓存可利用时睡眠。
内核模块或子系统也可使用函数netlink_broadcast来发送广播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);
前面的三个参数与netlink_unicast相同,参数group为接收消息的多播组,该参数的每个表明一个多播组,所以若是发送给多个多播组,就把该参数设置为多个多播组组ID的位或。参数allocation为内核内存分配类型,通常地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不能够睡眠),而GFP_KERNEL用于非原子上下文。
在内核中使用函数sock_release来释放函数netlink_kernel_create()建立的netlink socket:
void sock_release(struct socket * sock);
注意函数netlink_kernel_create()返回的类型为struct sock,所以函数sock_release应该这种调用:
sock_release(sk->sk_socket);
sk为函数netlink_kernel_create()的返回值。
在源代码包中给出了一个使用 netlink 的示例,它包括一个内核模块 netlink-exam-kern.c 和两个应用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。内核模块必须先插入到内核,而后在一个终端上运行用户态接收程序,在另外一个终端上运行用户态发送程序,发送程序读取参数指定的文本文件并把它做为 netlink 消息的内容发送给内核模块,内核模块接受该消息保存到内核缓存中,它也经过proc接口出口到 procfs,所以用户也可以经过 /proc/netlink_exam_buffer 看到所有的内容,同时内核也把该消息发送给用户态接收程序,用户态接收程序将把接收到的内容输出到屏幕上。