需要在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. p45.asm

    ; ========================================== ; pmtest3.asm ; 编译方法:nasm pmtest3.asm -o pmtest3.com ; ...

  2. CF Destroying Roads (最短路)

    Destroying Roads time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...

  3. BZOJ 1827: [Usaco2010 Mar]gather 奶牛大集会 树形DP

    [Usaco2010 Mar]gather 奶牛大集会 Bessie正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会.当然,她会选择最方便的地点来举办这次集会.每个奶牛居住在 N(1 ...

  4. 移动开发Html 5前端性能优化指南

    详细内容请点击 PC优化手段在Mobile侧同样适用在Mobile侧我们提出三秒种渲染完成首屏指标基于第二点,首屏加载3秒完成或使用Loading基于联通3G网络平均338KB/s(2.71Mb/s) ...

  5. Ionic之顺带APP

    1:Ionic简介 官方:我们设计ionic来帮助 web 开发人员能够像开发网站一样开发出强大的移动APP应用. ionic是一个html5开发APP的框架,在开发运行效率可以说是最好的H5框架,把 ...

  6. Part 10 AngularJS sort rows by table header

    Here is what we want to do 1. The data should be sorted when the table column header is clicked 2. T ...

  7. Java之阶乘数的计算

    说起“阶乘数”,我们应该都不会感到陌生.当老师布置了这样的作业,我们大多数人是一贯用笔算,还有的同学会用计算机去计算.数学是讲究原理和方法的,我们知其然,也要知其所以然.下面我们就用编程来计算阶乘数. ...

  8. spark写入Oracle 报错 java.lang.ArrayIndexOutOfBoundsException: -32423

    原因: oracle 10g的驱动执行的批量提交只支持32768个参数,如果表的字段多于32个,就会出现该异常 解决办法: 升级oracle的驱动版本,换成ojdbc6.jar

  9. 20141109--SQL 练习题-1

    create database xinxiku go use xinxiku go create table Student ( Sno ) primary key, Sname ) not null ...

  10. 对象-关系Metadata映射模式

    MetaData Mapping元数据映射 在MetaData中保存object-relation映射的详细信息. 以表格形式定义映射,并可由通用代码来处理映射. 运行机制 MetaData中的信息如 ...