- 论坛徽章:
- 0
|
开发和维护内核是一件很繁杂的工作,因此,只有那些最重要或者与系统性能息息相关的代码才将其安排在内核中。其它程序,比如GUI,管理以及控制部分的代码,一般都会作为用户态程序。在linux系统中,把系统的某个特性分割成在内核中和在用户空间中分别实现一部分的做法是很常见的(比如linux系统的防火墙就分成了内核态的Netfilter和用户态的iptables)。然而,内核程序与用户态的程序又是怎样行通讯的呢?
答案就是通过各种各样的用户态和内核态的IPC(interprocess communication )机制来实现。比如系统调用,ioctl接口,proc文件系统以及netlink socket,本文就是要讨论netlink socekt并向读者展示这种用网络
通讯接口方式实现的IPC机制的优点。
介绍:
netlink socekt是一种用于在内核态和用户态进程之间进行数据传输的特殊的IPC。它通过为内核模块提
供一组特殊的API,并为用户程序提供了一组标准的socket 接口的方式,实现了一种全双工的通讯连接。类似于TCP/IP中使用AF_INET地址族一样,netlink socket使用地址族AF_NETLINK。每一个netlink
socket在内核头文件
include/linux/netlink.h
中定义自己的协议类型。
下面是netlink socket 目前的特性集合以及它支持的协议类型:
NETLINK_ROUTE 用户空间的路由守护程序之间的通讯通道,比如BGP,OSPF,RIP以及内核数据转发模块。用户态的路由守护程序通过此类型的协议来更新内核中的路由表。
NETLINK_FIREWALL:接收IPV4防火墙代码发送的数据包。
NETLINK_NFLOG:用户态的iptables管理工具和内核中的netfilter模块之间通讯的通道。
NETLINK_ARPD:用来从用户空间管理内核中的ARP表。
为什么以上的功能在实现用户程序和内核程序通讯时,都使用netlink方法而不是系统调用,ioctls
或者proc文件系统呢?原因在于:为新的特性添加一个新的系统调用,ioctls或者一个proc文件的做法并不是很容易的一件事情,因为我们要冒着污染内核代码并且可能破坏系统稳定性的风险去完成这件事情。
然而,netlink socket却是如此的简单,你只需要在文件netlink.h中添加一个常量来标识你的协议类型,然后,内核模块和用户程序就可以立刻使用socket风格的API进行通讯了!
Netlink提供了一种异步通讯方式,与其他socket API一样,它提供了一个socket队列来缓冲或者平滑
瞬时的消息高峰。发送netlink消息的系统调用在把消息加入到接收者的消息对列后,会触发接收者的接收处理函数。接收者在接收处理函数上下文中,可以决定立即处理消息还是把消息放在队列中,在以后其它上下文去处理它(因为我们希望接收处理函数执行的尽可能快)。系统调用与netlink不同,它需要一个同步的处理,因此,当我们使用一个系统调用来从用户态传递消息到内核时,如果处理这个消息的时间很长的话,内核调度的粒度就会受到影响。
内核中实现系统调用的代码都是在编译时静态链接到内核的,因此,在动态加载模块中去包含一个系统调用的做法是不合适的,那是大多数设备驱动的做法。使用netlink socket时,动态加载模块中的netlink程序不会和linux内核中的netlink部分产生任何编译时依赖关系。
Netlink优于系统调用,ioctls和proc文件系统的另外一个特点就是它支持多点传送。一个进程可以把消息传输给一个netlink组地址,然后任意多个进程都可以监听那个组地址(并且接收消息)。这种机制为内核到用户态的事件分发提供了一种近乎完美的解决方案。
系统调用和ioctl都属于单工方式的IPC,也就是说,这种IPC会话的发起者只能是用户态程序。但是,如果内核有一个紧急的消息想要通知给用户态程序时,该怎么办呢?如果直接使用这些IPC的话,是没办法做到这点的。通常情况下,应用程序会周期性的轮询内核以获取状态的改变,然而,高频度的轮询势必会增加系统的负载。Netlink 通过允许内核初始化会话的方式完美的解决了此问题,我们称之为netlink socket的双工特性。
最后,netlink socket提供了一组开发者熟悉的BSD风格的API函数,因此,相对于使用神秘的系统调用API或者ioctl而言,netlink开发培训的费用会更低些。
与BSD的Routing socket的关系
在BSD TCP/IP的协议栈实现中,有一种特殊的socket叫做Routing socket.它的地址族为AF_ROUTE, 协议族为PF_ROUTE, socket类型为SOCK_RAW. 这种Routing socket是用户态进程用来向内核中的路由表增加或者删除路由信息用的。在Linux系统中,netlink socket通过协议类型NETLINK_ROUTE实现了与Routing socket相同的功能,可以说,netlink socket提供了BSD Routing socket功能的超集。
Netlink Socket 的API
标准的socket API函数-
socket(), sendmsg(), recvmsg()和close()
- 都能够被用户态程序直接调用来访问netlink socket.你可以访问man手册来获取这些函数的详细定义。在本文,我们只讨论怎样在netlink socket的上下文中为这些函数选择参数。这些API对于使用TCP/IP socket写过一些简单网络程序的读者来说应该很熟悉了。
使用socket()函数创建一个socket,输入:
int socket(int domain, int type, int protocol)
socket域(地址族)是AF_NETLINK,socket的类型是SOCK_RAW或者SOCK_DGRAM,因为netlink是一种面向数据包的服务。
协议类型选择netlink要使用的类型即可。下面是一些预定义的netlink协议类型:
NETLINK_ROUTE, NETLINK_FIREWALL, NETLINK_ARPD, NETLINK_ROUTE6
和 NETLINK_IP6_FW.
你同样可以很轻松的在netlink.h中添加自定义的协议类型。
每个netlink协议类型可以定义高达32个多点传输的组。每个组用一个比特位来表示,1
struct sockaddr_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;
当上面的结构体被bind()函数调用时,sockaddr_nl的nl_pid属性的值可以设置为访问netlink socket的当前进程的PID,nl_pid作为这个netlink socket的本地地址。应用程序应该选择一个唯一的32位整数来填充nl_pid的值。
NL_PID 公式 1: nl_pid = getpid();
公式一使用进程的PID作为nl_pid的值,如果这个进程只需要一个该类型协议的netlink socket的话,选用进程pid作为nl_pid是一个很自然的做法。
换一种情形,如果一个进程的多个线程想要创建属于各个线程的相同协议类型的netlink socket的话,公式二可以用来为每个线程的netlink socket产生nl_pid值。
NL_PID 公式 2: pthread_self()
采用这种方法,同一进程的不同线程都能获取属于它们的相同协议类型的不同netlink socket。事实上,即便是在一个单独的线程里,也可能需要创建同一协议类型的多个netlink socket。所以开发人员需要更多聪明才智去创建不同的nl_pid值,然而本文中不会就如何创建多个不同的nl_pid的值进行过多的讨论
如果应用程序想要接收特定协议类型的发往指定多播组的netlink消息的话,所有接收组的比特位应该进行与运算,形成sockaddr_nl的 nl_groups域的值。否则的话,nl_groups应该设置为0,以便应用程序只能够收到发送给它的netlink消息。在填充完结构体 nladdr后,作如下的绑定工作:
bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/111204/showart_2160979.html |
|