内容简介: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; } } } ...
这样就把数据读出来了
参考:
以上所述就是小编给大家介绍的《input之我见05——EVENTHUB兼谈EPOLL机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 快速失败机制 & 失败安全机制
- JavaScript线程机制与事件机制
- 区块链是怎样将分布式组网机制、合约机制、共识机制等技术结合并应用
- Java内存机制和GC回收机制-----笔记
- javascript垃圾回收机制 - 标记清除法/引用计数/V8机制
- Android 8.1 源码_机制篇 -- 全面解析 Handler 机制(原理篇)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。