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=MAX_LINKS)
return NULL;
if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
return NULL;
if (netlink_create(sock, unit) 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_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机制很好很强大!
分享到:
相关推荐
"支持多线程并发与消息异步处理的Linux Netlink通信机制研究" Linux操作系统中,Netlink是一种流行的进程间通信机制,用于实现用户空间和内核空间之间的通信。然而,在多线程程序中使用Netlink时,仍然存在一些问题...
用户程序源码 eknetlink.c -内核程序源码 netlink提供了一种很好很强大的的用户与内核之间的通讯机制,本文通过静态的新增一个netlink协议类型,并使用这个新的netlink类型实现用户态和内核态的双向通讯,对linux的...
netlink提供了一种很好很强大的的用户与内核之间的通讯机制,本文通过静态的新增一个netlink协议类型,并使用这个新的netlink类型实现用户态和内核态的双向通讯,对linux的netlink通讯方式有了一个初步的认识。...
本文主要介绍linux中内核态与用户态通信的netlink机制。介绍了netlink的程序流程,特点,和具体实现代码
Linux中的Netlink机制是一种在用户空间应用程序和内核之间进行高效通信的方法。Netlink提供了一种消息传递的方式,使得用户态程序能够安全地与内核交互,传递数据和控制信息,而无需通过系统调用来完成所有的操作。...
Linux 用户空间使用Netlink监听uevent,不是原理介绍,而是实战demo
在2.6版本的内核中,Netlink机制经历了显著的改进。 Netlink的核心是通过socket接口实现的,它使用了AF_NETLINK地址族。在内核中,`netlink_kernel_create()`函数是创建Netlink套接字的关键,这个函数接受多个参数...
本主题将深入探讨在Linux环境下如何进行Socket编程,以及如何利用特定的Netlink机制来实现系统内核与用户空间之间的高效通信。 首先,Socket编程的基础是Berkeley套接字接口,它提供了一种标准化的API,允许程序...
总结,Netlink是Linux内核与用户态通信的重要手段,它提供了高效、安全的消息传递机制。理解Netlink的工作原理和使用方法,对于开发需要与内核交互的应用程序至关重要。通过实践和分析源码,我们可以更好地利用...
《深入理解Linux Netlink机制》 Netlink是Linux内核与用户空间进行通信的一种机制,它为用户空间程序提供了一种高效、灵活的方式来传递信息,包括系统管理、网络配置和其他内核服务。Netlink最初设计用于网络配置,...
总之,netlink机制为不依赖udev的Linux系统提供了一种有效且灵活的方式来管理和响应USB设备的热插拔事件,使得开发者能够更好地控制和管理USB设备的生命周期。通过理解netlink的工作原理以及如何与USB设备交互,我们...
关于网络设备中netlink通信机制详解,主要为代码主题的实现
总的来说,netlink机制为Linux内核与用户空间提供了一种灵活、高效的通信手段,解决了两者之间无阻塞通信的问题,是实现内核模块与用户程序协同工作的重要工具。在Linux驱动开发、系统监控、网络管理等领域,netlink...
总的来说,Netlink机制为Linux系统提供了强大的内核与用户空间通信能力,它简化了系统的复杂性,增强了可扩展性和性能。无论是开发驱动程序、管理系统资源,还是实现高级网络服务,Netlink都是一个值得考虑的通信...
《基于netlink的Linux服务器集群统一外设事件监听机制》 在当前的大型分布式服务器集群系统中,由于服务器数量众多且可能分布广泛,对外设事件的管理和监控成为一个重要的安全问题。传统的监听机制往往无法满足实时...
通过对关键数据结构的理解,我们可以更好地掌握Netlink的工作原理及其如何在内核中实现高效的通信机制。这些数据结构的设计考虑了性能优化以及多播、组播等功能的支持,使得Netlink成为了Linux系统中不可或缺的一...
Netlink套接字是一种基于Socket编程接口的通信机制,在Linux系统中发挥着至关重要的作用。作为一种相对新的进程间通信机制,Netlink套接字具有其自身的优势。本文将详细介绍Netlink套接字在Linux系统通信中的应用...
在Linux 2.4及后续版本中,Netlink套接字成为了用户态和内核态交互的重要机制,尤其在处理中断过程与用户进程间的通信时表现突出。例如,iproute2这样的网络管理工具以及Netfilter包过滤框架都广泛使用了Netlink进行...
Netlink是Linux内核提供的一种消息传递机制,它允许用户空间程序直接与内核进行数据交换,而无需通过系统调用。Netlink基于UNIX域套接字(Unix Domain Socket)的概念,但专门设计用于内核与用户空间的通信。其主要...
在Linux操作系统中,Netlink是一种通信机制,它允许用户空间应用程序与内核进行高效的数据交换。Netlink充当了用户空间和内核空间之间的桥梁,使得开发者可以实现自定义的协议或者进行内核功能的调用而无需编写内核...