引言css
Alan Cox在内核1.3版本的开发阶段最早引入了Netlink,刚开始时Netlink是以字符驱动接口的方式提供内核与用户空间的双向数据通讯;随后,在2.1内核开发过程当中,Alexey Kuznetsov将Netlink改写成一个更加灵活、且易于扩展的基于消息通讯接口,并将其应用到高级路由子系统的基础框架里。自那时起,Netlink就成了Linux内核子系统和用户态的应用程序通讯的主要手段之一。linux
2001年,ForCES IETF委员会正式对Netlink进行了标准化的工做。Jamal Hadi Salim提议将Netlink定义成一种用于网络设备的路由引擎组件和其控制管理组件之间通讯的协议。不过他的建议最终没有被采纳,取而代之的是咱们今天所看到的格局:Netlink被设计成一个新的协议域,domain。编程
Linux之父托瓦斯曾说过“Linux is evolution, not intelligent design”。什么意思?就是说,Netlink也一样遵循了Linux的某些设计理念,即没有完整的规范文档,亦没有设计文档。只有什么?你懂得---“Read the f**king source code”。网络
固然,本文不是分析Netlink在Linux上的实现机制,而是就“什么是Netlink”以及“如何用好Netlink”的话题和你们作个分享,只有在遇到问题时才须要去阅读内核源码弄清个因此然。架构
什么是Netlink框架
关于Netlink的理解,须要把握几个关键点:dom
一、面向数据报的无链接消息子系统异步
二、基于通用的BSD Socket架构而实现socket
关于第一点使咱们很容易联想到UDP协议,能想到这一点就很是棒了。按着UDP协议来理解Netlink不是不无道理,只要你能举一反三,作到“活学”,善于总结概括、联想,最后实现知识迁移这就是学习的本质。Netlink能够实现内核->用户以及用户->内核的双向、异步的数据通讯,同时它还支持两个用户进程之间、甚至两个内核子系统之间的数据通讯。本文中,对后二者咱们不予考虑,焦点集中在如何实现用户<->内核之间的数据通讯。ide
看到第二点脑海中是否是瞬间闪现了下面这张图片呢?若是是,则说明你确实有慧根;固然,不是也不要紧,慧根能够慢慢长嘛,呵呵。
Netlink通讯类型
Netlink支持两种类型的通讯方式:单播和多播。
单播:常常用于一个用户进程和一个内核子系统之间1:1的数据通讯。用户空间发送命令到内核,而后从内核接受命令的返回结果。
Netlink的消息格式
Netlink消息由两部分组成:消息头和有效数据载荷,且整个Netlink消息是4字节对齐,通常按主机字节序进行传递。消息头为固定的16字节,消息体长度可变:Netlink的消息头
消息头定义在
点击(此处)折叠或打开
消息头中各成员属性的解释及说明:
nlmsg_len:整个消息的长度,按字节计算。包括了Netlink消息头自己。
nlmsg_type:消息的类型,便是数据仍是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息,以下:
NLMSG_NOOP-空消息,什么也不作;
NLMSG_ERROR-指明该消息中包含一个错误;
NLMSG_DONE-若是内核经过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其他全部消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
NLMSG_OVERRUN-暂时没用到。
nlmsg_flags:附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI。摘录以下: 标记
|
做用及说明
|
NLM_F_REQUEST
|
若是消息中有该标记位,说明这是一个请求消息。全部从用户空间到内核空间的消息都要设置该位,不然内核将向用户返回一个EINVAL无效参数的错误
|
NLM_F_MULTI
|
消息从用户->内核是同步的马上完成,而从内核->用户则须要排队。若是内核以前收到过来自用户的消息中有NLM_F_DUMP位为1的消息,那么内核就会向用户空间发送一个由多个Netlink消息组成的链表。除了最后个消息外,其他每条消息中都设置了该位有效。
|
NLM_F_ACK
|
该消息是内核对来自用户空间的NLM_F_REQUEST消息的响应
|
NLM_F_ECHO
|
若是从用户空间发给内核的消息中该标记为1,则说明用户的应用进程要求内核将用户发给它的每条消息经过单播的形式再发送给用户进程。和咱们一般说的“回显”功能相似。
|
…
|
…
|
nlmsg_seq:消息序列号。由于Netlink是面向数据报的,因此存在丢失数据的风险,可是Netlink提供了如何确保消息不丢失的机制,让程序开发人员根据其实际需求而实现。消息序列号通常和NLM_F_ACK类型的消息联合使用,若是用户的应用程序须要保证其发送的每条消息都成功被内核收到的话,那么它发送消息时须要用户程序本身设置序号,内核收到该消息后对提取其中的序列号,而后在发送给用户程序回应消息里设置一样的序列号。有点相似于TCP的响应和确认机制。
注意:当内核主动向用户空间发送广播消息时,消息中的该字段老是为0。
Netlink的消息体
Netlink的消息体采用TLV(Type-Length-Value)格式:Netlink提供的错误指示消息
当用户空间的应用程序和内核空间的进程之间经过Netlink通讯时发生了错误,Netlink必须向用户空间通报这种错误。Netlink对错误消息进行了单独封装,点击(此处)折叠或打开
Netlink编程须要注意的问题
基于Netlink的用户-内核通讯,有两种状况可能会致使丢包:
1、内存耗尽;
2、用户空间接收进程的缓冲区溢出。致使缓冲区溢出的主要缘由有多是:用户空间的进程运行太慢;或者接收队列过短。
若是Netlink不能将消息正确传递到用户空间的接收进程,那么用户空间的接收进程在调用recvmsg()系统调用时就会返回一个内存不足(ENOBUFS)的错误,这一点须要注意。换句话说,缓冲区溢出的状况是不会发送在从用户->内核的sendmsg()系统调用里,缘由前面咱们也说过了,请你们本身思考一下。
固然,若是使用的是阻塞型socket通讯,也就不存在内存耗尽的隐患了,这又是为何呢?赶忙去谷歌一下,查查什么是阻塞型socket吧。学而不思则罔,思而不学则殆嘛。
Netlink的地址结构体
在TCP博文中咱们提到过在Internet编程过程当中所用到的地址结构体和标准地址结构体,它们和Netlink地址结构体的关系以下:struct sockaddr_nl{}的详细定义和描述以下:
点击(此处)折叠或打开
nl_pid:该属性为发送或接收消息的进程ID,前面咱们也说过,Netlink不只能够实现用户-内核空间的通讯还可以使现实用户空间两个进程之间,或内核空间两个进程之间的通讯。该属性为0时通常适用于以下两种状况:
第一,咱们要发送的目的地是内核,即从用户空间发往内核空间时,咱们构造的Netlink地址结构体中nl_pid一般状况下都置为0。这里有一点须要跟你们交代一下,在Netlink规范里,PID全称是Port-ID(32bits),其主要做用是用于惟一的标识一个基于netlink的socket通道。一般状况下nl_pid都设置为当前进程的进程号。然而,对于一个进程的多个线程同时使用netlink socket的状况,nl_pid的设置通常采用以下这个样子来实现:
点击(此处)折叠或打开
第二,从内核发出的多播报文到用户空间时,若是用户空间的进程处在该多播组中,那么其地址结构体中nl_pid也设置为0,同时还要结合下面介绍到的另外一个属性。
nl_groups:若是用户空间的进程但愿加入某个多播组,则必须执行bind()系统调用。该字段指明了调用者但愿加入的多播组号的掩码(注意不是组号,后面咱们会详细讲解这个字段)。若是该字段为0则表示调用者不但愿加入任何多播组。对于每一个隶属于Netlink协议域的协议,最多可支持32个多播组(由于nl_groups的长度为32比特),每一个多播组用一个比特来表示。
关于Netlink剩下的知识点,咱们在后面的实战环节有用到时再讨论。
未完,待续…