内核中使用uevent事件通知用户空间,uevent首先在内核中调用netlink_kernel_create()函数创建一个socket套接字,该函数原型在netlink.h有定义,其类型是表示往用户空间发送消息的NETLINK_KOBJECT_UEVENT,groups=1,由于uevent只往用户空间发送消息而不接受,因此其输入回调函数input和cb_mutex都设置为NULL。
#include
struct sock *netlink_kernel_create(struct net *net,int unit,unsigned int groups,
void (*input)(struct sk_buff *skb),
struct mutex *cb_mutex,
struct module *module);
ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT,
1, NULL, NULL, THIS_MODULE);
当有事件发生的时候,调用 kobject_uevent()函数,实际上最终是调用
netlink_broadcast_filtered(uevent_sock, skb , 0, 1, GFP_KERNEL ,
kobj_bcast_filter, kobj);
完成广播任务。
用户空间程序只需要创建一个socket描述符,将描述符绑定到接收地址,就可以实现热拔插事件的监听了。
点击(此处)折叠或打开
- #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
- 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;
- sockfd=socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);
- if(sockfd==-1)
- printf("socket creating failed:%s\n",strerror(errno));
- if(bind(sockfd,(struct sockaddr *)&sa,sizeof(sa))==-1)
- printf("bind error:%s\n",strerror(errno));
- 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报头,包括指定接收缓存等工作。该函数会阻塞直到有热拔插事件产生。
运行程序,然后我插入一个U盘,得到下面的结果:
$ ./netlink
received 289 bytes
add@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1
SUBSYSTEM=usb
MAJOR=189
MINOR=8
DEVNAME=bus/usb/001/009
DEVTYPE=usb_device
DEVICE=/proc/bus/usb/001/009
PRODUCT=781/5530/100
TYPE=0/0/0
BUSNUM=001
DEVNUM=009
SEQNUM=2306
运行程序,拔掉U盘
$ ./netlink
received 294 bytes
remove@/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1/1-1.1:1.0/host10/target10:0:0/10:0:0:0/bsg/10:0:0:0
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.1/1-1.1:1.0/host10/target10:0:0/10:0:0:0/bsg/10:0:0:0
SUBSYSTEM=bsg
MAJOR=253
MINOR=2
DEVNAME=bsg/10:0:0:0
SEQNUM=2345
程序正确地接收到了U盘热拔插事件,通过该信息用户程序可以在第一时间得到事件通知。事实上热拔插的时候产生的消息可不止一条呢,可以在revmsg的时候用一个循环接收更多的消息。