之前写过几篇Input系统相关的分析,但是比较零散,本篇开始准备基于Android 11代码看一下Android 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
。
1.epoll_create
int epoll_create(int size);
epoll_create
用于创建一个epoll
对象,size用来告诉内核需要监听的fd数量。
2. 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);
3. 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系统这部分代码时就非常容易了。