linux netlink?linux ioctl

详解Linux内核通信netlink机制

详解Linux内核通信netlink机制

netlink socket是一种用于内核态和用户态进程之间进行数据传输的特殊IPC机制。它通过为内核模块提供一组特殊的API,并为用户程序提供了一组标准的socket接口的方式,实现了一种全双工的通讯连接。

Netlink socket使用地址族AF_NETLINK,每一个netlink socket在内核头文件include/linux/netlink.h中定义自己的协议类型。它提供了一种异步通讯方式,与其他socket API一样,它提供了一个socket队列来缓冲或平滑瞬时的消息高峰。发送netlink消息的系统调用在把消息加入到接收者的消息队列后,会触发接收者的接收处理函数。接收者在接收处理函数上下文中,可以决定立即处理消息还是把消息放在队列中,在以后其他上下文去处理它。

内核中实现系统调用的代码都是在编译时静态链接到内核的,因此,在动态加载模块中去包含一个系统调用的做法是不合适的。使用netlink socket时,动态加载模块中的netlink程序不会和linux内核中的netlink部分产生任何编译时依赖关系。

Netlink优于系统调用、ioctls和proc文件系统的另一个特点就是它支持多点传送。一个进程可以把消息传输给一个netlink组地址,然后任意多个进程都可以监听那个组地址,并接收消息。这种机制为内核到用户态的事件分发提供了一种近乎完美的解决方案。

系统调用和ioctl都属于单工方式的IPC,也就是说,这种IPC会话的发起者只能是用户态程序。然而,如果内核有一个紧急的消息想要通知给用户态程序时,该怎么办呢?如果直接使用这些IPC的话,是没办法做到这点的。通常情况下,应用程序会周期性地轮询内核以获取状态的改变,然而,高频度的轮询势必会增加系统的负载。Netlink通过允许内核初始化会话的方式完美地解决了此问题,我们称之为netlink socket的双工特性。

Netlink Socket的API包括标准的socket API函数-socket(), sendmsg(), recvmsg()和close()。使用socket()函数创建一个socket,输入:int socket(int domain, int type, int protocol)。跟TCP/IP中的socket一样,netlink的bind()函数把一个本地socket地址与一个打开的socket进行关联,netlink地址结构体如下。另一个结构体 struct sockaddr_nl nladdr作为目的地址。如果这个netlink消息是发往内核的话,nl_pid属性和nl_groups属性都应该设置为0。如果这个消息是发往另一个进程的单点传输消息,nl_pid应该设置为接收者进程的PID,nl_groups应该设置为0。netlink消息同样也需要它自身的消息头,这样做是为了给所有协议类型的netlink消息提供一个通用的背景。

内核空间的netlink API接口由内核中的netlink核心代码支持,在net/core/af_netlink.c中实现。从内核的角度来说,API接口与用户空间的API是不一样的。内核模块通过这些API访问netlink socket并且与用户空间的程序进行通讯。在用户空间,我们通过socket()调用来创建一个netlink socket,而在内核空间,我们调用如下的API:struct sock* netlink_kernel_create(int unit, void(*input)(struct sock*sk, int len))。参数uint是netlink协议类型,例如NETLINK_TEST。函数指针,input,是netlink socket在收到消息时调用的处理消息的回调函数指针。在内核创建了一个NETLINK_TEST类型的netlink socket后,无论什么时候,只要用户程序发送一个NETLINK_TEST类型的netlink消息到内核的话,通过netlink_kernel_create()函数注册的回调函数input()都会被调用。

使用skb= skb_recv_datagram(nl_sk)来接收消息,nl_sk是netlink_kernel_create()函数返回的netlink socket,然后,只需要处理skb->data指针指向的netlink消息就可以了。从内核中发送netlink消息就像从用户空间发送消息一样,内核在发送netlink消息时也需要设置源netlink地址和目的netlink地址。假设结构体struct sk_buff* skb指向存储着要发送的netlink消息的缓冲区,源地址可以这样设置。从内核空间关闭netlink socket,netlink_kernel_create()函数返回的netlink socket为struct sock*nl_sk,我们可以通过访问下面的API来从内核空间关闭这个netlink socket:sock_release(nl_sk->socket);

linux内核:Netlink通信详解

netlink通信数据结构

每个netlink消息的头部固定16字节,包含nlmsg_len、nlmsg_type、nlmsg_flags、nlmsg_seq和nlmsg_pid等字段。nlmsg_len表示整个消息长度,nlmsg_type定义消息类型,nlmsg_flags标记消息类型,nlmsg_seq和nlmsg_pid用于消息排队和识别发送端。

socket消息数据包结构

应用层通过sendto()或sendmsg()函数向内核发送消息。msghdr结构包含msg_name、msg_namelen、msg_iov、msg_control、msg_controllen和msg_flags。msg_name指向目的地址,msg_iov包含消息实际数据,msg_control辅助数据,msg_controllen大小。netlink消息中,msg_name指向目的sockaddr_nl地址,msg_iov指向消息头的地址,iov_len设置为消息头加上实际数据的长度。

netlink消息处理宏

Linux提供了一系列处理netlink消息的宏,便于各种场景使用。netlink消息格式定义在netlink.h中。

应用层发送netlink消息

通过sendmsg函数向内核发送消息。初始化目的地址、消息头、实际数据和发送参数。示例代码展示了如何使用sendmsg向内核netlink套接字发送消息。

内核发送netlink消息

内核使用nlmsg_unicast()或nlmsg_multicast()函数向应用层发送消息。根据目标socket,内核进行过滤和绑定,然后调用sk_data_ready()发送消息。

应用层接收内核消息

使用recvmsg系统调用以阻塞方式接收内核发送的netlink消息。接收时,msg结构中msg_name存放消息源地址,msg_iov用于接收缓存,msg_iovlen指定接收iov空间个数。消息接收过程涉及一次内存拷贝动作,将skb中的数据直接拷贝到用户空间。

Linux Netlink 使用简单教程

Linux的Netlink Socket是一种特别的进程间通信(IPC)机制,它使用户进程与内核进程之间建立起直接的沟通桥梁,是网络应用与内核交互的常用接口。

Netlink的运作分为用户态和核心态两部分。用户态主要负责数据结构的管理和消息发送,如sockaddr_nl,它是用户进程与内核进程通信的地址容器。而nlmsghd则是消息的头,承载着payload,即实际的数据内容。

在编程实践中,用户态的函数通常会包含函数原型定义和一些预定义的宏,如例程所示。对于核心态,需要涉及Linux内核编程,这部分内容超出了本文的范围。尽管如此,无论是用户态还是核心态,它们都使用相同的nlmsghdr结构体,只是操作的内核环境不同。

内核netlink socket使用特定的配置参数结构体netlink_kernel_cfg,其中包含了协议类型信息。Linux支持一系列预定义的协议类型,用户可以根据需要选择,但务必确保选择的协议类型在用户态和核心态之间是兼容的,通常不超过32的协议号。

以echo服务器为例,核心态的实现可以参考test_netlink.c,而用户态的交互则在test_user.c中。为了编译和运行这些代码,你需要编写Makefile,包含编译指令,并可能需要安装内核模块。具体的步骤可以参考简书上的“Linux下Netlink的使用简介”以及insujang.github.io网站的“Implementing a New Custom Netlink Family Protocol”。

阅读剩余
THE END