内核通信之Netlink源码分析-用户内核通信原理

2017-07-05


 本节从一个小案例入手,结合源码分析下通过netlink进行内核和用户通信的流程。

内核端

按照传统CS模式,其实内核端可以作为是服务器端,用以接收用户的请求并作出处理,但是从netlink本身的特性,其更像是一个对等实体。双方都可以进行主动数据的传递。

内核中首先调用netlink_kernel_create函数创建一个sock结构,其实这里仅仅是返回一个sock结构,而其中创建了相关的socket,netlink_sock,inode等。

老版本的函数参数都是写在netlink_kernel_create函数里的,现在把部分参数抽离到一个netlink_kernel_cfg结构中,其中我们只关心接收数据的处理函数.input。首个参数表示网络命名空间,一般取init_net,NETLINK_TEST是我们自定义的协议类型,netlink支持32个协议0-31,其实就是协商好的一个数字,用来用户层和内核层的通信。第三个就是我们的配置结构。函数内部首先创建了socket,然后创建了sock,并在socket和sock间建立关联,默认创建sock是在全局的命名空间init_net下,所以还需要修改到参数中的net,不过一般我们也正是选择全局的。然后需要设置接收函数到netlink_sock中的netlink_rcv字段。最后需要把sock加入到全局管理结构nl_table中。函数大致功能如此,后面我们再详细讨论。

目前sock已经注册上,如何处理接收到的数据呢?看下我们实现的简单的接收函数rece_msg,

Netlink基于socket,所以其数据是通过套接字缓冲区sk_buff管理的。从skb中获取信息长度,该长度是包含了nlmsghdr的长度加上数据,后面为了方便直接读取的100字节,实际上应该让获取到的长度将去nlmsghdr的长度。操作完毕需要调用skb_pull减去已经读取到的长度。后面没获取一次请求我们就调用了发送函数向用户空间发送一个消息,pid是nlmsghdr头部中的nlmsg_pid,表示发送者的端口。看下发送函数sendmsg

由于是在内核,有些事情需要亲力亲为,比如skb的分配。这里我们首先分配了一个skb,然后获取nlmsghdr,设置skb的源属性,即来自于哪里,这里设置源端口为0,组掩码为0表示不支持组播。接着就赋值数据到skb的数据区,这里依然是通过nlmsghdr。最后才调用netlink_unicast发送出去。

用户端

一下贴图均位于一个main函数中。

用户端首先要创建一个套接字,不过这里就不会返回套接字结构,而是返回一个文件描述符fd;参数都是标准套接字的参数,这里就不多说,针对netlink首个协议族选择AF_NETLINK,第二个参数是socket类型,我们选择的是原生socketSOCK_RAW,最后是指定的协议,这个和前面内核定义的是一致的,否则无法通信。

设置源地址信息,我们设置的源端口为100,也可以是线程ID,保证唯一性即可。组播掩码同样设置0。最后调用bind函数把源地址和socket进行绑定。

 

分配一个nlmsghdr头部,把源信息记录进去,并设置目标地址信息,这里nl.pid=0表示目标在内核,这点同样是和内核中对应的。nlh->nlmsg_len是对应的包含头部在内的总长度,回想下内核中接收部分就明白了。

 

设置好之后就要准备发送了,不过用户层发送到内核是通过另一个结构msghdr,这点在首篇文章框架介绍中有详细说明,为此我们还要对msghdr进行填充,中间是通过iov向量管理。填充完毕就调用sendmsg库函数发送到内核。接收就相当简单了,我们利用了前面设置的msg,直接调用recvmsg函数即可

 

基本的通信流程如上文所述,下文针对每一部分做详细分析,最后效果如图所示……

 

以马内利

 参考资料:

1、《深入linux内核架构》

2、linux3.10.1源码

posted @ 2017-07-06 10:59  jack.chen  Views(4084)  Comments(0Edit  收藏  举报

以马内利