input之我见05——EVENTHUB兼谈EPOLL机制
1,引子
Input事件从Kernel层传到用户层,传输节点通过/dev/input/下面的节点:
p253:/dev/input # ls -al
crw-rw---- 1 root input 13, 64 2015-01-01 00:00 event0
crw-rw---- 1 root input 13, 65 2015-01-01 00:00 event1
crw-rw---- 1 root input 13, 66 2015-01-01 00:00 event2
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方法中
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
...
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
2.2,入口
同样在上一篇文章中,EventHub从InputMangaer的构造中传入,又作为参数传给了InputReader
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
这里的EventHubInterface是EventHub的父类,所以这类型也没问题。InputReader在自己的线程执行函数中,通过这个eventHub来获取各个Input设备的数据。
...
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
...
参数timeoutMillis,设置一个超时时间;
参数mEventBuffer,是一个RawEvent数组;
// The event queue.
static const int EVENT_BUFFER_SIZE = 256;
RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
参数EVENT_BUFFER_SIZE,即buffer大小。
3,EventHub的构造
先看一下相关类图

再看代码实现
EventHub::EventHub(void) {
...
//获得epoll fd和inotify fd
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
mINotifyFd = inotify_init();
//通过inotifyFd监听/dev/input这个目录
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
//实例化epoll_event结构体
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = EPOLL_ID_INOTIFY;
//设置notify FD
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
//初始化PipeFD,并设置PipeFd
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
这里面主要是初始化了一些EPOLL机制,这也就进入我这篇文章的第二个主机,EPOLL机制
4,EPOLL机制
4.1,Linux IO操作模型
说到EPOLL机制,不得不提到Linux的IO操作模型,大概有如下几种模型
- 同步阻塞IO
- 同步非阻塞IO
- 多路复用IO
- 异步IO

IO模型 | 定义 | 过程图示 | 数据等待阶段 | 数据复制阶段 | 优点 | 缺点 | |||||
同步阻塞IO |
|
BLOCK | BLOCK | 能够及时返回数据,无延迟; | |||||||
同步非阻塞IO | 同步非阻塞就是 “每隔一会儿瞄一眼进度条” 的轮询(polling)方式 |
|
NON-BLOCK | BLOCK | 能够在等待任务完成的时间里干其他活了 | 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作 | |||||
多路复用IO | UNIX/Linux 下的 select、poll、epoll |
|
NON-BLOCK | BLOCK | 监听多个Iofd | ||||||
异步IO | 用户进程发起aio_read操作之后,立刻就可以开始去做其它的事,当这一切都完成之后,kernel会给用户进程发送一个signal或执行一个基于线程的回调函数来完成这次 IO 处理过程 |
|
EventHub使用的EPOLL机制是一种多路利用IO模型,习惯把这种模型归类为同步IO操作,其实无所以,来具体看看这种机制
4.2,EPOLL模型
看看相关的数据结构
3种类型
/* Valid opcodes to issue to sys_epoll_ctl() */
#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3
epoll_event数据结构
struct epoll_event {
__u32 events;
__u64 data;
} EPOLL_PACKED;
通知的两种类型
// Ids used for epoll notifications not associated with devices.
static const uint32_t EPOLL_ID_INOTIFY = 0x80000001;
static const uint32_t EPOLL_ID_WAKE = 0x80000002;
重要的方法
intepoll_create(int size);
生成一个Epoll专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个Epoll fd上能关注的最大socket fd数,大小自定,只要内存足够。
intepoll_ctl(int epfd, intop, int fd, structepoll_event *event);
控制某个Epoll文件描述符上的事件:注册、修改、删除。其中参数epfd是epoll_create()创建Epoll专用的文件描述符。相对于select模型中的FD_SET和FD_CLR宏。
intepoll_wait(int epfd,structepoll_event * events,int maxevents,int timeout);
等待I/O事件的发生;参数说明:
epfd:由epoll_create() 生成的Epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;
返回发生事件数。
回到前面,在EventHub构造函数中,设置Epoll的最大数,获得了epoll_fd,之后,每打开一个Input设置,都会把这个设备的fd注册进epoll里面去
openDevLocked();
// Register with epoll.
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
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;
}
epoll_event只表明某个设备上有事件,并不包含事件内容,具体事件内容需要通过read来读取。
getEvents();
//在调用epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);之后,读到的总event_poll保存在
mPendingEventCount,mPendingEventIndex初始化为0,所以这里面的逻辑是一个个取出来while (mPendingEventIndex < mPendingEventCount) {
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
- //EPOLL_ID_INOTIFY类型事件
if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
if (eventItem.events & EPOLLIN) {
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
- //EPOLL_ID_WAKE事件
if (eventItem.data.u32 == EPOLL_ID_WAKE) {
if (eventItem.events & EPOLLIN) {
ALOGV("awoken after wake()");
awoken = true;
char buffer[16];
ssize_t nRead;
do {
- //进行数据读取
nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
eventItem.events);
}
continue;
}
- ...
- Device* device = mDevices.valueAt(deviceIndex);
if (eventItem.events & EPOLLIN) {
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
...
} else {
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
- //开始解析Buffuer
size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
struct input_event& iev = readBuffer[i];
ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
device->path.string(),
(int) iev.time.tv_sec, (int) iev.time.tv_usec,
iev.type, iev.code, iev.value);
// Some input devices may have a better concept of the time
// when an input event was actually generated than the kernel
// which simply timestamps all events on entry to evdev.
// This is a custom Android extension of the input protocol
// mainly intended for use with uinput based device drivers.
if (iev.type == EV_MSC) {
if (iev.code == MSC_ANDROID_TIME_SEC) {
device->timestampOverrideSec = iev.value;
continue;
} else if (iev.code == MSC_ANDROID_TIME_USEC) {
device->timestampOverrideUsec = iev.value;
continue;
}
}
if (device->timestampOverrideSec || device->timestampOverrideUsec) {
iev.time.tv_sec = device->timestampOverrideSec;
iev.time.tv_usec = device->timestampOverrideUsec;
if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
device->timestampOverrideSec = 0;
device->timestampOverrideUsec = 0;
}
ALOGV("applied override time %d.%06d",
int(iev.time.tv_sec), int(iev.time.tv_usec));
}
// Use the time specified in the event instead of the current time
// so that downstream code can get more accurate estimates of
// event dispatch latency from the time the event is enqueued onto
// the evdev client buffer.
//
// The event's timestamp fortuitously uses the same monotonic clock
// time base as the rest of Android. The kernel event device driver
// (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
// The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
// calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
// system call that also queries ktime_get_ts().
event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
+ nsecs_t(iev.time.tv_usec) * 1000LL;
ALOGV("event time %" PRId64 ", now %" PRId64, event->when, now);
// Bug 7291243: Add a guard in case the kernel generates timestamps
// that appear to be far into the future because they were generated
// using the wrong clock source.
//
// This can happen because when the input device is initially opened
// it has a default clock source of CLOCK_REALTIME. Any input events
// enqueued right after the device is opened will have timestamps
// generated using CLOCK_REALTIME. We later set the clock source
// to CLOCK_MONOTONIC but it is already too late.
//
// Invalid input event timestamps can result in ANRs, crashes and
// and other issues that are hard to track down. We must not let them
// propagate through the system.
//
// Log a warning so that we notice the problem and recover gracefully.
...
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1;
capacity -= 1;
}
if (capacity == 0) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
mPendingEventIndex -= 1;
break;
}
}
}
...
这样就把数据读出来了
参考: