赞
踩
netlink作为一种用户空间和内核空间通信的机制已经有一定年头了,它不光为了内核和用户通信,还可以作为IPC机制进行进程间通信。其实netlink定义了一个框架,人们可以基于这个框架用它来做可以做的任何事情,linux中不乏这些类似的好的框架。它们的共同点就是内核并不管它们能做什么,然而它们真的很强大,往往可以做到的事情很多,这就是内核不问策略只管实现机制,所有策略让用户实现,netlink框架就是用来传递数据的,内核只知道它可以传递数据而不知道为何要传递这些数据也不管这些数据是什么。你甚至可以将它用于真正的网络而不仅仅限于本机,这些都是可以的,它也用到了sk_buff结构体,和网络套接字一样,更好的事情是它并没有触及sk_buff里面的标准字段,而仅仅用了一个扩展的cb字段,cb在sk_buff里面的定义是char cb[40];在netlink模块里面NETLINK_CB宏就是取cb字段的,也就是netlink所用的私有字段,这样的话你就可以用netlink向任何执行实体传输任何数据了,不限于本机。
关于用户空间的netlink套接字很简单,就和传统的网络套接字一样一样的,只不过修改了一些参数罢了。如下:
sd = socket(AF_NETLINK, SOCK_RAW,NETLINK_GENERIC);
就是建立一个用户netlink套接字。之后的bind也是很简单,注意数据结构的意义就是了。这里就不说了,下面详细说一下内核的netlink实现。内核里面建立一个netlink套接字需要如下调用:
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
{
struct socket *sock;
struct sock *sk;
if (unit<0 || unit>=MAX_LINKS)
return NULL;
if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
return NULL;
if (netlink_create(sock, unit) < 0) {
sock_release(sock);
return NULL;
}
sk = sock->sk;
sk->sk_data_ready = netlink_data_ready; //之所以将sk_data_ready设为新的函数而不用默认的是因为为了实现一些用户策略,比如可以传入自己的input函数,待到有数据的时候自行处理。
if (input)
nlk_sk(sk)->data_ready = input;
netlink_insert(sk, 0);
return sk;
}
注意该函数的参数input是个回调函数,在有数据的时候内核会调用它。另外sk_data_ready回调函数是套接字标准中定义的,不管什么套接字都有sk_data_ready回调机制。在input中,你可以直接处理收到的数据,也可以不处理,在大量数据传输的情况下,在input中处理是不明智的,正确的方式应该是建立一个内核线程专门接收数据,没有数据的时候该内核线程睡眠,一旦有了数据,input回调函数唤醒这个内核线程就是了。
static void netlink_data_ready(struct sock *sk, int len)
{
struct netlink_opt *nlk = nlk_sk(sk);
if (nlk->data_ready)
nlk->data_ready(sk, len); //这里调用的回调函数就是内核netlink套接字建立的时候传入的那个函数。
netlink_rcv_wake(sk); //告知别的进程该sock上刚完成了一次接收,可能会腾出地方以便接收新的skb
}
static inline void netlink_rcv_wake(struct sock *sk)
{
struct netlink_opt *nlk = nlk_sk(sk);
if (!skb_queue_len(&sk->sk_receive_queue))
clear_bit(0, &nlk->state);
if (!test_bit(0, &nlk->state))
wake_up_interruptible(&nlk->wait); //唤醒可能等待发送的进程
}
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
{
struct sock *sk;
int err;
long timeo;
netlink_trim(skb, gfp_any());
timeo = sock_sndtimeo(ssk, nonblock);
retry:
sk = netlink_getsockbypid(ssk, pid);
...
err = netlink_attachskb(sk, skb, nonblock, timeo); //将sock和sk_buff绑定在一起,在netlink中套接字和skb的绑定与解绑定是很频繁的。
if (err == 1)
goto retry;
if (err)
return err;
return netlink_sendskb(sk, skb, ssk->sk_protocol); //在套接字sk上传输这个skb,其实就是将这个skb排入了该sk的接收队列的后头。
}
int netlink_attachskb(struct sock *sk, struct sk_buff *skb, int nonblock, long timeo)
{//这个函数将一个sk_buff给了一个套接字sock,也就是skb与sock的绑定,在绑定之前有很多工作要做。
struct netlink_opt *nlk;
nlk = nlk_sk(sk);
if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) {
DECLARE_WAITQUEUE(wait, current);
...
__set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&nlk->wait, &wait);
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) &&
!sock_flag(sk, SOCK_DEAD)) //如果此时这个sock不能接受这个sk,那么就要等待了,正好等在nlk->wait上,待到和该sock相关的进程在netlink_rcv_wake中唤醒之,说明可以过继skb了。
timeo = schedule_timeout(timeo);
__set_current_state(TASK_RUNNING);
remove_wait_queue(&nlk->wait, &wait);
sock_put(sk);
if (signal_pending(current)) {
kfree_skb(skb);
return sock_intr_errno(timeo);
}
return 1;
}
skb_orphan(skb);
skb_set_owner_r(skb, sk); //该sock正式接受这个sk_buff
return 0;
}
那么谁会调用netlink_attachskb呢?这是显而易见的,在发送的时候,要把一个要发送的消息初始化成一个sk_buff结构体,但是这个skb归谁所有呢?确定绑定的主客双方的过程就是绑定,也就是上面的函数做的事。在netlink的消息发送过程中的第一步就是sock和sk_buff的绑定,于是调用上述绑定函数的就是netlink_sendmsg中调用的netlink_unicast,也就是单播,单播的意思就是只发给一个sock而不是多个,于是要做的就是找到接收此skb的sock,netlink_unicast的参数dst_pid为确定目标sock提供了方向,在netlink_unicast中(见上)正是通过这个dst_pid找到了目标sock。
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len)
{
...
err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
out:
return err;
}
发送是一个主动的过程,因此需要主动寻找目标sock,然后把根据要发送的消息初始化好的sk_buff找机会排入目标sock的接收队列就完事了,如此看来的话,接收更是一个简单的过程了,它只要等着接收就可以了,纯粹就是一个被动的过程了,在对应的netlink_recvmsg中循环接收,没有数据时就睡在一个sock->sk_sleep队列上就是了,一旦有数据过来,该接收进程就被唤醒,具体过程就是,当发送方调用netlink_sendmsg时,后者调用netlink_unicast,然后它进一步调用netlink_sendskb,这个netlink_sendskb最后将sk_buff排入目标接收sock的接收队列后调用目标sock的sk_data_ready,而这个sk_data_ready对于用户空间的netlink就是sock_def_readable,它会唤醒睡眠了sk->sk_sleep上的接收sock进程,而对于内核netlink套接字,其sk_data_ready将会是netlink_data_ready,就是上面所说的那个函数。这个netlink_data_ready里面调用了一个程序设计者传入内核的data_ready回调函数从而可以实现用户策略,再次引入了机制和策略的分离。在netlink_rcv_wake中会判断当前接收队列sk->sk_receive_queue是否已经空了,空了的话证明接收已经完成,这种情况下就要唤醒等待排入队列新skb的发送进程,也就是调用:
if (!skb_queue_len(&sk->sk_receive_queue)) //是否接收已经完成
clear_bit(0, &nlk->state); //完成的话state清位
if (!test_bit(0, &nlk->state)) //如果清位说明接收完成,那么就唤醒等待发送的进程,这个接收进程可以继续接收了。 其实只有在溢出的情况下才会置位state
wake_up_interruptible(&nlk->wait);
以上就是简要的netlink发送和接收过程,netlink之所以如此简洁和高效,靠的就是它的一个巧妙的数据组织形式,内核要在接收到用户netlink套接字数据时快速确定策略和快速定位发送的目标都是问题,linux的netlink实现了一个表结构,其实是一个链结构,只要有netlink形成,不管是内核的还是用户空间的,都要将netlink套接字本身和它的信息一并插入到这个链表结构中,然后在发送时定位目标的时候,只要遍历这个表就可以了,插入代码如下:
static int netlink_insert(struct sock *sk, u32 pid)
{
int err = -EADDRINUSE;
struct sock *osk;
struct hlist_node *node;
netlink_table_grab();
sk_for_each(osk, node, &nl_table[sk->sk_protocol]) {
if (nlk_sk(osk)->pid == pid)
break;
}
if (!node) {
err = -EBUSY;
if (nlk_sk(sk)->pid == 0) {
nlk_sk(sk)->pid = pid;
sk_add_node(sk, &nl_table[sk->sk_protocol]);
err = 0;
}
}
netlink_table_ungrab();
return err;
}
相应的,查找代码如下:
static __inline__ struct sock *netlink_lookup(int protocol, u32 pid)
{
struct sock *sk;
struct hlist_node *node;
read_lock(&nl_table_lock);
sk_for_each(sk, node, &nl_table[protocol]) {
if (nlk_sk(sk)->pid == pid) {
sock_hold(sk);
goto found;
}
}
sk = NULL;
found:
read_unlock(&nl_table_lock);
return sk;
}
最后看看netlink的广播是如何实现的,其实这里的广播并没有用什么网络协议的知识,和那也没有关系,所谓的广播其实就是将数据发给所有的可以接收的netlink套接字而已。其实就是遍历这个nl_table表,然后抽出其中每一个netlink套接字,经检验可以发送时,就将数据发给它们,实现起来很简单。
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, int allocation)
{
struct sock *sk;
struct hlist_node *node;
struct sk_buff *skb2 = NULL;
int protocol = ssk->sk_protocol;
int failure = 0, delivered = 0;
netlink_trim(skb, allocation);
netlink_lock_table();
sk_for_each(sk, node, &nl_table[protocol]) { //遍历整个netlink套接字链表
struct netlink_opt *nlk = nlk_sk(sk);
if (ssk == sk) //不再向自己发送
continue;
...//简单判断并且递增该sock结构的引用计数,因为下面要用它了。
} else if (netlink_broadcast_deliver(sk, skb2)) {
...
}
static __inline__ int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
{
struct netlink_opt *nlk = nlk_sk(sk);
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf && !test_bit(0, &nlk->state)) {
skb_orphan(skb);
skb_set_owner_r(skb, sk); //将skb直接过继给需要接收的sock结构
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_data_ready(sk, skb->len); //告知需要接收的sock结构有数据到来
return 0;
}
return -1;
}
其实,linux的很多机制都是netlink套接字实现的,比如udev守护进程,netlink机制很好很强大!
原文链接:http://blog.csdn.net/dog250/article/details/5303430
==========================================================================================
==========================================================================================
【用户空间】
Netlink套接字可以使用标准的套接字APIs来创建socket( )、关闭close( )、接收recvmsg( )或者发送sendto( )消息。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
socket函数返回的套接字,可以交给bind等函数调用:
static int skfd;
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
if(skfd < 0)
{
printf("can not create a netlink socket\n");
exit(0);
}
bind函数需要绑定协议地址,netlink的socket地址使用struct sockaddr_nl结构描述:
struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
};
成员nl_family为协议簇AF_NETLINK,成员nl_pad当前没有使用,因此要总是设置为0,成员nl_pid为接收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。成员nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为0,表示调用者不加入任何多播组:
struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid(); /*设置pid为自己的pid值*/
local.nl_groups = 0;
/*绑定套接字*/
if(bind(skfd, (struct sockaddr*)&local, sizeof(local)) != 0)
{
printf("bind() error\n");
return -1;
}
用户空间可以调用send函数簇向内核发送消息,如sendto、sendmsg等,同样地,也可以使用struct sockaddr_nl来描述一个对端地址,以待send函数来调用,与本地地址稍不同的是,因为对端为内核,所以nl_pid成员需要设置为0:
struct sockaddr_nl kpeer;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
另一个问题就是发内核发送的消息的组成,使用我们发送一个IP网络数据包的话,则数据包结构为“IP包头+IP数据”,同样地,netlink的消息结构 是“netlink消息头部+数据”。Netlink消息头部使用struct nlmsghdr结构来描述:
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指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,一般地,我们使用netlink提供的宏NLMSG_LENGTH 来计算这个长度,仅需向NLMSG_LENGTH宏提供要发送的数据的长度,它会自动计算对齐后的总长度:
/*计算包含报头的数据报长度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*字节对齐*/
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1)& ~(NLMSG_ALIGNTO-1) )
后面还可以看到很多netlink提供的宏,这些宏可以为我们编写netlink宏提供很大的方便。
字段nlmsg_type用于应用内部定义消息的类型,它对netlink内核实现是透明的,因此大部分情况下设置为0,字段nlmsg_flags用于 设置消息标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。
struct msg_to_kernel /*自定义消息*/
{
struct nlmsghdr hdr;
struct nlmsgdata data;
};
struct nlmsgdata /*消息实际数据*/
{
__u32 uaddr;
__u16 uport;
};
struct msg_to_kernel message;
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgdata));
/*计算消息,数据长度为结构nlmsgdata的大小*/
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = MYNL_U_PID; /*设置自定义消息类型*/
message.hdr.nlmsg_pid = local.nl_pid; /*设置发送者的PID*/
这样,有了本地地址、对端地址和发送的数据,就可以调用发送函数将消息发送给内核了:
/*发送一个请求*/
sendto(skfd, &message, message.hdr.nlmsg_len, 0,(struct sockaddr*)&kpeer, sizeof(kpeer));
当发送完请求后,就可以调用recv函数簇从内核接收数据了,接收到的数据包含了netlink消息首部和要传输的数据:
/*接收的数据包含了netlink消息首部和自定义数据结构*/
struct u_packet_info
{
struct nlmsghdr hdr;
struct packet_info icmp_info;
};
struct u_packet_info info;
while(1)
{
kpeerlen = sizeof(struct sockaddr_nl);
/*接收内核空间返回的数据*/
rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen);
/*处理接收到的数据*/
……
}
同样地,函数close用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int中处理:
/*这个信号函数,处理一些程序退出时的动作*/
static void sig_int(int signo)
{
struct sockaddr_nl kpeer;
struct msg_to_kernel message;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0);
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = MYNL_CLOSE;
message.hdr.nlmsg_pid = getpid();
/*向内核发送一个消息,由nlmsg_type表明,应用程序将关闭*/
sendto(skfd, &message, message.hdr.nlmsg_len, 0, (struct sockaddr *)(&kpeer), sizeof(kpeer));
close(skfd);
exit(0);
}
这个结束函数中,向内核发送一个退出的消息,然后调用close函数关闭netlink套接字,退出程序。
【内核空间】
与应用程序内核,内核空间也主要完成三件工作:
+ 创建netlink套接字
+ 接收处理用户空间发送的数据
+ 发送数据至用户空间
API函数netlink_kernel_create用于创建一个netlinksocket,同时,注册一个回调函数,用于接收处理用户空间的消息:
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结构来表示。
static int __init init(void)
{
rwlock_init(&user_proc.lock); /*初始化读写锁*/
/*创建一个netlink socket,协议类型是自定义的NETLINK_MYTEST,kernel_reveive为接受处理函数*/
nlfd = netlink_kernel_create(NETLINK_MYTEST, kernel_receive);
if(!nlfd) /*创建失败*/
{
printk("can not create a netlink socket\n");
return -1;
}
/*注册一个Netfilter 钩子*/
return nf_register_hook(&imp2_ops);
}
module_init(init);
用户空间向内核发送了两种自定义消息类型:MYNL_U_PID和MYNL_CLOSE,分别是发送消息和关闭。kernel_receive函数分别处理这两种消息:
DECLARE_MUTEX(receive_sem); /*初始化信号量*/
static void kernel_receive(struct sock *sk, int len)
{
do{
struct sk_buff *skb;
if(down_trylock(&receive_sem)) /*获取信号量*/
return;
/*从接收队列中取得skb,然后进行一些基本的长度的合法性校验*/
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
{
{
struct nlmsghdr *nlh = NULL;
struct nlmsgdata *nld = NULL;
if(skb->len >= sizeof(struct nlmsghdr))
{
nlh = (struct nlmsghdr *)skb->data;/*获取数据中的nlmsghdr 结构的报头*/
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))&& (skb->len >= nlh->nlmsg_len))
{
/*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID的保存,即为内核保存“把消息发送给谁”*/
if(nlh->nlmsg_type == MYNL_U_PID) /*发送消息*/
{
write_lock_bh(&user_proc.pid);
user_proc.pid = nlh->nlmsg_pid;
user_proc.nldata.uaddr=nld->uaddr;
user_proc.nldata.uport=nld->uport;
write_unlock_bh(&user_proc.pid);
}
else if(nlh->nlmsg_type == MYNL_CLOSE) /*应用程序关闭*/
{
write_lock_bh(&user_proc.pid);
if(nlh->nlmsg_pid == user_proc.pid)
user_proc.pid = 0;
write_unlock_bh(&user_proc.pid);
}
}
}
}
kfree_skb(skb);
}
up(&receive_sem); /*返回信号量*/
}while(nlfd && nlfd->receive_queue.qlen);
}
因为内核模块可能同时被多个进程同时调用,所以函数中使用了信号量和锁来进行互斥。skb = skb_dequeue(&sk->receive_queue)用于取得socket sk的接收队列上的消息,返回为一个struct sk_buff的结构,skb->data指向实际的netlink消息。
程序中注册了一个Netfilter钩子,钩子函数是get_icmp,它截获ICMP数据包,然后调用send_to_user函数将数据发送给应用空 间进程。发送的数据是info结构变量,它是struct packet_info结构,这个结构包含了来源/目的地址两个成员。
send_to_user 用于将数据发送给用户空间进程,发送调用的是API函数netlink_unicast 完成的:
netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
参数sk为函数netlink_kernel_create()返回的套接字,参数skb存放待发送的消息,它的data字段指向要发送的netlink 消息结构,而skb的控制块保存了消息的地址信息,参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将 在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。
向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段,控制字段包含了内核发送netlink消息时,需要设置的目标地址与源地址,内核中消息是通过sk_buff来管理的, linux/netlink.h中定义了NETLINK_CB宏来方便消息的地址设置:
#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。
static int send_to_user(struct packet_info *info)
{
int ret;
int size;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct packet_info *packet;
/*计算消息总长:消息首部加上数据加度*/
size = NLMSG_SPACE(sizeof(*info));
/*分配一个新的套接字缓存*/
skb = alloc_skb(size, GFP_ATOMIC);
old_tail = skb->tail;
/*初始化一个netlink消息首部*/
nlh = NLMSG_PUT(skb, 0, 0, NL_K_MSG, size-sizeof(*nlh));
/*跳过消息首部,指向数据区*/
packet = NLMSG_DATA(nlh);
/*初始化数据区*/
memset(packet, 0, sizeof(struct packet_info));
/*填充待发送的数据*/
packet->src = info->src;
packet->dest = info->dest;
/*计算skb两次长度之差,即netlink的长度总和*/
nlh->nlmsg_len = skb->tail - old_tail;
/*设置控制字段*/
NETLINK_CB(skb).dst_groups = 0;
/*发送数据*/
read_lock_bh(&user_proc.lock);
ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
read_unlock_bh(&user_proc.lock);
}
函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast函数把数据发送出去。函数中调用 了netlink的一个重要的宏NLMSG_PUT,它用于初始化netlink 消息首部:
#define NLMSG_PUT(skb, pid, seq, type, len) \
({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; \
__nlmsg_put(skb, pid, seq, type, len); })
static __inline__ struct nlmsghdr *
__nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len)
{
struct nlmsghdr *nlh;
int size = NLMSG_LENGTH(len);
nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size));
nlh->nlmsg_type = type;
nlh->nlmsg_len = size;
nlh->nlmsg_flags = 0;
nlh->nlmsg_pid = pid;
nlh->nlmsg_seq = seq;
return nlh;
}
这个宏一个需要注意的地方是调用了nlmsg_failure标签,所以在程序中应该定义这个标签。
在内核中使用函数sock_release来释放函数netlink_kernel_create()创建的netlink socket:
void sock_release(struct socket * sock);
程序在退出模块中释放netlink sockets和netfilter hook:
static void __exit fini(void)
{
if(nlfd)
{
sock_release(nlfd->socket);
/*释放netlink socket*/
}
nf_unregister_hook(&mynl_ops);
/*撤锁netfilter 钩子*/
}
【Netlink优点】
Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:
1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17。然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。
2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在 后面的文章中将介绍这一机制的使用。
5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。
[参考文献] http://bbs.chinaunix.net/viewthread.php?tid=822500&extra=page%3D1%26filter%3Ddigest
==================================================================================================
==================================================================================================
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。