input之我见05——EVENTHUB兼谈EPOLL机制

input之我见05——EVENTHUB兼谈EPOLL机制

1,引子

Input事件从Kernel层传到用户层,传输节点通过/dev/input/下面的节点:
  1. p253:/dev/input # ls -al
  2. crw-rw---- 1 root input 13, 64 2015-01-01 00:00 event0
  3. crw-rw---- 1 root input 13, 65 2015-01-01 00:00 event1
  4. crw-rw---- 1 root input 13, 66 2015-01-01 00:00 event2
  5. crw-rw---- 1 root input 13, 63 2015-01-01 00:00 mice
从Kernel层的INPUT子系统层面,各个INPUT设备完成初始化之后,都是通过input_event()这个函数“上报”输入事件。深究这个“上报”,我们发现是input_event()往/dev/input/下面的某个event的节点里,按一定结构写数据包(type,code,value),而用户层是有专门的线程监听这个节点,一旦有数据更新,就立马读取,传传递给其它线程进一步加工处理。
从第一讲中的草图可以看到这些信息。
这里面讲的监听线程就是EventHub,而实现这种读取传递的机制就是EPOLL机制。这两个东西就是今天需要讲的。

2,EventHub的前世今生
2.1,构造
Android7.0 Input事件处理这篇文章可以知道,EventHub是在InputReader构造的时候,传进入去的参数。那么EventHub的构造是在哪里?再继续往上跟踪可以发现,EventHub的构造是在com_android_server_input_InputManagerService.cpp的NativeInputManager方法中
  1. NativeInputManager::NativeInputManager(jobject contextObj,
  2. jobject serviceObj, const sp<Looper>& looper) :
  3. mLooper(looper), mInteractive(true) {
  4. ...
  5. sp<EventHub> eventHub = new EventHub();
  6. mInputManager = new InputManager(eventHub, this, this);
  7. }
2.2,入口
同样在上一篇文章中,EventHub从InputMangaer的构造中传入,又作为参数传给了InputReader
  1. InputManager::InputManager(
  2. const sp<EventHubInterface>& eventHub,
  3. const sp<InputReaderPolicyInterface>& readerPolicy,
  4. const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
  5. mDispatcher = new InputDispatcher(dispatcherPolicy);
  6. mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
  7. initialize();
  8. }
这里的EventHubInterface是EventHub的父类,所以这类型也没问题。InputReader在自己的线程执行函数中,通过这个eventHub来获取各个Input设备的数据。
  1. ...
  2. size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
  3. ...
参数timeoutMillis,设置一个超时时间;
参数mEventBuffer,是一个RawEvent数组;
  1. // The event queue.
  2. static const int EVENT_BUFFER_SIZE = 256;
  3. RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
参数EVENT_BUFFER_SIZE,即buffer大小。
3,EventHub的构造
先看一下相关类图
 再看代码实现
  1. EventHub::EventHub(void) {
  2. ...
  3. //获得epoll fd和inotify fd
  4. mEpollFd = epoll_create(EPOLL_SIZE_HINT);
  5. mINotifyFd = inotify_init();
  6. //通过inotifyFd监听/dev/input这个目录
  7. int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
  8. //实例化epoll_event结构体
  9. struct epoll_event eventItem;
  10. memset(&eventItem, 0, sizeof(eventItem));
  11. eventItem.events = EPOLLIN;
  12. eventItem.data.u32 = EPOLL_ID_INOTIFY;
  13. //设置notify FD
  14. result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
  15. //初始化PipeFD,并设置PipeFd

  16. result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
这里面主要是初始化了一些EPOLL机制,这也就进入我这篇文章的第二个主机,EPOLL机制
4,EPOLL机制
4.1,Linux IO操作模型
说到EPOLL机制,不得不提到Linux的IO操作模型,大概有如下几种模型
  • 同步阻塞IO
  • 同步非阻塞IO
  • 多路复用IO
  • 异步IO
%u5728%u8FD9%u91CC%u8F93%u5165%u56FE%u7247%u6807%u9898
IO模型 定义 过程图示 数据等待阶段 数据复制阶段 优点 缺点
同步阻塞IO  
%u5728%u8FD9%u91CC%u8F93%u5165%u56FE%u7247%u6807%u9898
BLOCK BLOCK 能够及时返回数据,无延迟;  
同步非阻塞IO 同步非阻塞就是
“每隔一会儿瞄一眼进度条” 的轮询(polling)方式

%u5728%u8FD9%u91CC%u8F93%u5165%u56FE%u7247%u6807%u9898
NON-BLOCK BLOCK 能够在等待任务完成的时间里干其他活了 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作
多路复用IO UNIX/Linux
下的 select、poll、epoll

%u5728%u8FD9%u91CC%u8F93%u5165%u56FE%u7247%u6807%u9898
NON-BLOCK BLOCK 监听多个Iofd  
异步IO 用户进程发起aio_read操作之后,立刻就可以开始去做其它的事,当这一切都完成之后,kernel会给用户进程发送一个signal或执行一个基于线程的回调函数来完成这次
IO 处理过程

%u5728%u8FD9%u91CC%u8F93%u5165%u56FE%u7247%u6807%u9898
       
EventHub使用的EPOLL机制是一种多路利用IO模型,习惯把这种模型归类为同步IO操作,其实无所以,来具体看看这种机制

4.2,EPOLL模型
看看相关的数据结构
3种类型
  1. /* Valid opcodes to issue to sys_epoll_ctl() */
  2. #define EPOLL_CTL_ADD 1
  3. #define EPOLL_CTL_DEL 2
  4. #define EPOLL_CTL_MOD 3
epoll_event数据结构
  1. struct epoll_event {
  2. __u32 events;
  3. __u64 data;
  4. } EPOLL_PACKED;
通知的两种类型
  1. // Ids used for epoll notifications not associated with devices.
  2. static const uint32_t EPOLL_ID_INOTIFY = 0x80000001;
  3. static const uint32_t EPOLL_ID_WAKE = 0x80000002;
重要的方法
  1. intepoll_create(int size);
  2. 生成一个Epoll专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个Epoll fd上能关注的最大socket fd数,大小自定,只要内存足够。
  3. intepoll_ctl(int epfd, intop, int fd, structepoll_event *event);
  4. 控制某个Epoll文件描述符上的事件:注册、修改、删除。其中参数epfdepoll_create()创建Epoll专用的文件描述符。相对于select模型中的FD_SETFD_CLR宏。
  5. intepoll_wait(int epfd,structepoll_event * events,int maxevents,int timeout);
  6. 等待I/O事件的发生;参数说明:
  7. epfd:由epoll_create() 生成的Epoll专用的文件描述符;
  8. epoll_event:用于回传代处理事件的数组;
  9. maxevents:每次能处理的事件数;
  10. timeout:等待I/O事件发生的超时值;
  11. 返回发生事件数。
回到前面,在EventHub构造函数中,设置Epoll的最大数,获得了epoll_fd,之后,每打开一个Input设置,都会把这个设备的fd注册进epoll里面去
openDevLocked();
  1. // Register with epoll.
  2. struct epoll_event eventItem;
  3. memset(&eventItem, 0, sizeof(eventItem));
  4. eventItem.events = EPOLLIN;

  5. eventItem.data.u32 = deviceId;
  6. if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
  7. ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
  8. delete device;
  9. return -1;
  10. }
epoll_event只表明某个设备上有事件,并不包含事件内容,具体事件内容需要通过read来读取。
getEvents();
  1. //在调用epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);之后,读到的总event_poll保存在mPendingEventCount,mPendingEventIndex初始化为0,所以这里面的逻辑是一个个取出来
  2. while (mPendingEventIndex < mPendingEventCount) {
  3. const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
  4. //EPOLL_ID_INOTIFY类型事件
  5. if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
  6. if (eventItem.events & EPOLLIN) {
  7. mPendingINotify = true;
  8. } else {
  9. ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
  10. }
  11. continue;
  12. }
  13. //EPOLL_ID_WAKE事件
  14. if (eventItem.data.u32 == EPOLL_ID_WAKE) {
  15. if (eventItem.events & EPOLLIN) {
  16. ALOGV("awoken after wake()");
  17. awoken = true;
  18. char buffer[16];
  19. ssize_t nRead;
  20. do {
  21. //进行数据读取
  22. nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
  23. } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
  24. } else {
  25. ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
  26. eventItem.events);
  27. }
  28. continue;
  29. }
  30. ...
  31. Device* device = mDevices.valueAt(deviceIndex);
  32. if (eventItem.events & EPOLLIN) {
  33. int32_t readSize = read(device->fd, readBuffer,
  34. sizeof(struct input_event) * capacity);
  35. ...
  36. } else {
  37. int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
  38. //开始解析Buffuer
  39. size_t count = size_t(readSize) / sizeof(struct input_event);
  40. for (size_t i = 0; i < count; i++) {
  41. struct input_event& iev = readBuffer[i];
  42. ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
  43. device->path.string(),
  44. (int) iev.time.tv_sec, (int) iev.time.tv_usec,
  45. iev.type, iev.code, iev.value);
  46. // Some input devices may have a better concept of the time
  47. // when an input event was actually generated than the kernel
  48. // which simply timestamps all events on entry to evdev.
  49. // This is a custom Android extension of the input protocol
  50. // mainly intended for use with uinput based device drivers.
  51. if (iev.type == EV_MSC) {
  52. if (iev.code == MSC_ANDROID_TIME_SEC) {
  53. device->timestampOverrideSec = iev.value;
  54. continue;
  55. } else if (iev.code == MSC_ANDROID_TIME_USEC) {
  56. device->timestampOverrideUsec = iev.value;
  57. continue;
  58. }
  59. }
  60. if (device->timestampOverrideSec || device->timestampOverrideUsec) {
  61. iev.time.tv_sec = device->timestampOverrideSec;
  62. iev.time.tv_usec = device->timestampOverrideUsec;
  63. if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
  64. device->timestampOverrideSec = 0;
  65. device->timestampOverrideUsec = 0;
  66. }
  67. ALOGV("applied override time %d.%06d",
  68. int(iev.time.tv_sec), int(iev.time.tv_usec));
  69. }
  70. // Use the time specified in the event instead of the current time
  71. // so that downstream code can get more accurate estimates of
  72. // event dispatch latency from the time the event is enqueued onto
  73. // the evdev client buffer.
  74. //
  75. // The event's timestamp fortuitously uses the same monotonic clock
  76. // time base as the rest of Android. The kernel event device driver
  77. // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
  78. // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
  79. // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
  80. // system call that also queries ktime_get_ts().
  81. event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
  82. + nsecs_t(iev.time.tv_usec) * 1000LL;
  83. ALOGV("event time %" PRId64 ", now %" PRId64, event->when, now);
  84. // Bug 7291243: Add a guard in case the kernel generates timestamps
  85. // that appear to be far into the future because they were generated
  86. // using the wrong clock source.
  87. //
  88. // This can happen because when the input device is initially opened
  89. // it has a default clock source of CLOCK_REALTIME. Any input events
  90. // enqueued right after the device is opened will have timestamps
  91. // generated using CLOCK_REALTIME. We later set the clock source
  92. // to CLOCK_MONOTONIC but it is already too late.
  93. //
  94. // Invalid input event timestamps can result in ANRs, crashes and
  95. // and other issues that are hard to track down. We must not let them
  96. // propagate through the system.
  97. //
  98. // Log a warning so that we notice the problem and recover gracefully.
  99. ...
  100. event->deviceId = deviceId;
  101. event->type = iev.type;
  102. event->code = iev.code;
  103. event->value = iev.value;
  104. event += 1;
  105. capacity -= 1;
  106. }
  107. if (capacity == 0) {
  108. // The result buffer is full. Reset the pending event index
  109. // so we will try to read the device again on the next iteration.
  110. mPendingEventIndex -= 1;
  111. break;
  112. }
  113. }
  114. }
  115. ...
这样就把数据读出来了

参考:
Comments are closed.
TOP