用户空间和内核空间通信之【Netlink 上】

引言css

         Alan Cox在内核1.3版本的开发阶段最早引入了Netlink,刚开始时Netlink是以字符驱动接口的方式提供内核与用户空间的双向数据通讯;随后,在2.1内核开发过程当中,Alexey KuznetsovNetlink改写成一个更加灵活、且易于扩展的基于消息通讯接口,并将其应用到高级路由子系统的基础框架里。自那时起,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”。网络

       固然,本文不是分析NetlinkLinux上的实现机制,而是就“什么是Netlink”以及“如何用好Netlink”的话题和你们作个分享,只有在遇到问题时才须要去阅读内核源码弄清个因此然。架构


什么是Netlink框架

       关于Netlink的理解,须要把握几个关键点:dom

       一、面向数据报的无链接消息子系统异步

       二、基于通用的BSD Socket架构而实现socket

      关于第一点使咱们很容易联想到UDP协议,能想到这一点就很是棒了。按着UDP协议来理解Netlink不是不无道理,只要你能举一反三,作到“活学”,善于总结概括、联想,最后实现知识迁移这就是学习的本质。Netlink能够实现内核->用户以及用户->内核的双向、异步的数据通讯,同时它还支持两个用户进程之间、甚至两个内核子系统之间的数据通讯。本文中,对后二者咱们不予考虑,焦点集中在如何实现用户<->内核之间的数据通讯。ide

      看到第二点脑海中是否是瞬间闪现了下面这张图片呢?若是是,则说明你确实有慧根;固然,不是也不要紧,慧根能够慢慢长嘛,呵呵。

    在后面实战Netlink套接字编程时咱们主要会用到socket(),bind(),sendmsg()
recvmsg()等系统调用,固然还有socket提供的轮训(polling)机制。        


Netlink通讯类型

      Netlink支持两种类型的通讯方式:单播多播

      单播:常常用于一个用户进程和一个内核子系统之间1:1的数据通讯。用户空间发送命令到内核,而后从内核接受命令的返回结果。

      多播:常常用于一个内核进程和多个用户进程之间的1:N的数据通讯。内核做为会话的发起者,用户空间的应用程序是接收者。为了实现这个功能,内核空间的程序会建立一个多播组,而后全部用户空间的对该内核进程发送的消息感兴趣的进程都加入到该组便可接收来自内核发送的消息了。以下:
    其中进程A和子系统1之间是单播通讯,进程BC和子系统2是多播通讯。上图还向咱们说明了一个信息。从用户空间传递到内核的数据是不须要排队的,即其操做是同步完成;而从内核空间向用户空间传递数据时须要排队,是异步的。了解了这一点在开发基于Netlink的应用模块时可使咱们少走不少弯路。假如,你向内核发送了一个消息须要获取内核中某些信息,好比路由表,或其余信息,若是路由表过于庞大,那么内核在经过Netlink向你返回数据时,你能够好生琢磨一下如何接收这些数据的问题,毕竟你已经看到了那个输出队列了,不能视而不见啊。


Netlink的消息格式

       Netlink消息由两部分组成:消息头和有效数据载荷,且整个Netlink消息是4字节对齐,通常按主机字节序进行传递。消息头为固定的16字节,消息体长度可变:

Netlink的消息头

      消息头定义在 文件里,由结构体nlmsghdr表示:

点击(此处)折叠或打开

  1. struct nlmsghdr
  2. {
  3.     __u32        nlmsg_len;    /* Length of message including header */
  4.     __u16        nlmsg_type;    /* Message content */
  5.     __u16        nlmsg_flags;    /* Additional flags */
  6.     __u32        nlmsg_seq;    /* Sequence number */
  7.     __u32        nlmsg_pid;    /* Sending process PID */
  8. };

      消息头中各成员属性的解释及说明:

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_flags有多种取值就能够,至于每种值的做用和意义,经过谷歌和源代码必定能够找到答案,这里就不展开了。上一张2.6.21内核中全部的取值状况:

nlmsg_seq:消息序列号。由于Netlink是面向数据报的,因此存在丢失数据的风险,可是Netlink提供了如何确保消息不丢失的机制,让程序开发人员根据其实际需求而实现。消息序列号通常和NLM_F_ACK类型的消息联合使用,若是用户的应用程序须要保证其发送的每条消息都成功被内核收到的话,那么它发送消息时须要用户程序本身设置序号,内核收到该消息后对提取其中的序列号,而后在发送给用户程序回应消息里设置一样的序列号。有点相似于TCP的响应和确认机制。

注意:当内核主动向用户空间发送广播消息时,消息中的该字段老是为0


nlmsg_pid :当用户空间的进程和内核空间的某个子系统之间经过Netlink创建了数据交换的通道后,Netlink会为每一个这样的通道分配一个惟一的数字标识。其主要做用就是未来自用户空间的请求消息和响应消息进行关联。说得直白一点,假如用户空间存在多个用户进程,内核空间一样存在多个进程,Netlink必须提供一种机制用于确保每一对“用户-内核”空间通讯的进程之间的数据交互不会发生紊乱。
     即,进程AB经过Netlink向子系统1获取信息时,子系统1必须确保回送给进程A的响应数据不会发到进程B那里。主要适用于用户空间的进程从内核空间获取数据的场景。一般状况下,用户空间的进程在向内核发送消息时通常经过系统调用getpid()将当前进程的进程号赋给该变量,即用户空间的进程但愿获得内核的响应时才会这么作。从内核主动发送到用户空间的消息该字段都被设置为0

Netlink的消息体

      Netlink的消息体采用TLV(Type-Length-Value)格式:
      Netlink每一个属性都由 文件里的struct nlattr{}来表示:


Netlink提供的错误指示消息

      当用户空间的应用程序和内核空间的进程之间经过Netlink通讯时发生了错误,Netlink必须向用户空间通报这种错误。Netlink对错误消息进行了单独封装,

点击(此处)折叠或打开

  1. struct nlmsgerr
  2. {
  3.     int        error; //标准的错误码,定义在errno.h头文件中。能够用perror()来解释
  4.     struct nlmsghdr msg; //指明了哪条消息触发告终构体中error这个错误值
  5. };


Netlink编程须要注意的问题

      基于Netlink的用户-内核通讯,有两种状况可能会致使丢包:

      1、内存耗尽;

      2、用户空间接收进程的缓冲区溢出。致使缓冲区溢出的主要缘由有多是:用户空间的进程运行太慢;或者接收队列过短。

      若是Netlink不能将消息正确传递到用户空间的接收进程,那么用户空间的接收进程在调用recvmsg()系统调用时就会返回一个内存不足(ENOBUFS)的错误,这一点须要注意。换句话说,缓冲区溢出的状况是不会发送在从用户->内核的sendmsg()系统调用里,缘由前面咱们也说过了,请你们本身思考一下。

      固然,若是使用的是阻塞型socket通讯,也就不存在内存耗尽的隐患了,这又是为何呢?赶忙去谷歌一下,查查什么是阻塞型socket吧。学而不思则罔,思而不学则殆嘛。


Netlink的地址结构体

      在TCP博文中咱们提到过在Internet编程过程当中所用到的地址结构体和标准地址结构体,它们和Netlink地址结构体的关系以下:

    struct sockaddr_nl{}的详细定义和描述以下:

点击(此处)折叠或打开

  1. struct sockaddr_nl
  2. {
  3.     sa_family_t    nl_family;    /*该字段老是为AF_NETLINK    */
  4.     unsigned short    nl_pad;        /* 目前未用到,填充为0*/
  5.     __u32        nl_pid;        /* process pid    */
  6.     __u32        nl_groups;    /* multicast groups mask */
  7. };

nl_pid:该属性为发送或接收消息的进程ID,前面咱们也说过,Netlink不只能够实现用户-内核空间的通讯还可以使现实用户空间两个进程之间,或内核空间两个进程之间的通讯。该属性为0时通常适用于以下两种状况:

        第一,咱们要发送的目的地是内核,即从用户空间发往内核空间时,咱们构造的Netlink地址结构体中nl_pid一般状况下都置为0。这里有一点须要跟你们交代一下,在Netlink规范里,PID全称是Port-ID(32bits),其主要做用是用于惟一的标识一个基于netlinksocket通道。一般状况下nl_pid都设置为当前进程的进程号。然而,对于一个进程的多个线程同时使用netlink socket的状况,nl_pid的设置通常采用以下这个样子来实现:

点击(此处)折叠或打开

  1. pthread_self() << 16 | getpid();

       第二,从内核发出的多播报文到用户空间时,若是用户空间的进程处在该多播组中,那么其地址结构体中nl_pid也设置为0,同时还要结合下面介绍到的另外一个属性。

nl_groups:若是用户空间的进程但愿加入某个多播组,则必须执行bind()系统调用。该字段指明了调用者但愿加入的多播组号的掩码(注意不是组号,后面咱们会详细讲解这个字段)。若是该字段为0则表示调用者不但愿加入任何多播组。对于每一个隶属于Netlink协议域的协议,最多可支持32个多播组(由于nl_groups的长度为32比特),每一个多播组用一个比特来表示。 

       关于Netlink剩下的知识点,咱们在后面的实战环节有用到时再讨论。

       未完,待续
相关文章
相关标签/搜索