EventHub与设备、Input事件的交互

关于EventHub的学习有一点前置知识很重要,就是epoll和inotify机制,可以参考这篇博文:http://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);
}

总结下来就是这张图:

![](http://www.cheelok.com/wp-content/uploads/2017/09/EventHub-And-Kernel-1.png)

EventHub->getEvents

按照上面的学习,我们知道:EventHub在创建时,创建了两个Fd,mEpollFd和mINotifyFd。其中mINotifyFd用于监听设备节点是否有设备文件的增删,将mINotifyFd注册到mEpollFd中,当发生新的设备增删,设备节点下的设备文件也会随之增删,就会通知mEpollFd有新的可读的设备增删事件,通知EventHub对设备进行处理。

换言之,刚创建EventHub时,mEpollFd只监听了mINotifyFd。

getEvents整个函数比较长,我就不贴所有代码了,这个函数主要做了以下事情:

  1. 创建一个大小为bufferSize的input_event的缓冲区,用于存储读取的Input事件
  2. 判断是否需要重新打开Input设备
  3. 处理最后被添加/删除的Input设备,其中会为了处理添加的设备而进行设备扫描
  4. 判断是否需要扫描设备
  5. 获取Input事件
  6. 打开/关闭在步骤3中添加/删除的Input设备
  7. 如果设备信息发生变化,则通知
  8. 唤醒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 = "<none>";
    } else {
        wakeMechanism = "EVIOCSSUSPENDBLOCK";
    }
}

这里我们将新设备的fd注册到mEpollFd中进行监听,并且写入E唤醒epoll。于是前面的问题就解决了,由于每次获取Input事件前都会更新设备信息,因此mEpollFd能监听到最新的设备fd。

获取Input事件

获取Input事件的整个流程代码很多,我就不贴了,主要做了以下事情:

  1. 循环读取mPendingEventItems中的eventItem
  2. 判断eventItem是否为合法的inotify类型(eventItem.data.u32为EPOLL_ID_INOTIFY,且eventItem.events & EPOLLIN为true),如果是,说明有新的设备增删事件,则需要更新设备列表信息
  3. 判断eventItem是否为合法的唤醒管道读端的事件(eventItem.data.u32为EPOLL_ID_WAKE),若合法,则唤醒管道的读端,也就是InputReader
  4. 判断eventItem的接收设备是否合法
  5. 如果eventItem不属于EPOLL_ID_INOTIFY、EPOLL_ID_WAKE类型,且是epoll的输入事件(EPOLLIN),则开始事件读取的逻辑

事件读取的逻辑里做了以下事情:

  1. 首先通过设备的readSize判断设备是否被移除、设备读取的事件总大小是否为input_event倍数(是否符合读取格式)、readSize是否合法,通过以上检查则获取设备id
  2. 循环读取readBuffer中的input_event
  3. 对input_event的发生时间进行处理,存储到RawEvent的when中
  4. 将deviceId、type、code、value封装到RawEvent中

于是EventHub就成功地得到了来自设备的事件,并成功将它们转化为RawEvent,交给InputReader。

至此,本文结束。

题外话

如果你喜欢看我写的技术文的话,可以关注我的公众号:Cheelok的自留地喔。

http://weixin.qq.com/r/EyguNmHEgjz9rSVV930B (二维码自动识别)

    原文作者:溜了溜了
    原文地址: https://zhuanlan.zhihu.com/p/30127752
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞