参考书籍
《精通Linux内核网络》 Rami Rosen著(罗伊森)
Netlink 也是一种套接字,就是socket,跟TCP/UDP的socket是类似的,但是,不同的是,TCP/UDP层的socket是 BSD socket。Netlink是独立的一套协议族。
作用
这是该参考书中的说明,这段说明,真是扯淡,对于没有从事过内核网络开发的人来说,这简直是天书。
说白了,这也是一种通信协议,不同的是,一般所说的通信协议,指的是不同主机,包括网络设备之间的通信,可以是有线,也可以是无线。
但是,通信,不仅仅是发生在不同的网络设备之间,也可能发生在一个机器内部,比如进程与进程之间,这就是所谓的进程间通信,手段有信号,信号量,消息队列,共享内存,管道,unix域套接字等等,Unix环境高级编程卷三,就是专门说的这个进程间通信。除了进程与进程之间,内核与进程之间,也是需要通信的。前面也有过一些例子,copy_to_user,copy_from_user之类的,通过ioctl 或者 write/read 进行从内核到用户空间数据的传输,这个走的是文件系统(我猜的)。而Netlink就是一种用户空间与内核之间的通信机制,可以实现双向的通信,并且,Netlink还可以进行内核不同模块之间的通信。当然,不同的进程与内核通信,就能通过Netlink进行进程间通信,但是,不推荐这么干。
Netlink协议,优势在于,第一,不需要轮询。第二,内核可以主动往应用层发送异步消息,而不需要用户空间触发,也就是不需要用户空间去调用什么ioctl。第三,Netlink套接字支持组播。
其实除了以上的优点,我在想,是否还有其他的优点,以下说的未必准确:
第一,Netlink封装了一层,有效的去抽象出了一个功能模块,协助内核内部其他模块的通信,架构层面,更加清晰了,编码更容易了。
第二,Linux内核的网络编程里面,网络数据的流转,都是通过 skb 去流转的,其实都是 skb 的指针,而netlink 虽然抽象出了这样一层通信工具层,但是,数据流转依旧是以skb指针去流转,在内核内部,依旧遵循了 零拷贝 策略,保证了性能。
第三,没有那些过程中依赖的文件里,比如之前博客中的字符设备,/dev 下会创建一个设备文件,netlink就不会创建了。
创建方式
对于用户空间,也就是进程代码中,就当成 socket 创建就可以。
family参数: AF_NETLINK , 与TCP/UDP的 AF_INET / AF_INET6 不同。
第二个参数:SOCK_RAW 或者 SOCK_DGRAM,当然这里面加了个 CLOEXEC 标志。这个fd的标志,不写,也是默认加的,所以可以不显式的去写。至于干嘛的,跟 dup,fork, vfork , 以及 exec 系列族有关。我们要关心的就是 fork的时候,fd会被dup的方式被继承到子进程,如果调用exec系列族函数,那么有这个标志,这个fd就不会被继承了,失效了,否则,这个fd依旧可以读写,前面有博客提到了fork 针对 fd的处理,dup或者fork继承了内核的同一张文件表项。
第三个参数:这是netlink众多协议族中的一种NETLINK_ROUTE。netlink是协议族,这是其中一个协议的规定,利用了netlink的框架,我们自己试验,可以自己定义一个协议,不跟其他的去混淆。
我们不按照这个书去走,这个书上说的,废话连篇,而且,看完之后,你也写不出一个实验来,很扯淡。
我们做个试验
场景:内核与进程通信
内核作为一个 server 端,也就说,不主动给进程空间发消息,而是被动的监听用户进程消息,收到了之后呢,回复一个消息给到用户层,然后就结束,类似回射模型,并且,用同步的方式去处理。
内核层内,我们用动态模块的方式去加载进内核。
用户空间,就写个linux的app即可。
内核代码
// server_netlink.c
#include <linux/init.h>
#include <linux/module.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/rwsem.h>
#include <linux/idr.h>
#include <linux/string.h>
#define NETLINK_TEST 30
#define USER_PORT 100
extern struct net init_net;
static struct sock *s_netlink;
static DECLARE_RWSEM(hello_lock);
static int hello_send_msg_2_user(char *pbuf, uint16_t len)
{
struct sk_buff *nl_skb;
struct nlmsghdr *nlh;
int ret = 0;
/* 创建sk_buff 空间 */
nl_skb = nlmsg_new(len, GFP_ATOMIC);
if(!nl_skb)
{
printk("netlink alloc failure !\n");
return -1;
}
/* 设置netlink消息头部 */
nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
if(nlh == NULL)
{
printk("nlmsg_put failaure !\n");
nlmsg_free(nl_skb);
return -1;
}
/* 拷贝数据发送 */
memcpy(nlmsg_data(nlh), pbuf, len);
ret = netlink_unicast(s_netlink, nl_skb, USER_P