关于EventHub的学习有一点前置知识很重要,就是epoll和inotify机制,可以参考这篇博文:www.cheelok.com/_inx/88。
在前面Input系列的博文中已经学习,来自底层的Input事件经过事件枢纽EventHub处理后,让InputReader通过epoll和inotify从EventHub中读取。那么现在就来学习EventHub是如何读取底层事件的吧。
EventHub注册设备节点的监听
EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(0), mClosingDevices(0),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
// 创建epoll对象用于监听是否有可读事件,指定最大监听个数为EPOLL_SIZE_HINT
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
// 创建inotify对象用于监听设备节点DEVICE_PATH,即/dev/input,是否有变化(设备增删),设备的增删对应着设备节点的文件增删
mINotifyFd = inotify_init();
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",
DEVICE_PATH, errno);
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = EPOLL_ID_INOTIFY;
// 将inotify对象注册到epoll中监听是否有新的可读的设备增删事件
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
// 创建管道,并将读端交给epoll,唤醒端(写端)交给InputReader,用于唤醒epoll,避免epoll阻塞在epoll_wait()中
int wakeFds[2];
result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
eventItem.data.u32 = EPOLL_ID_WAKE;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
int major, minor;
getLinuxRelease(&major, &minor);
// EPOLLWAKEUP was introduced in kernel 3.5
mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}复制代码
总结下来就是这张图:
EventHub->getEvents
按照上面的学习,我们知道:EventHub在创建时,创建了两个Fd,mEpollFd和mINotifyFd。其中mINotifyFd用于监听设备节点是否有设备文件的增删,将mINotifyFd注册到mEpollFd中,当发生新的设备增删,设备节点下的设备文件也会随之增删,就会通知mEpollFd有新的可读的设备增删事件,通知EventHub对设备进行处理。
换言之,刚创建EventHub时,mEpollFd只监听了mINotifyFd。
getEvents整个函数比较长,我就不贴所有代码了,这个函数主要做了以下事情:
- 创建一个大小为bufferSize的input_event的缓冲区,用于存储读取的Input事件
- 判断是否需要重新打开Input设备
- 处理最后被添加/删除的Input设备,其中会为了处理添加的设备而进行设备扫描
- 判断是否需要扫描设备
- 获取Input事件
- 打开/关闭在步骤3中添加/删除的Input设备
- 如果设备信息发生变化,则通知
- 唤醒epoll_wait,通知InputReader读取事件,需要注意的是,整个唤醒过程都是加锁的
对于我们来说,学习重点自然是步骤4了,但是在看它做了什么之前不妨先想想,创建EventHub之后,第一次getEvents时,mEpollFd只监听了mINotifyFd,那mEpollFd要怎么获取设备上的发生的Input事件呢?
扫描设备
我们在getEvents里面可以看到,在获取Input事件之前,会判断是否需要扫描设备:
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}复制代码
mNeedToScanDevices在创建EventHub时是默认赋值为true的,那么第一次走getEvents肯定会走进来,scanDevicesLocked最终会调用scanDirLocked扫描/dev/input。在这个函数里面就是循环扫描/dev/input下的设备文件了:
status_t EventHub::scanDirLocked(const char *dirname)
{
……
dir = opendir(dirname);
……
while((de = readdir(dir))) {
……
openDeviceLocked(devname);
}
……
}复制代码
看起来扫描到设备文件后就会openDeviceLocked,在这个函数里其实没做什么特别的事情,就是添加设备以及各种处理,有一个地方需要关注下:
// Register with epoll.
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
if (mUsingEpollWakeup) {
eventItem.events |= EPOLLWAKEUP;
}
eventItem.data.u32 = deviceId;
if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
delete device;
return -1;
}
String8 wakeMechanism("EPOLLWAKEUP");
if (!mUsingEpollWakeup) {
#ifndef EVIOCSSUSPENDBLOCK
// uapi headers don't include EVIOCSSUSPENDBLOCK, and future kernels
// will use an epoll flag instead, so as long as we want to support
// this feature, we need to be prepared to define the ioctl ourselves.
#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
#endif
if (ioctl(fd, EVIOCSSUSPENDBLOCK, 1)) {
wakeMechanism = "";
} else {
wakeMechanism = "EVIOCSSUSPENDBLOCK";
}
}复制代码
这里我们将新设备的fd注册到mEpollFd中进行监听,并且写入E唤醒epoll。于是前面的问题就解决了,由于每次获取Input事件前都会更新设备信息,因此mEpollFd能监听到最新的设备fd。
获取Input事件
获取Input事件的整个流程代码很多,我就不贴了,主要做了以下事情:
- 循环读取mPendingEventItems中的eventItem
- 判断eventItem是否为合法的inotify类型(eventItem.data.u32为EPOLL_ID_INOTIFY,且eventItem.events & EPOLLIN为true),如果是,说明有新的设备增删事件,则需要更新设备列表信息
- 判断eventItem是否为合法的唤醒管道读端的事件(eventItem.data.u32为EPOLL_ID_WAKE),若合法,则唤醒管道的读端,也就是InputReader
- 判断eventItem的接收设备是否合法
- 如果eventItem不属于EPOLL_ID_INOTIFY、EPOLL_ID_WAKE类型,且是epoll的输入事件(EPOLLIN),则开始事件读取的逻辑
事件读取的逻辑里做了以下事情:
- 首先通过设备的readSize判断设备是否被移除、设备读取的事件总大小是否为input_event倍数(是否符合读取格式)、readSize是否合法,通过以上检查则获取设备id
- 循环读取readBuffer中的input_event
- 对input_event的发生时间进行处理,存储到RawEvent的when中
- 将deviceId、type、code、value封装到RawEvent中
于是EventHub就成功地得到了来自设备的事件,并成功将它们转化为RawEvent,交给InputReader。
至此,本文结束。
题外话
如果你觉得我的分享有帮助到你的话,请我吃个零食/喝杯咖啡呗~