Android Input系统9 INotify与Epoll机制

一 概述

本篇文章来分析 Input 系统对输入事件的监听机制:Linux 下的 INotify 与 Epoll 机制,这两个机制主要是对 Input 子系统下的文件节点的增删和文件节点发生的事件进行监听。

二 INotify 机制

INotify 是 Linux 提供给用户态,监听内核文件系统变化的机制,可以监听文件/目录的增删等。INotify 的用法很简单,首先需要调用如下代码创建一个文件描述符:

int inotifyfd = inotify_init();

接着需要通过 inotify_add_watch 将我们关注的事件添加到监听:

int wd = inotify_add_watch(inotifyfd, path, event_mask)

inotify_add_watch 的第一个参数是 inotify_init 创建的文件描述符,第二个参数是要监听的路径,第三个参数是事件的类型,如文件创建 IN_CREATE,文件删除 IN_DELETE 等。

上面两步完成之后当指定路径发生了我们想要监听的事件,就会写到 inotifyfd 中,此时就可以通过 read 函数对 inotifyfd 进行读取:

 	char event_buf[512];
    int ret;
    struct inotify_event *event;
	ret = read(inotifyfd, event_buf, sizeof(event_buf));

读取到的信息封装为 inotify_event 结构体,使用 while 循环就可以将所有事件读取出来:

 while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        ......
 }

inotify_event 结构体的信息如下:

struct inotify_event {
  	__s32		wd;		/* watch descriptor */
  	__u32		mask;		/* watch mask */
  	__u32		cookie;		/* cookie to synchronize two events */
  	__u32		len;		/* length (including nulls) of name */
  	char		name[0];	/* stub for possible name */
  };

其实 INotify 的用法就三步:

  • 使用 inotify_init 创建一个 inotify 对象
  • 使用 inotify_add_watch 对文件路径进行监听
  • 使用 read 读取监听到的事件

其实 Android SDK 就提供了一个监听文件的类 FileObserver,它的底层原理就是使用的 INotify 机制,有兴趣可以去看看它内部的几个 native 函数。

接下来进入实践环节,写一个简单的 INotify 测试类,如下目录创建两个类:
在这里插入图片描述
main.cpp

// demo 代码
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <log/log.h>
int read_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    
    ALOGD("dongjiao...block read........");
    // 通过 read 函数读取目标文件路径发生的事件,没有事件则阻塞
    ret = read(fd, event_buf, sizeof(event_buf));
    // read 的返回值表示实际读取的事件大小,如果小于一个事件大小则说明读取事件失败
    if(ret < (int)sizeof(struct inotify_event)) {
        ALOGD("dongjiao...read error,could get event");
        return -1;
    }
    // 将所有事件循环取出来
    while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        ALOGD("dongjiao...event->len = :%d",event->len);
        if(event->len) {
            if(event->mask & IN_CREATE) {
                // 文件创建
                ALOGD("dongjiao...create file:%s successfully \n", event->name);
            } else {
                // 文件删除
                ALOGD("dongjiao...delete file:%s successfully \n", event->name);
            }
        }
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        event_pos += event_size;
    }
    return 0;
}


int main(int argc, char** argv) {
    // inotify 读取到一次事件就会结束,这里使用死循环读取
    while(true){
    	int inotifyFd;
    	int ret;
    	const char* path = argv[1];
        ALOGD("dongjiao...argc = :%d",argc);
    	// 初始化 inotify
    	inotifyFd = inotify_init();
    	if(inotifyFd == -1) {
        	ALOGD("dongjiao...inotify_init error!\n");
        	return -1;
    	}
    	ALOGD("dongjiao...listen target patch:%s \n", path);
        // 对目标文件路径进行监听,监听的事件是文件创建 IN_CREATE,和文件删除 IN_DELETE
    	ret = inotify_add_watch(inotifyFd, path, IN_CREATE | IN_DELETE) ;
        // 等待目标文件路径的事件发生
     	read_events(inotifyFd);
        // 删除 inotifyFd
     	if(inotify_rm_watch(inotifyFd, ret) == -1) {
        	ALOGD("dongjiao...notify_rm_watch error!\n");
        	return -1;
    	}
    	// 关闭 inotifyFd
    	close(inotifyFd);
    }
    return 0;
}

Android.bp

cc_binary {
    name: "main",
    srcs: ["main.cpp"],
    shared_libs: [
        "liblog",
    ],
}

mmm frameworks/native/services/inputflinger/temp/ 进行编译
在这里插入图片描述
成功之后 push 进手机:
在这里插入图片描述
运行可执行文件 main:
在这里插入图片描述
log 如下:
在这里插入图片描述
接着我们到 dev/input 目录下操作文件,首先创建一个文件 1.txt,
在这里插入图片描述
看下 log:
在这里插入图片描述
删除文件 log:
在这里插入图片描述
我们可以看到 INotify 的使用还是很简单的,Input 子系统就是使用 INotify 监听 dev/input/ 目录下的节点变化,后续会看到。

三 Epoll 机制

INotify 有个问题就是需要主动调用 read 函数去读取事件,这并不是 Input 系统想要的,Input 系统需要的是 INotify 监听的目标路径发生变化之后来能通知自己,而不是自己主动去读,这就需要结合另一个机制 Epoll 来实现,Epoll 是一种 I/O 多路复用技术,主要作用就是去监听 Linux 下的 fd,当这些 fd 发生事件之后,会通过回调来通知 Epoll。

Epoll 提供了三个操作函数 epoll_create,epoll_ctl,epoll_wait。

epoll_create

int epoll_create(int size);

epoll_create 用于创建一个 epoll 对象,size 用来告诉内核需要监听的 fd 数量。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)

epoll_ctl 用于对需要监听的文件描述符 (fd) 执行 op 操作,epoll_ctl 的第一个参数 epfd 就是 epoll_create 的返回值,第二个参数 op 表示对 fd 的操作方式:

EPOLL_CTL_ADD(添加)EPOLL_CTL_DEL(删除),
EPOLL_CTL_MOD(修改)

最后一个参数 event 表示要监听的具体事件,这是一个结构体 epoll_event:

  typedef union epoll_data {
  	void *ptr;
  	int fd;
  	uint32_t u32;
  	uint64_t u64;
  } epoll_data_t;
  
  struct epoll_event {
  	uint32_t events; /* Epoll 事件类型 */
  	epoll_data_t data;  /*用户数据,包含监听的 fd*/
  }

Epoll 事件类型通常有如下:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说    
的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,  
需要再次把这个socket加入到EPOLL队列里
EPOLLWAKEUP:系统会在事件排队时就保持唤醒,从epoll_wait调用开始,持续要下一次epoll_wait调用

通常 epoll_ctl 的用法就是这样:

    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);

epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_wait 用于等待事件的上报,第一个参数是 epoll_create 的返回值,第二个参数 events 是用来获取内核得到事件的集合,通常是一个 epoll_event 数组,第三个参数 maxevents 是最大事件的数量,第四个参数是超时返回时间。

Epoll 的使用步骤也很简单:

  • 通过 epoll_create 创建 epoll 对象
  • 为需要监听的 fd 构建一个 epoll_event 结构体,并注册到 epoll_ctl 进行监听。
  • 调用epoll_wait进入监听状态,传入一个epoll_event结构体数组,用于收集监听到的事件。
  • 遍历第三步的epoll_event结构体数组,依次取出事件处理。

接着进入实践环节,我们结合INotify和Epoll一起来使用:

main.cpp

//demo 代码
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <log/log.h>
int read_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    
    ALOGD("dongjiao...block read........");
    // 通过 read 函数读取目标文件路径发生的事件,没有事件则阻塞
    ret = read(fd, event_buf, sizeof(event_buf));
    // read 的返回值表示实际读取的事件大小,如果小于一个事件大小则说明读取事件失败
    if(ret < (int)sizeof(struct inotify_event)) {
        ALOGD("dongjiao...read error,could get event");
        return -1;
    }
    // 将所有事件循环取出来
    while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        ALOGD("dongjiao...event->len = :%d",event->len);
        if(event->len) {
            if(event->mask & IN_CREATE) {
                // 文件创建
                ALOGD("dongjiao...create file:%s successfully \n", event->name);
            } else {
                // 文件删除
                ALOGD("dongjiao...delete file:%s successfully \n", event->name);
            }
        }
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        event_pos += event_size;
    }
    return 0;
}


int main(int argc, char** argv) {
        // inotify 读取到一次事件就会结束,这里使用死循环读取
        while(true){
    	int inotifyFd;
    	int ret;
        int mEpollFd;
        int result;
        int EPOLL_MAX_EVENTS = 16;
        struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
    	const char* path = argv[1];
        ALOGD("dongjiao...argc = :%d",argc);
    	// 初始化 inotify
    	inotifyFd = inotify_init();
        // 初始化 epoll
        mEpollFd = epoll_create1(EPOLL_CLOEXEC);
        // 创建封装 inotifyFd 的结构体 epoll_event
        struct epoll_event eventItem = {};
        eventItem.events = EPOLLIN | EPOLLWAKEUP;
        eventItem.data.fd = inotifyFd;
        // 将 inotifyFd 添加到 epoll 进行监听
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, inotifyFd, &eventItem);
        
    	if(inotifyFd == -1) {
        	ALOGD("dongjiao...inotify_init error!\n");
        	return -1;
    	}
        ALOGD("dongjiao...listen target patch:%s \n",path);
        // 对目标文件路径进行监听,监听的事件是文件创建 IN_CREATE,和文件删除 IN_DELETE
        ret = inotify_add_watch(inotifyFd, path, IN_CREATE | IN_DELETE) ;


        ALOGD("dongjiao...epoll_wait.....");
        // 等待事件的发生,会阻塞
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -1);
        ALOGD("dongjiao...epoll event happened pollResult = :%d",pollResult);
     	
        for(auto &event:mPendingEventItems){
             if(event.data.fd == inotifyFd){
                 // 当 inotifyFd 上有事件发生,则读取事件
     	         read_events(inotifyFd);
              }
        }
        
        // 删除 inotifyFd
     	if(inotify_rm_watch(inotifyFd, ret) == -1) {
        	ALOGD("dongjiao...notify_rm_watch error!\n");
        	return -1;
    	}
    	// 关闭 inotifyFd
    	close(inotifyFd);
        }
        return 0;
}

Android.bp

cc_binary {
    name: "main",
    srcs: ["main.cpp"],
    shared_libs: [
        "liblog",
    ],
}

编译成功后 push 进手机:adb push out/target/product/bangkok_TF/system/bin/main /system/bin/

adb shell /system/bin/main dev/input运行,log如下:
在这里插入图片描述
可以看到阻塞在了epoll_wait,接着在 dev/input 下创建测试文件:
在这里插入图片描述
log如下:
在这里插入图片描述
epoll 监听到 inotifyFd 上有事件发生之后,便会从阻塞中醒来,我们此时判定当前 fd 类型为 event.data.fd == inotifyFd 便可以去读取对应事件了。

这样我们就结合 INotify 与 Epoll 机制实现了被动监听文件事件的功能,其实 Android 的 Input 子系统就是这么干的,我写的测试代码就是参考 Input 的相关实现,有了这个基础,后面再分析 Input 系统这部分代码时就非常容易理解了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值