linux用户空间与内核空间通信——Netlink通信机制
一般来说用户空间和内核空间的通信方式有三种:/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 5static 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 5struct 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#include#include#include#include#include//该头文件需要放在netlink.h前面防止编译出现__kernel_sa_family未定义 #include#includevoid 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; }