引言

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

1、面向数据报的无连接消息子系统

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

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

看到第二点脑海中是不是瞬间闪现了下面这张图片呢?如果是,则说明你确实有慧根;当然,不是也没关系,慧根可以慢慢长嘛,呵呵。

在后面实战Netlink套接字编程时我们主要会用到socket(),bind(),sendmsg()

和recvmsg()等系统调用,当然还有socket提供的轮训(polling)机制。

Netlink通信类型

Netlink支持两种类型的通信方式:单播和多播。

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

多播:经常用于一个内核进程和多个用户进程之间的1:N的数据通信。内核作为会话的发起者,用户空间的应用程序是接收者。为了实现这个功能,内核空间的程序会创建一个多播组,然后所有用户空间的对该内核进程发送的消息感兴趣的进程都加入到该组即可接收来自内核发送的消息了。如下:

其中进程A和子系统1之间是单播通信,进程B、C和子系统2是多播通信。上图还向我们说明了一个信息。从用户空间传递到内核的数据是不需要排队的,即其操作是同步完成;而从内核空间向用户空间传递数据时需要排队,是异步的。了解了这一点在开发基于Netlink的应用模块时可以使我们少走很多弯路。假如,你向内核发送了一个消息需要获取内核中某些信息,比如路由表,或其他信息,如果路由表过于庞大,那么内核在通过Netlink向你返回数据时,你可以好生琢磨一下如何接收这些数据的问题,毕竟你已经看到了那个输出队列了,不能视而不见啊。

Netlink的消息格式

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

Netlink的消息头

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

struct nlmsghdr
{__u32        nlmsg_len;    /* Length of message including header */__u16        nlmsg_type;    /* Message content */__u16        nlmsg_flags;    /* Additional flags */__u32        nlmsg_seq;    /* Sequence number */__u32        nlmsg_pid;    /* Sending process PID */
};

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

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每个属性都由文件里的struct nlattr{}来表示:

Netlink提供的错误指示消息

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

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

Netlink编程需要注意的问题

基于Netlink的用户-内核通信,有两种情况可能会导致丢包:

1、内存耗尽;

2、用户空间接收进程的缓冲区溢出。导致缓冲区溢出的主要原因有可能是:用户空间的进程运行太慢;或者接收队列太短。

如果Netlink不能将消息正确传递到用户空间的接收进程,那么用户空间的接收进程在调用recvmsg()系统调用时就会返回一个内存不足(ENOBUFS)的错误,这一点需要注意。换句话说,缓冲区溢出的情况是不会发送在从用户->内核的sendmsg()系统调用里,原因前面我们也说过了,请大家自己思考一下。

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

Netlink的地址结构体

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

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

struct sockaddr_nl
{sa_family_t    nl_family;    /*该字段总是为AF_NETLINK    */unsigned short    nl_pad;        /* 目前未用到,填充为0*/__u32        nl_pid;        /* process pid    */__u32        nl_groups;    /* multicast groups mask */
};

nl_pid:该属性为发送或接收消息的进程ID,前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间,或内核空间两个进程之间的通信。该属性为0时一般适用于如下两种情况:

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

pthread_self() << 16 | getpid();

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

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

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

Netlink 0002 -- 什么是Netlink相关推荐

  1. NetLink Communication Mechanism And Netlink Sourcecode Analysis

    catalog 1. Netlink简介 2. Netlink Function API Howto 3. Generic Netlink HOWTO kernel API 4. RFC 3549 L ...

  2. linux 内核与用户空间通信之netlink使用方法

    Linux中的进程间通信机制源自于Unix平台上的进程通信机制.Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的Syste ...

  3. linux netlink 编程示例(一)内核端

    Netlink是一种内核层与应用层通信的一种机制,比如说在做一个内核模块的时候,往往会需要应用层提供一些配置信息,这时候就可以使用netlink.netlink包括内核层和应用层,内核层注册一个net ...

  4. Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用

    首先看一下Openwrt系统中关于按键功能的使用和修改,以18.06版本为例 按键功能实现在脚本中, 比如18.06/package/base-files/files/etc/rc.button/re ...

  5. Linux 内核态与用户态通信 netlink

    参考资料: https://blog.csdn.net/zqixiao_09/article/details/77131283 https://www.cnblogs.com/lopnor/p/615 ...

  6. netlink怎么读_内核交互 netlink,检测部分进程死亡和启动。

    和内核交互netlink netlink 内核和用户进程交互 用户空间用的是socket,内核空间用的是内部API和一个模块. 向下兼容. 面向数据包的应用.即SOCK_RAW and SOCK_DG ...

  7. Linux-3.2.0.24中内核的Netlink测试使用

    Netlink在2.6版本的内核中变化也是很大的,在最新的2.6.37内核中,其定义已经改成下面这种形式,传递的参数已经达到6个.其中第一个参数和mutex参数都是最新添加的.Mutex也可以为空.这 ...

  8. 【转】内核通信之 Netlink 源码分析和实例分析

    目录 前言 什么是 netlink netlink 内核代码走读 netlink 内核相关文件介绍 af_netlink.c 代码走读 netlink 用户态和内核交互过程 netlink 关键数据结 ...

  9. linux generic netlink实现机制:注册、创建

    目录 1 Generic Netlink 概述 2 Generic Netlink相关结构体 2.1 Generic Netlink消息头结构:struct genlmsghdr 2.2 Generi ...

  10. NetLink机制使用

    前些日子研究如何在Android实现USB-Audio的热插拔,顺带了解了一下netlink机制.netlink在TCP/IP方面用得较多,但根据需要也可用在HDMI/USB等热插拔消息通知.前人已经 ...

最新文章

  1. Android中的HTTP通信
  2. QQ交流群,欢迎有兴趣的同学加入。
  3. Unity 2D游戏开发教程之为游戏场景添加多个地面
  4. 我们该不该旗帜鲜明地反对李彦宏当选院士?
  5. 综合布线系统技术培训资料
  6. UVA11525 Permutation 逆康托展开
  7. 转:flex [Inspectable]标签详解
  8. Python论做游戏外挂,Python输过谁?
  9. c++ 哈希_Redis源码解析十一--Hash键实现Redis 哈希键命令实现(t_hash)
  10. Linux之ssh-agent命令
  11. ld framework not found FileProvider for architecture x86_64 报错
  12. 使用at任务定点执行
  13. 2018年四大爬虫代理IP提供商对比
  14. Mujoco xml建模
  15. 本地与远程服务器之间的文件传输
  16. 【CPM同步】连续相位调制(CPM)通信调制方法的载波同步和定时同步研究和matlab仿真
  17. 经典圣诞老人题----同步与互斥
  18. 南京php吧,利用php爬虫分析南京房价
  19. 2018杭州云栖大会参会总结
  20. 一壶浊酒尽余欢、今宵别梦寒!

热门文章

  1. Codeforce 697A - Pineapple Incident
  2. linux中的sed指令
  3. JS中innerHTML 和innerText和value的区别
  4. SQLServer2005 没有日志文件(*.ldf) 只有数据文件(*.mdf) 恢复数据库的方法
  5. java string 最大长度_我说我精通字符串,面试官竟然问我Java中的String有没有长度限制!?...
  6. 冲刺第七天 12.3 MON
  7. VMware Workstation 備份與恢復虛擬機
  8. 传输层的端口与TCP标志中的URG和PSH位
  9. python 之selectors 实现文件上传下载
  10. Java知多少(31)static关键字以及Java静态变量和静态方法