导读
AlanCox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动插口的形式提供内核与用户空间的单向数据通讯;随即,在2.1内核开发过程中,AlexeyKuznetsov将Netlink改写成一个愈发灵活、且便于扩充的基于消息通讯插口,并将其应用到中级路由子系统的基础框架里。自那时起,Netlink就成了Linux内核子系统和用户态的应用程序通讯的主要手段之一。
2001年,ForCESIETF委员会即将对Netlink进行了标准化的工作。JamalHadiSalim提议将Netlink定义成一种用于网路设备的路由引擎组件和其控制管理组件之间通讯的合同。不过他的建议最终没有被采纳,取而代之的是我们明天所见到的格局:Netlink被设计成一个新的合同域,domain。
Linux之父托瓦斯曾说过“Linuxisevolution,notintelligentdesign”。哪些意思?就是说,Netlink也同样遵守了Linux的个别设计理念,即没有完整的规范文档,亦没有设计文档。只有哪些?你懂得---“Readthef**kingsourcecode”。
其实,本文不是剖析Netlink在Linux上的实现机制,而是就“什么是Netlink”以及“如何用好Netlink”的话题和你们做个分享,只有在碰到问题时才须要去阅读内核源码弄清个所以然。
哪些是Netlink
关于Netlink的理解,须要掌握几个关键点:
1、面向数据报的无联接消息子系统
2、基于通用的BSDSocket构架而实现
关于第一点使我们很容易联想到UDP合同,能想到这一点就十分棒了。按着UDP合同来理解Netlink不是不无道理,只要你能触类旁通,做到“活学”,擅于总结归纳、联想,最后实现知识迁移这就是学习的本质。Netlink可以实现内核->用户以及用户->内核的单向、异步的数据通讯,同时它还支持两个用户进程之间、甚至两个内核子系统之间的数据通讯。本文中,对后二者我们不予考虑,焦点集中在怎么实现用户内核之间的数据通讯。
听到第二点脑海中是不是顿时闪现了下边这张图片呢?若果是,则说明你确实有善根;其实,不是也没关系,善根可以渐渐长嘛,呵呵。
在前面实战Netlink套接字编程时我们主要会用到socket(),bind(),sendmsg()
和recvmsg()等系统调用,其实还有socket提供的轮训(polling)机制。
Netlink通讯类型
Netlink支持两种类型的通讯方法:时隙和多播。
时隙:常常用于一个用户进程和一个内核子系统之间1:1的数据通讯。用户空间发送命令到内核,之后从内核接受命令的返回结果。
多播:常常用于一个内核进程和多个用户进程之间的1:N的数据通讯。内核作为会话的发起者,用户空间的应用程序是接收者。为了实现这个功能,内核空间的程序会创建一个多播组,之后所有用户空间的对该内核进程发送的消息感兴趣的进程都加入到该组即可接收来自内核发送的消息了。如下:
其中进程A和子系统1之间是时隙通讯,进程B、C和子系统2是多播通讯。上图还向我们说明了一个信息。从用户空间传递到内核的数据是不须要排队的,即其操作是同步完成;而从内核空间向用户空间传递数据时须要排队,是异步的。了解了这一点在开发基于Netlink的应用模块时可以使我们少走好多弯路。如果,你向内核发送了一个消息须要获取内核中个别信息linux 内核 用户空间,例如路由表,或其他信息,假如路由表过分庞大,这么内核在通过Netlink向你返回数据时,你可以好生寻思一下怎样接收那些数据的问题,虽然你已然见到了那种输出队列了linux学习视频,不能视而不见啊。
Netlink的消息格式
Netlink消息由两部份组成:消息头和有效数据荷载,且整个Netlink消息是4字节对齐,通常按主机字节序进行传递。消息头为固定的16字节,消息体宽度可变:
Netlink的消息头
消息头定义在文件里,由结构体nlmsghdr表示:
点击(此处)折叠或打开
structnlmsghdr{__u32nlmsg_len;/*Lengthofmessageincludingheader*/__u16nlmsg_type;/*Messagecontent*/__u16nlmsg_flags;/*Additionalflags*/__u32nlmsg_seq;/*Sequencenumber*/__u32nlmsg_pid;/*SendingprocessPID*/};
消息头中各成员属性的解释及说明:
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必须提供一种机制用于确保每一对“用户-内核”空间通讯的进程之间的数据交互不会发生衰弱。
即,进程A、B通过Netlink向子系统1获取信息时,子系统1必须确保回献给进程A的响应数据不会发到进程B哪里。主要适用于用户空间的进程从内核空间获取数据的场景。一般情况下,用户空间的进程在向内核发送消息时通常通过系统调用getpid()将当前进程的进程号赋给该变量,即用户空间的进程希望得到内核的响应时才能如此做。从内核主动发送到用户空间的消息该数组都被设置为0。
Netlink的消息体
Netlink的消息体采用TLV(Type-Length-Value)格式:
Netlink每位属性都由文件里的structnlattr{}来表示:
Netlink提供的错误指示消息
内容
当用户空间的应用程序和内核空间的进程之间通过Netlink通讯时发生了错误,Netlink必须向用户空间通报此类错误。Netlink对错误消息进行了单独封装,:
点击(此处)折叠或打开
structnlmsgerr{interror;//标准的错误码,定义在errno.h头文件中。可以用perror()来解释structnlmsghdrmsg;//指明了哪条消息触发了结构体中error这个错误值};
Netlink编程须要注意的问题
基于Netlink的用户-内核通讯,有两种情况可能会造成丢包:
1、内存用尽;
2、用户空间接收进程的缓冲区溢出。造成缓冲区溢出的主要缘由有可能是:用户空间的进程运行太慢;或则接收队列太紧。
假如Netlink不能将消息正确传递到用户空间的接收进程,这么用户空间的接收进程在调用recvmsg()系统调用时才会返回一个显存不足(ENOBUFS)的错误,这一点须要注意。换句话说,缓冲区溢出的情况是不会发送在从用户->内核的sendmsg()系统调用里,缘由上面我们也说过了,请你们自己思索一下。
其实,假如使用的是阻塞型socket通讯,也就不存在显存用尽的隐患了,这又是为何呢?赶快去微软一下,查查哪些是阻塞型socket吧。学而不思则罔,思而不学则殆嘛。
Netlink的地址结构体
在TCP博文中我们谈到过在Internet编程过程中所用到的地址结构体和标准地址结构体,它们和Netlink地址结构体的关系如下:
structsockaddr_nl{}的详尽定义和描述如下:
点击(此处)折叠或打开
structsockaddr_nl{sa_family_tnl_family;/*该数组总是为AF_NETLINK*/unsignedshortnl_pad;/*目前未用到,填充为0*/__u32nl_pid;/*processpid*/__u32nl_groups;/*multicastgroupsmask*/};
nl_pid:该属性为发送或接收消息的进程ID,上面我们也说过,Netlink除了可以实现用户-内核空间的通讯还可使现实用户空间两个进程之间,或内核空间两个进程之间的通讯。该属性为0时通常适用于如下两种情况:
第一,我们要发送的目的地是内核,即从用户空间发往内核空间时,我们构造的Netlink地址结构体中nl_pid一般情况下都置为0。这儿有一点须要跟你们交待一下,在Netlink规范里linux命令,PID全称是Port-ID(32bits)linux 内核 用户空间,其主要作用是用于惟一的标示一个基于netlink的socket通道。一般情况下nl_pid都设置为当前进程的进程号。但是,对于一个进程的多个线程同时使用netlinksocket的情况,nl_pid的设置通常采用如下这个样子来实现:
点击(此处)折叠或打开
pthread_self()