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机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Java算法

Java算法

塞奇威克 / 赵文进 / 清华大学出版社 / 2004-06-01 / 59.0

《Java算法》用Java语言全面实现了当今最重要的计算机算法,并用大量图表和数学公式对算法进行了详尽的描述和分析。全书共分3卷,本书是其中的第1卷(第1至第4部分)。内容包括基本概念(第1部分)、数据结构(第2部分)、排序算法(第3部分)和查找算法(第4部分)。本书概念清楚,内容翔实,新颖,由浅入深地描述了算法。本书可作为高等院校计算机相关专业本科生和研究生的教材和补充读物,也可作为Java爱好一起来看看 《Java算法》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

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

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具