Android Input(二)-输入子系统

原创内容,转载请注明出处,多谢配合。

经过上一篇的介绍,对Input模块的组成有了大致了解。这篇介绍下输入子系统,了解下kernel如何收集硬件设备产生的输入事件。

一、输入子系统

Android设备可以同时连接多个输入设备,比如说触摸屏,键盘,鼠标等等。用户在任何一个设备上的输入就会产生一个中断,经由Linux内核的中断处理以及设备驱动转换成一个Event,并传递给用户空间的应用程序进行处理。每个输入设备都有自己的驱动程序,数据接口也不尽相同,如何在一个线程里(上面说过只有一个InputReader Thread)把所有的用户输入都给捕捉到? 这首先要归功于Linux 内核的输入子系统(Input Subsystem), 它在各种各样的设备驱动程序上加了一个抽象层,只要底层的设备驱动程序按照这层抽象接口来实现,上层应用就可以通过统一的接口来访问所有的输入设备。这个抽象层有三个重要的概念,input handler, input handle 和 input_dev,它们的关系如下图所示:

input_dev 代表底层驱动。
input_handler 代表某类输入设备的处理方法,相当于一个上层驱动。
(一个input_dev 可以有多个input_handler,同样,一个input_handler 可以用于多种输入设备。)
input handle 用来关联某个input_dev 和 某个 input_handler, 它对应上图中的紫色的原点。每个input handle 都会生成一个文件节点。

/dev/input $ ls                                                                                                                                       

event0 event1 event2 event3 event4 event5 event6

比如图中4个evdev的handle就对应与 /dev/input/下的四个文件"event0 - 3". 通过input handle, 可以找到对应的input_handler 和 input_dev.

通过Linux input system获取用户输入的流程简单如下:

  1. 设备通过input_register_dev 将自己的驱动注册到Input 系统。
  2. 各种Handler 通过 input_register_handler将自己注册到Input系统中。
  3. 每一个注册进来的input_dev 或 Input_handler 都会通过input_connect() 寻找对方,生成对应的 input_handle,并在/dev/input/下产成一个设备节点文件.
  4. 应用程序通过打开(Open)Input_handle对应的文件节点,打开其对应的input_dev 和 input_handler的驱动。这样,当用户按键时,底层驱动就能捕捉到,并交给对应的上层驱动(handler)进行处理,然后返回给应用程序。

所以,只要打开 /dev/input/ 下的所有 event* 设备文件,我们就可以有办法获取所有输入设备的输入事件,不管它是触摸屏,还是一个USB 设备,还是一个红外遥控器。Android中完成这个工作的就是EventHub。它在NativeInputManager的初始化中被创建。

二、EventHub

另外, 说到输入子系统,不得不提EventHub, 它是底层event处理的枢纽,并向上层传递。

先看构造函数:

frameworks/native/services/inputflinger/EventHub.cpp

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
    //创建epoll对象用于监听是否有可读事件,EPOLL_SIZE_HINT为最大监听个数:8
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
  //创建inotify对象用于监听设备节点DEVICE_PATH,即/dev/input,是否有变化(设备增删),设备的增删对应着设备节点的文件增删
    mINotifyFd = inotify_init();
    //inotify 监听 /dev/input 目录
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",
            DEVICE_PATH, errno);
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    //将inotify对象注册到epoll中监听是否有新的可读的设备增删事件
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);
    int wakeFds[2];
  //创建管道,并将读端交给epoll,写端交给InputReader,用于唤醒epoll,避免epoll阻塞在epoll_wait()
  //mWakeReaderFD, mWakeWriterFD对应上管道两端
    result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
    int major, minor;
    getLinuxRelease(&major, &minor);
    // EPOLLWAKEUP was introduced in kernel 3.5
   mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}

总结:EventHub在初始化时,构造函数中创建了两个Fd,mEpollFd和mINotifyFd。其中mINotifyFd用于监听设备节点是否有设备文件的增删,将mINotifyFd注册到mEpollFd中,当发生新的设备增删,设备节点下的设备文件也会随之增删,就会通知mEpollFd有新的可读的设备增删事件,通知EventHub对设备进行处理。而mEpollFd监听是否有可读事件,创建管道,将读端交给epoll,写端交给InputReader来处理事件。

InputReader会在每次loop时,调用EventHub的getEvents来获取input事件:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
   ALOG_ASSERT(bufferSize >= 1);
   AutoMutex _l(mLock);
   struct input_event readBuffer[bufferSize];
   RawEvent* event = buffer;
   size_t capacity = bufferSize;
   bool awoken = false;
   for (;;) {
       nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
       // Reopen input devices if needed.
       if (mNeedToReopenDevices) {
           mNeedToReopenDevices = false;
           ALOGI("Reopening all input devices due to a configuration change.");
           closeAllDevicesLocked();
           mNeedToScanDevices = true;
           break; // return to the caller before we actually rescan
       }
       // Report any devices that had last been added/removed.
       while (mClosingDevices) {
           Device* device = mClosingDevices;
           ALOGV("Reporting device closed: id=%d, name=%s\n",
                device->id, device->path.string());
           mClosingDevices = device->next;
           event->when = now;
           event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
           event->type = DEVICE_REMOVED;
           event += 1;
           delete device;
           mNeedToSendFinishedDeviceScan = true;
           if (--capacity == 0) {
               break;
           }
       }
       //第一次为true
       if (mNeedToScanDevices) {
           mNeedToScanDevices = false;
           //打开"/dev/input/"目录下的input设备后,将其注册到epoll的监控队列中。
           scanDevicesLocked();
           mNeedToSendFinishedDeviceScan = true;
       }
       while (mOpeningDevices != NULL) {
           Device* device = mOpeningDevices;
           ALOGV("Reporting device opened: id=%d, name=%s\n",
                device->id, device->path.string());
           mOpeningDevices = device->next;
           event->when = now;
           event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
           event->type = DEVICE_ADDED;
           event += 1;
           mNeedToSendFinishedDeviceScan = true;
           if (--capacity == 0) {
               break;
           }
       }
       if (mNeedToSendFinishedDeviceScan) {
           mNeedToSendFinishedDeviceScan = false;
           event->when = now;
           event->type = FINISHED_DEVICE_SCAN;
           event += 1;
           if (--capacity == 0) {
               break;
           }
       }
       // Grab the next input event.
       bool deviceChanged = false;
       while (mPendingEventIndex < mPendingEventCount) {
           const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
           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;
           }
           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;
           }
           ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
           if (deviceIndex < 0) {
               ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
                       eventItem.events, eventItem.data.u32);
               continue;
           }
           Device* device = mDevices.valueAt(deviceIndex);
           if (eventItem.events & EPOLLIN) {
                //从device中得到fd后再去读取设备,获取input事件
               int32_t readSize = read(device->fd, readBuffer,
                       sizeof(struct input_event) * capacity);
               if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                   // Device was removed before INotify noticed.
                   ALOGW("could not get event, removed? (fd: %d size: %" PRId32
                           " bufferSize: %zu capacity: %zu errno: %d)\n",
                           device->fd, readSize, bufferSize, capacity, errno);
                   deviceChanged = true;
                   closeDeviceLocked(device);
               } else if (readSize < 0) {
                   if (errno != EAGAIN && errno != EINTR) {
                       ALOGW("could not get event (errno=%d)", errno);
                   }
               } else if ((readSize % sizeof(struct input_event)) != 0) {
                   ALOGE("could not get event (wrong size: %d)", readSize);
               } else {
                   int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
                   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.
                       if (event->when >= now + 10 * 1000000000LL) {
                           // Double-check.  Time may have moved on.
                           nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC);
                           if (event->when > time) {
                               ALOGW("An input event from %s has a timestamp that appears to "
                                       "have been generated using the wrong clock source "
                                       "(expected CLOCK_MONOTONIC): "
                                       "event time %" PRId64 ", current time %" PRId64
                                       ", call time %" PRId64 ".  "
                                       "Using current time instead.",
                                       device->path.string(), event->when, time, now);
                               event->when = time;
                           } else {
                               ALOGV("Event time is ok but failed the fast path and required "
                                       "an extra call to systemTime: "
                                       "event time %" PRId64 ", current time %" PRId64
                                       ", call time %" PRId64 ".",
                                       event->when, time, now);
                           }
                       }
                       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;
                   }
               }
           } else if (eventItem.events & EPOLLHUP) {
               ALOGI("Removing device %s due to epoll hang-up event.",
                       device->identifier.name.string());
               deviceChanged = true;
               closeDeviceLocked(device);
           } else {
               ALOGW("Received unexpected epoll event 0x%08x for device %s.",
                       eventItem.events, device->identifier.name.string());
           }
       }
       // readNotify() will modify the list of devices so this must be done after
       // processing all other events to ensure that we read all remaining events
       // before closing the devices.
       if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
           mPendingINotify = false;
           readNotifyLocked();
           deviceChanged = true;
       }
       // Report added or removed devices immediately.
       if (deviceChanged) {
           continue;
       }
       // Return now if we have collected any events or if we were explicitly awoken.
       if (event != buffer || awoken) {
           break;
       }
       // Poll for events.  Mind the wake lock dance!
       // We hold a wake lock at all times except during epoll_wait().  This works due to some
       // subtle choreography.  When a device driver has pending (unread) events, it acquires
       // a kernel wake lock.  However, once the last pending event has been read, the device
       // driver will release the kernel wake lock.  To prevent the system from going to sleep
       // when this happens, the EventHub holds onto its own user wake lock while the client
       // is processing events.  Thus the system can only sleep if there are no events
       // pending or currently being processed.
       //
       // The timeout is advisory only.  If the device is asleep, it will not wake just to
       // service the timeout.
       mPendingEventIndex = 0;
       mLock.unlock(); // release lock before poll, must be before release_wake_lock
       release_wake_lock(WAKE_LOCK_ID);
      //等待input事件
       int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
       acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
       mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
       if (pollResult == 0) {
           // Timed out.
           mPendingEventCount = 0;
           break;
       }
       if (pollResult < 0) {
           // An error occurred.
           mPendingEventCount = 0;
           // Sleep after errors to avoid locking up the system.
           // Hopefully the error is transient.
           if (errno != EINTR) {
               ALOGW("poll failed (errno=%d)\n", errno);
               usleep(100000);
           }
       } else {
           // Some events occurred.
           mPendingEventCount = size_t(pollResult);
       }
   }
   // All done, return the number of events we read.
   return event - buffer;
}

第一次getEvents时,打开"/dev/input/"目录下的input设备,并将其注册到epoll的监控队列中。这样一旦对应设备上有可读的input事件,则epool_wait()就会返回,并带回deviceid,找到具体的device。整个事件的获取中,除了input事件,设备的打开关闭等信息,也要包装成event,上报给InputReader。简单理解,EventHub就是InputReader用于打开和关闭Input设备,监听和读取Input事件的对象。

下一篇文章:
Android Input(三)-InputReader获取事件

参考:

https://www.cnblogs.com/samchen2009/p/3368158.html
我读过的最好的epoll讲解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349