需要在linux网卡 驱动中加入一个自己的驱动,实现在内核态完成一些报文处理(这个过程可以实现一种零COPY的网络报文截获),对于复杂报文COPY下必要的数据交给用户 态来完成(因为过于复杂的报文消耗CPU太大,会导致中断占用时间太长)。因此需要一种内核和用户态配合的通信机制,尝试了很多方式都不太理想,最后采用 netlink+内存映射的模式很好的解决了这个问题。Netlink是一种采用socket通信的机制,用于linux内核和上层用户空间进行通信的一 种机制,通过实践我认为netlink最大的优点是可以实现“双向通信”,是内核向用户态发起通知的一种最好选择。

内核和用户空间进行通信,大概有如下几种方式可以考虑:
采用内存映射的方式,将内核地址映射到用户态。这种方式最直接,可以适用大量的数据传输机制。这种方式的缺点是很难进行“业务控制”,没有一种可靠的机制
保障内核和用户态的调动同步,比如信号量等都不能跨内核、用户层使用。因此内存映射机制一般需要配合一种“消息机制”来控制数据的读取,比如采用“消息”
类型的短数据通道来完成一个可靠的数据读取功能。
ioctl机制,ioctl机制可以在驱动中扩展特定的ioctl消息,用于将一些状态从内核反应到用户态。Ioctl有很好的数据同步保护机制,不要担
心内核和用户层的数据访问冲突,但是ioctl不适合传输大量的数据,通过和内存映射结合可以很好的完成大量数据交换过程。但是,ioctl的发起方一定
是在用户态,因此如果需要内核态主动发起一个通知消息给用户层,则非常的麻烦。可能需要用户态程序采用轮询机制不停的ioctl。
其他一些方式比如系统调用必须通过用户态发起,proc方式不太可靠和实时,用于调试信息的输出还是非常合适的。
通过前面的项目背景,我需要一种可以在内核态主动发起消息的通知方式,而用户态的程序最好可以采用一种“阻塞调用”的方式等待消息。这样的模型可以最大限度的节省CPU的调度,同时可以满足及时处理的要求,最终选择了netlink完成通信的过程。
Netlink的通信模型和socket通信非常相似,主要要点如下:

  • netlink采用自己独立的地址编码,struct sockaddr_nl;
  • 每个通过netlink发出的消息都必须附带一个netlink自己的消息头,struct nlmsghdr;
  • 内核态的netlink的操作API和用户态完全不一样,后面再介绍;
  • 用户态的netlink操作完成采用socket函数,非常方便和简单,有TCP/UDP socket编程基础的非常容易上手。

Netlink的通信地址和协议

所有socket之间的通信,必须有个地址结构,Netlink也不例外。我们最熟悉的就是IPV4的地址了,netlink的地址结构如下:

  1. struct sockaddr_nl
  2. {
  3. sa_family_t nl_family;          //必须为AF_NETLINK或者PF_NETLINK
  4. unsigned short  nl_pad;             //必须为0
  5. __u32       nl_pid;             //通信端口
  6. __u32       nl_groups;              //组播掩码
  7. };

上面几个数据,最关键的是nl_family(就对应IP通信中的AF_INET)和nl_pid。

nl_pid就是一个约定的通信端口,用户态使用的时候需要用一个非0的数字,一般来
说可以直接采用上层应用的进程ID(不用进程ID号码也没事,只要系统中不冲突的一个数字即可使用)。对于内核的地址,该值必须用0,也就是说,如果上层
通过sendto向内核发送netlink消息,peer addr中nl_pid必须填写0。

nl_groups用于一个消息同时分发给不同的接收者,是一种组播应用,本文不讲组播应用。

本质上,nl_pid就是netlink的通信地址。除了通信地址,netlink还提供“协议”来标示通信实体,在创建socket的时候,需要指定
netlink的通信协议号。每个协议号代表一种“应用”,上层可以用内核已经定义的协议和内核进行通信,获得内核已经提供的信息。具体支持的协议列表如
下:

  1. #define NETLINK_ROUTE       0   /* Routing/device hook              */
  2. #define NETLINK_UNUSED      1   /* Unused number                */
  3. #define NETLINK_USERSOCK    2   /* Reserved for user mode socket protocols  */
  4. #define NETLINK_FIREWALL    3   /* Firewalling hook             */
  5. #define NETLINK_INET_DIAG   4   /* INET socket monitoring           */
  6. #define NETLINK_NFLOG       5   /* netfilter/iptables ULOG */
  7. #define NETLINK_XFRM        6   /* ipsec */
  8. #define NETLINK_SELINUX     7   /* SELinux event notifications */
  9. #define NETLINK_ISCSI       8   /* Open-iSCSI */
  10. #define NETLINK_AUDIT       9   /* auditing */
  11. #define NETLINK_FIB_LOOKUP  10
  12. #define NETLINK_CONNECTOR   11
  13. #define NETLINK_NETFILTER   12  /* netfilter subsystem */
  14. #define NETLINK_IP6_FW      13
  15. #define NETLINK_DNRTMSG     14  /* DECnet routing messages */
  16. #define NETLINK_KOBJECT_UEVENT  15  /* Kernel messages to userspace */
  17. #define NETLINK_GENERIC     16
  18. /* leave room for NETLINK_DM (DM Events) */
  19. #define NETLINK_SCSITRANSPORT   18  /* SCSI Transports */
  20. #define NETLINK_ECRYPTFS    19

协议的用途很好理解,比如我们单纯创建一个上层应用,通过和
NETLINK_ROUTE协议通信,可以获得内核的路由信息。我需要利用netlink创建一个我自己的通信协议,因此我定义了一种新的协议。新协议的
定义不能和内核已经定义的冲突,同时不能超过MAX_LINKS这个宏的限定,MAX_LINKS = 32。所以我定义的协议号为30。

小结:netlink采用协议号+通信端口的方式构建自己的地址体系。

用户态操作netlink socket

用户态创建netlink socket的基本过程和操作其他socket的API一模一样,区别就2点:
1、 netlink有自己的地址;
2、 netlink接收到的消息带一个netlink自己的消息头;

用户态创建、销毁socket的过程:
1、 用socket函数创建,socket(PF_NETLINK,
SOCK_DGRAM,
NETLINK_XXX);第一个参数必须是PF_NETLINK或者AF_NETLINK,第二个参数用SOCK_DGRAM和SOCK_RAW都没问
题,第三个参数就是netlink的协议号。
2、 用bind函数绑定自己的地址。
3、 用close关闭套接字。

创建socket的代码样例:

  1. {
  2. struct sockaddr_nl addr;
  3. int flags;
  4. //建立netlink socket
  5. s_nlm_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_XXX);
  6. if(s_nlm_socket < 0)
  7. {
  8. USE_DBG_OUT("create netlink socket error.\r\n");
  9. goto Err_Exit;
  10. }
  11. //bind
  12. addr.nl_family = PF_NETLINK;
  13. addr.nl_pad    = 0;
  14. addr.nl_pid    = getpid();
  15. addr.nl_groups = 0;
  16. if(bind(s_nlm_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0)
  17. {
  18. USE_DBG_OUT("bind socket error.\r\n");
  19. goto Err_Exit;
  20. }
  21. //设置socket为非阻塞模式
  22. flags = fcntl(s_nlm_socket, F_GETFL, 0);
  23. fcntl(s_nlm_socket, F_SETFL, flags|O_NONBLOCK);
  24. return 0;
  25. Err_Exit:
  26. return -1;
  27. }

用户态接收、发送消息的API:
用户态用sendto向内核发送netlink消息,用recvfrom接收消息。只是注意,发送、接收的时候在自己附带的消息前面要加上一个netlink的消息头。例如,定义一个如下的消息通信结构:

  1. struct tag_rcv_buf
  2. {
  3. struct nlmsghdr hdr;            //netlink的消息头
  4. netlink_notify_s my_msg;        //通信实体消息
  5. }st_snd_buf;

发送代码的例子:

  1. My_send_msg
  2. {
  3. struct tag_rcv_buf
  4. {
  5. struct nlmsghdr hdr;            //netlink的消息头
  6. netlink_notify_s my_msg;        //通信实体消息
  7. }st_snd_buf;
  8. fd_set st_write_set;                         //select fd,避免线程吊死
  9. struct timeval write_time_out = {10, 0};     //10秒超时
  10. int ret;
  11. //设置select
  12. FD_ZERO(&st_write_set);
  13. FD_SET(s_nlm_socket, &st_write_set);
  14. /*
  15. 设置发送数据
  16. */
  17. st_snd_buf.hdr.nlmsg_len   = sizeof(st_snd_buf);        //NLMSG_LENGTH(sizeof(netlink_notify_s))--这个宏包含有头
  18. st_snd_buf.hdr.nlmsg_flags = 0;                         /*消息的附加选项,没啥用*/
  19. st_snd_buf.hdr.nlmsg_type  = 0;                         /*设置自定义消息类型*/
  20. st_snd_buf.hdr.nlmsg_pid   = getpid();                  /*设置发送者的PID*/
  21. st_snd_buf.my_msg.start_pack_id = s_id;
  22. st_snd_buf.my_msg.end_pack_id   = e_id;
  23. ret = select(s_nlm_socket+1, NULL, &st_write_set, NULL, &write_time_out);
  24. if(ret == -1)
  25. {
  26. //have some error.
  27. USE_DBG_OUT("send has some error %d.\n", errno);
  28. goto out;
  29. }
  30. else if(ret == 0)
  31. {
  32. //超时退出
  33. TMP_DBG_OUT("send timeout.\n");
  34. goto out;
  35. }
  36. else
  37. {
  38. //接收消息
  39. ret = sendto(s_nlm_socket, &st_snd_buf, sizeof(st_snd_buf), 0,
  40. (struct sockaddr*)&s_peer_addr, sizeof(s_peer_addr));
  41. if(ret < 0)
  42. {
  43. USE_DBG_OUT("send to kernal by nl error %d\r\n", errno);
  44. }
  45. else
  46. {
  47. TMP_DBG_OUT("send to kernal ok s_id is %d, e_id is %d.\r\n", s_id, e_id);
  48. }
  49. }
  50. out:
  51. return;
  52. }

接收数据的代码例子:

  1. {
  2. struct tag_rcv_buf
  3. {
  4. struct nlmsghdr hdr;            //netlink的消息头
  5. netlink_notify_s my_msg;        //通信实体消息
  6. }st_rcv_buf;
  7. int ret, addr_len, io_ret;
  8. struct sockaddr_nl st_peer_addr;
  9. fd_set st_read_set;                         //select fd,避免线程吊死
  10. struct timeval read_time_out = {10, 0};     //10秒超时
  11. int rcv_buf;
  12. //设置内核的通信地址
  13. st_peer_addr.nl_family = AF_NETLINK;
  14. st_peer_addr.nl_pad = 0;                                   /*always set to zero*/
  15. st_peer_addr.nl_pid = 0;                                   /*kernel's pid is zero*/
  16. st_peer_addr.nl_groups = 0;                                /*multicast groups mask, if unicast set to zero*/
  17. addr_len = sizeof(st_peer_addr);
  18. //设置select
  19. FD_ZERO(&st_read_set);
  20. FD_SET(s_nlm_socket, &st_read_set);
  21. ret = select(s_nlm_socket+1, &st_read_set, NULL, NULL, &read_time_out);
  22. if(ret == -1)
  23. {
  24. //have some error.
  25. USE_DBG_OUT("select rcv some error %d", errno);
  26. goto err;
  27. }
  28. else if(ret == 0)
  29. {
  30. //超时退出
  31. TMP_DBG_OUT("rcv timeout.\n");
  32. *p_size = 0;
  33. goto out;
  34. }
  35. else
  36. {
  37. //接收消息
  38. ret = recvfrom(s_nlm_socket, &st_rcv_buf, sizeof(st_rcv_buf), 0,
  39. (struct sockaddr *)&st_peer_addr, &addr_len);
  40. }
  41. if(ret == sizeof(st_rcv_buf) )
  42. {
  43. //收到消息了...
  44. else
  45. {
  46. USE_DBG_OUT("rcv msg have some err. ret is %d, errno is %d\r\n", ret, errno);
  47. goto err;
  48. }
  49. out:
  50. return 0;
  51. err:
  52. *p_size = 0;
  53. return -1;
  54. }

netlink---Linux下基于socket的内核和上层通信机制 (转)的更多相关文章

  1. linux下基于rsync + find命令实现文件同步机制

    rsync和find是linux系统自带的命令,如果没有安装可以找到系统安装盘或者ISO文件,里面有rpm包,安装一下就可以了.       具体思路如下:             1)可以实现定时进 ...

  2. linux下TCP/IP及内核参数优化调优(转)

    Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等. 如下配置是写在sysctl.con ...

  3. C语言 linux环境基于socket的简易即时通信程序

    转载请注明出处:http://www.cnblogs.com/kevince/p/3891033.html      ——By Kevince 最近在看linux网络编程相关,现学现卖,就写了一个简易 ...

  4. Linux下基于LDAP统一用户认证的研究

    Linux下基于LDAP统一用户认证的研究                   本文出自 "李晨光原创技术博客" 博客,谢绝转载!

  5. Linux下基于.NET5开发CAX应用

    <<.NET5下的三维应用程序开发>>一文中介绍了如何在.NET5下使用AnyCAD开发应用程序.相比.NET4.x,.NET5一大进步便是可以跨平台,即可以在Linux.Ma ...

  6. Linux下unix socket 读写 抓包

    Linux下unix socket 读写 抓包-ubuntuer-ChinaUnix博客 http://blog.chinaunix.net/uid-9950859-id-247877.html

  7. Linux-PAM(Linux下的密碼認證和安全机制)系統管理員指南(中文版)

    he Linux-PAM 系统管理员指南作者:Andrew G. Morgan, morgan@linux.kernel.org翻译:孙国清(Thomas Sun),thomassun@yeah.ne ...

  8. c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码

    原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html c++ 网络编程(一)TCP/UDP  入门级客户端与服务端交互代码 网 ...

  9. c++ 网络编程(二) linux 下多进程socket通信 多个客户端与单个服务端交互代码实现回声服务器

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9612820.html 锲子-- 预备知识优雅的关闭套接字连接: 基于TCP的半关闭 TCP中的 ...

随机推荐

  1. 【安卓面试题】Activity和Task的启动模式有哪些?每种含义是什么?举例说明各自的应用场景

    Activity和Task的启动模式有哪些?每种含义是什么?举例说明各自的应用场景 Activity的启动模式 (Launchmode) 有4种 1.standard 默认模式,不需要配置 含义: 启 ...

  2. Java IO 技术之基本流类

    流式IO 流(Stream)是字节的源或目的.         两种基本的流是:输入流(Input Stream)和输出流(Output Stream).可从中读出一系列字节的对象称为输入流.而能向其 ...

  3. Oracle存储过程的调用(返回参数)

    CREATE OR REPLACE PROCEDURE test_in_out_exception (v_empno VARCHAR2,v_guess_sal NUMBER,v_true_sal OU ...

  4. Linux 进程管理子系统

    一.进程管理子系统 1.进程要素 (1). 程序与进程 程序:存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的实体 进程:是一个执行中的程序,他是一个动态的实体. (2). 进程4要素 1.有 ...

  5. Matlab之矩阵

    1.新建矩阵 A = zeros(5,5); 2.矩阵赋值 A(:,j) = [5  5]表示取A矩阵的第j列全部元素 a.矩阵的同行元素之间用空格(或”,”)隔开: b.矩阵的行与行之间用”;”(或 ...

  6. char*,const char*和string 三者转换

    1. const char* 和string 转换 (1) const char*转换为 string,直接赋值即可. EX: const char* tmp = "tsinghua&quo ...

  7. C#如何关闭一个窗口的同时打开另一个窗口

    在.net的WinForm程序中,如果是直接起动的Form作为主窗口,那么这个主窗口是不能关闭的,因为它维护了一个Windows消息循环,它一旦关闭了就等于声明整个应用程序结束,所以新打开的窗口也就被 ...

  8. windows时间同步出错

    http://jingyan.baidu.com/article/fd8044faf1f7ae5030137a7d.html

  9. 用java发送email邮件例子

    package com.hzk.mail; import java.net.MalformedURLException; import java.net.URL; import java.text.S ...

  10. 【leetcode】368. Largest Divisible Subset

    题目描述: Given a set of distinct positive integers, find the largest subset such that every pair (Si, S ...