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

栏目: 后端 · 发布时间: 7年前

内容简介: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),而用户层是有专门的线程监听这个节点,一旦有数据更新,就立马读取,传传递给其它线程进一步加工处理。

从第一讲中的草图可以看到这些信息。

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

这里面讲的监听线程就是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的构造

先看一下相关类图

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

再看代码实现

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

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

IO模型 定义 过程图示 数据等待阶段 数据复制阶段 优点 缺点
同步阻塞IO
input之我见05——EVENTHUB兼谈EPOLL机制
BLOCK BLOCK 能够及时返回数据,无延迟;
同步非阻塞IO 同步非阻塞就是
“每隔一会儿瞄一眼进度条” 的轮询(polling)方式
input之我见05——EVENTHUB兼谈EPOLL机制
NON-BLOCK BLOCK 能够在等待任务完成的时间里干其他活了 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作
多路复用IO UNIX/Linux
下的 select、poll、epoll
input之我见05——EVENTHUB兼谈EPOLL机制
NON-BLOCK BLOCK 监听多个Iofd
异步IO 用户进程发起aio_read操作之后,立刻就可以开始去做其它的事,当这一切都完成之后,kernel会给用户进程发送一个signal或执行一个基于线程的回调函数来完成这次
IO 处理过程
input之我见05——EVENTHUB兼谈EPOLL机制

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;
 }
 }
 }
... 
 

这样就把数据读出来了

参考:

1, 聊聊Linux 五种IO模型

2, Linux Epoll介绍和程序实例


以上所述就是小编给大家介绍的《input之我见05——EVENTHUB兼谈EPOLL机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机网络(第7版)

计算机网络(第7版)

谢希仁 / 电子工业出版社 / 2017-1 / 45.00

本书自1989年首次出版以来,曾于1994年、1999年、2003年、2008年和2013年分别出了修订版。在2006年本书通过了教育部的评审,被纳入普通高等教育“十一五”国家级规划教材;2008年出版的第5版获得了教育部2009年精品教材称号。2013年出版的第6版是“十二五”普通高等教育本科国家级规划教材。 目前2017年发行的第7版又在第6版的基础上进行了一些修订。 全书分为9章,比较......一起来看看 《计算机网络(第7版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具