一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,但是Netlink可以实现双工通信。
1、Netlink socket的作用:
Netlink socket 是一种Linux特有的socket,用于实现用户进程与内核进程之间通信的一种特殊的进程间通信方式(IPC) ,也是网络应用程序与内核通信的最常用的接口。
Netlink 是一种在内核和用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就能使用 Netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 Netlink。
2、内核预定义的协议类型
H Code \kernel-4.14\include\uapi\linux\netlink.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | | #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unused number */ #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ #define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */ #define NETLINK_SOCK_DIAG 4 /* socket monitoring */ #define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ #define NETLINK_XFRM 6 /* ipsec */ #define NETLINK_SELINUX 7 /* SELinux event notifications */ #define NETLINK_ISCSI 8 /* Open-iSCSI */ #define NETLINK_AUDIT 9 /* auditing */ #define NETLINK_FIB_LOOKUP 10 #define NETLINK_CONNECTOR 11 #define NETLINK_NETFILTER 12 /* netfilter subsystem */ #define NETLINK_IP6_FW 13 #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ #define NETLINK_GENERIC 16 /* leave room for NETLINK_DM (DM Events) */ #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ #define NETLINK_ECRYPTFS 19 #define NETLINK_RDMA 20 #define NETLINK_CRYPTO 21 /* Crypto layer */ #define NETLINK_SMC 22 /* SMC monitoring */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
#define MAX_LINKS 32
struct sockaddr_nl { __kernel_sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID */ __u32 nl_groups; /* multicast groups mask */ };
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 port ID */ }; |
对于每一个netlink协议类型,能有多达 32多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多拨消息的应用而言,大大地降低了系统调用的次数。
3.Netlink应用举例:热插拔监控
SD卡、USB、网线等设备的使用过程中都可以热插拔。新的Linux内核使用udev代替了hotplug作为热拔插管理,虽然有udevd管理热拔插,但有时候我们还是需要在应用程序中检测热拔插事件以便快速地处理,比如在读写SD卡的时候拔下SD卡,那么需要立即检测出该情况,然后结束读写线程,防止VFS崩溃。Netlink是面向数据包的服务,为内核与用户层搭建了一个高速通道,是udev实现的基础。该工作方式是异步的,用户空间程序不必使用轮询等技术来检测热拔插事件。
3.1.内核空间
在内核中使用 uevent 事件通知用户空间。uevent首先在内核中调用 netlink_kernel_create()函数创建一个socket套接字,该函数原型在netlink.h有定义,其类型是表示往用户空间发送消息的NETLINK_KOBJECT_UEVENT,groups=1。由于uevent只往用户空间发送消息而不接受,因此其输入回调函数input和cb_mutex都没有设置。
H Code \kernel-4.14\include\linux\netlink.h
1 2 3 4 5 | | static inline struct sock * netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) { return __netlink_kernel_create(net, unit, THIS_MODULE, cfg); } |
C Code
1 2 3 4 5 | | <lib/kobject_uevent.c> struct netlink_kernel_cfg my_cfg .groups = 1, //groups=1表示多播 }; static struct sock *my_socket = netlink_kernel_create(&init_net,NETLINK_KOBJECT_UEVENT, &my_cfg); |
3.2.用户空间
处于用户空间的程序使用标准的socket API:socket(), bind(), sendmsg(), recvmsg() 和 close() 就能非常容易地使用 netlink socket。
C Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <asm/types.h> //该头文件需要放在netlink.h前面防止编译出现__kernel_sa_family未定义 #include <sys/socket.h> #include <linux/netlink.h> void MonitorNetlinkUevent() { int sockfd; struct sockaddr_nl sa; int len; char buf[4096]; struct iovec iov; struct msghdr msg; int i; memset(&sa,0,sizeof(sa)); sa.nl_family=AF_NETLINK; sa.nl_groups=NETLINK_KOBJECT_UEVENT; sa.nl_pid = 0;//getpid(); both is ok //①、创建一个socket描述符 sockfd=socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT); if(sockfd==-1) printf("socket creating failed:%s\n",strerror(errno)); //②、将描述符绑定到接收地址, 函数 bind() 用于把一个打开的 netlink socket 和 netlink 源 socket 地址绑定在一起 if(bind(sockfd,(struct sockaddr *)&sa,sizeof(sa))==-1) printf("bind error:%s\n",strerror(errno)); memset(&msg,0,sizeof(msg)); iov.iov_base=(void *)buf; iov.iov_len=sizeof(buf); msg.msg_name=(void *)&sa; msg.msg_namelen=sizeof(sa); msg.msg_iov=&iov; msg.msg_iovlen=1; //③开始接收uevent len=recvmsg(sockfd,&msg,0); if(len<0) printf("receive error\n"); else if(len<32||len>sizeof(buf)) printf("invalid message"); for(i=0;i<len;i++) if(*(buf+i)=='\0') buf[i]='\n'; printf("received %d bytes\n%s\n",len,buf); } int main(int argc,char **argv) { MonitorNetlinkUevent(); return 0; } |
创建socket描述符的时候指定协议族为 AF_NETLINK 或者 PF_NETLINK, 套接字type选择 SOCK_RAW 或者 SOCK_DGRAM,Netlink协议并不区分这两种类型,第三个参数协议填充 NETLINK_KOBJECT_UEVENT表示接收内核uevent信息。接着就绑定该文件描述
符到sockadd_nl, 注意该结构体nl_groups是接收掩码,取~0是将接收所有来自内核的消息,我们接收热拔插只需要填NETLINK_KOBJECT_UEVENT即可。接下来调用recvmsg开始接收内核消息,recvmsg函数需要我们填充message报头,包括指定接收缓存等工作。该函数会阻塞直到有热拔插事件产生。
4、相对于系统调用,ioctl及proc文件系统Netlink 具有哪些好处?
(1)、为了使用Netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可。
比如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就能即时通过 socket API 使用该 netlink协议类型进行数据交换。但系统调用需要增加新的系统调用;ioctl 则需要增加设备或文件, 那需要不少代码;proc文件系统则需要在 proc 下添加新的文件或目录,那将使本来就混乱的 proc 更加混乱。
(2)、netlink是一种异步通信机制,在内核和用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不必等待接收者收到消息,但系统调用和 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
(3)、使用 netlink 的内核部分能采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,他无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
(4)、netlink 支持多播,内核模块或应用能把消息多播给一个netlink组,属于该neilink 组的所有内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,所有对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在后面的文章中将介绍这一机制的使用。
(5)、内核能使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
(6)、netlink 使用标准的 socket API,因此非常容易使用,但系统调用和 ioctl则需要专门的培训才能使用。