Netlink实现Linux内核与用户空间通信

来源:互联网 发布:python 模拟访问ip 编辑:程序博客网 时间:2024/05/21 10:04

Netlink实现Linux内核与用户空间通信

文章分类 : C语言, Linux, 应用与编程, 网络

Linux内核与用户空间通信的方式目前主要有9种,分别是内核启动参数、模块参数与 sysfs、sysctl、系统调用、netlink、procfs、seq_file、debugfs和relayfs。Netlink是一种特殊的文件描述符套结字),为2.6.14及更高版本的Linux所特有,是一种在内核与用户应用间进行双向数据交互的便捷方式,用户态应用调用标准的套结字API 就可以使用netlink提供的强大功能,内核态需要调用专门的内核 API 来使用netlink。Netlink应用已经相当广泛,例如,Linux系统的网络防火墙分为内核态的netfilter和用户态的iptables,netfilter与iptables的数据交换就是通过Netlink机制来完成。

一、采用Netlink进行内核与用户空间交互的优势

Netlink相对于系统调用、ioctl以及proc文件系统等其他内核与用户空间的数据交换方式而言具有以下优点:

1、使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过套结字API使用该 netlink 协议类型进行数据通信。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 目录更加混乱。

2、netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度速度。

3、使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。

4、netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件。

5、内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。

6、netlink 使用标准的套结字API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。

二、Netlink用户空间调用方式

用户态应用使用标准的socket API, socket(), bind(), sendmsg(), recvmsg() ,recv()和 close() 就能很容易地使用 netlink socket,查询手册页可以了解这些函数的使用细节,本文只是讲解使用 netlink 的用户应该如何使用这些函数。注意,使用 netlink 的应用必须包含头文件linux/netlink.h。当然 socket 需要的头文件也必不可少,sys/socket.h。

为了创建一个 netlink socket,用户需要使用如下参数调用 socket():

view plaincopy to clipboardprint?
  1. int ntSocket;  
  2. ntSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);  

第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM,第三个参数指定netlink协议类型,NETLINK_KOBJECT_UEVENT意为“内核事件向用户态通知”,绑定该协议创建的ntSocket可以接收到来自内核的通知事件(如USB热插拔)。

内核预定义的协议类型有:

view plaincopy to clipboardprint?
  1. #define NETLINK_ROUTE 0 //路由守护进程  
  2. #define NETLINK_W1 1 //1-wire 子系统  
  3. #define NETLINK_USERSOCK 2 //用户态套结字协议  
  4. #define NETLINK_FIREWALL 3 //防火墙  
  5. #define NETLINK_INET_DIAG 4 //套结字监视  
  6. #define NETLINK_NFLOG 5 //网络数据过滤日志  
  7. #define NETLINK_XFRM 6 //ipsec 安全策略  
  8. #define NETLINK_SELINUX 7 //SELinux 事件通知  
  9. #define NETLINK_ISCSI 8 //iSCSI网络存储子系统  
  10. #define NETLINK_AUDIT 9 //进程审计  
  11. #define NETLINK_FIB_LOOKUP 10 //转发信息表查询  
  12. #define NETLINK_CONNECTOR 11 //netlink连接器  
  13. #define NETLINK_NETFILTER 12 //网络数据过滤系统  
  14. #define NETLINK_IP6_FW 13 //IPv6 防火墙  
  15. #define NETLINK_DNRTMSG 14 //DECnet路由信息  
  16. #define NETLINK_KOBJECT_UEVENT 15 //内核事件向用户态通知  
  17. #define NETLINK_GENERIC 16 //通用netlink(用户自定义功能)  

对于每一个netlink协议类型,可以有多达 32多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多拨消息的应用而言,大大地降低了系统调用的次数。

函数 bind() 用于把一个打开的 netlink socket 与 netlink 源 socket 地址绑定在一起。netlink socket 的地址结构如下:

view plaincopy to clipboardprint?
  1. struct sockaddr_nl {  
  2.     sa_family_t nl_family;  
  3.     unsigned short nl_pad;  
  4.     __u32 nl_pid;  
  5.     __u32 nl_groups;  
  6. };  

字段 nl_family 必须设置为 AF_NETLINK 或着 PF_NETLINK,字段 nl_pad 当前没有使用,因此要总是设置为 0,字段 nl_pid 为接收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。字段 nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组。

view plaincopy to clipboardprint?
  1. struct timeval timeout;  
  2. memset(&client, 0, sizeof(client));  
  3. client.nl_family = AF_NETLINK;  
  4. client.nl_pid = getpid();  
  5. client.nl_groups = 1; /* receive broadcast message*/  

传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个进程的多个线程使用 netlink socket 的情况,字段 nl_pid 则可以设置为其它的值,因此字段 nl_pid 实际上未必是进程 ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段。函数 bind 的调用方式如下:

view plaincopy to clipboardprint?
  1. bind(ntSocket, (struct sockaddr*)&client, sizeof(client))  

ntSocket为前面的 socket 调用返回的文件描述符,参数 client 为 struct sockaddr_nl 类型的地址。为了接收来自ntSocket的内核通知数据,显然要监听ntSocket的可读性,当select监听到ntSocket的可读事件时,调用recv函数接收内核传递过来的通知数据即可。整个过程完整的C语言实现过程如下:

view plaincopy to clipboardprint?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <fcntl.h>  
  5. #include <sys/socket.h>  
  6. #include <linux/netlink.h>  
  7. #define UEVENT_BUFFER_SIZE 2048  
  8. int main(void)  
  9. {  
  10.     struct sockaddr_nl client;  
  11.     struct timeval tv;  
  12.     int ntSocket, rcvlen, ret;  
  13.     fd_set fds;  
  14.     int buffersize = 1024;  
  15.     ntSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);  
  16.     memset(&client, 0, sizeof(client));  
  17.     client.nl_family = AF_NETLINK;  
  18.     client.nl_pid = getpid();  
  19.     client.nl_groups = 1; /* receive broadcast message*/  
  20.     setsockopt(ntSocket, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));  
  21.     bind(ntSocket, (struct sockaddr*)&client, sizeof(client));  
  22.     while (1) {  
  23.         char buf[UEVENT_BUFFER_SIZE] = { 0 };  
  24.         FD_ZERO(&fds);  
  25.         FD_SET(ntSocket, &fds);  
  26.         tv.tv_sec = 0;  
  27.         tv.tv_usec = 100 * 1000;  
  28.         ret = select(ntSocket + 1, &fds, NULL, NULL, &tv);  
  29.         if(!(ret > 0 && FD_ISSET(ntSocket, &fds)))  
  30.             continue;  
  31.         /* receive data */  
  32.         rcvlen = recv(ntSocket, &buf, sizeof(buf), 0);  
  33.         if (rcvlen > 0) {  
  34.             printf("%s\n", buf);  
  35.             // You can do something here to make the program more perfect!!!  
  36.         }  
  37.     }  
  38.     close(ntSocket);  
  39.     return 0;  
  40. }  

运行程序,测试U盘插入/拔除,输出如下:

view plaincopy to clipboardprint?
  1. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1  
  2. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0  
  3. add@/module/usb_storage  
  4. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6  
  5. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/scsi_host/host6  
  6. add@/bus/usb/drivers/usb-storage  
  7. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0  
  8. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0  
  9. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/scsi_disk/6:0:0:0  
  10. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/scsi_device/6:0:0:0  
  11. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/scsi_generic/sg2  
  12. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/bsg/6:0:0:0  
  13. change@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0  
  14. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/block/sdb  
  15. add@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/block/sdb/sdb4  
  16. add@/devices/virtual/bdi/8:16  
  17. add@/module/fat  
  18. add@/kernel/slab/fat_cache  
  19. add@/kernel/slab/fat_inode_cache  
  20. add@/module/vfat  
  21. add@/module/nls_cp437  
  22. add@/module/nls_iso8859_1  
  23. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/bsg/6:0:0:0  
  24. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/scsi_generic/sg2  
  25. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/scsi_device/6:0:0:0  
  26. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/scsi_disk/6:0:0:0  
  27. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/block/sdb/sdb4  
  28. remove@/devices/virtual/bdi/8:16  
  29. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0/block/sdb  
  30. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/target6:0:0/6:0:0:0  
  31. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6/scsi_host/host6  
  32. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host6  
  33. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0  
  34. remove@/devices/pci0000:00/0000:00:1d.7/usb2/2-1  
  35. remove@/host6/target6:0:0  
0 0
原创粉丝点击