關於EventHub的學習有一點前置知識很重要,就是epoll和inotify機制,可以參考這篇博文:www.cheelok.com/_inx/88。
在前面Input系列的博文中已經學習,來自底層的Input事件經過事件樞紐EventHub處理後,讓InputReader通過epoll和inotify從EventHub中讀取。那麼現在就來學習EventHub是如何讀取底層事件的吧。
EventHub註冊裝置節點的監聽
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
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();
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);
// 建立管道,並將讀端交給epoll,喚醒端(寫端)交給InputReader,用於喚醒epoll,避免epoll阻塞在epoll_wait()中
int wakeFds[2];
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->getEvents
按照上面的學習,我們知道:EventHub在建立時,建立了兩個Fd,mEpollFd和mINotifyFd。其中mINotifyFd用於監聽裝置節點是否有裝置檔案的增刪,將mINotifyFd註冊到mEpollFd中,當發生新的裝置增刪,裝置節點下的裝置檔案也會隨之增刪,就會通知mEpollFd有新的可讀的裝置增刪事件,通知EventHub對裝置進行處理。
換言之,剛建立EventHub時,mEpollFd只監聽了mINotifyFd。
getEvents整個函式比較長,我就不貼所有程式碼了,這個函式主要做了以下事情:
- 建立一個大小為bufferSize的input_event的緩衝區,用於儲存讀取的Input事件
- 判斷是否需要重新開啟Input裝置
- 處理最後被新增/刪除的Input裝置,其中會為了處理新增的裝置而進行裝置掃描
- 判斷是否需要掃描裝置
- 獲取Input事件
- 開啟/關閉在步驟3中新增/刪除的Input裝置
- 如果裝置資訊發生變化,則通知
- 喚醒epoll_wait,通知InputReader讀取事件,需要注意的是,整個喚醒過程都是加鎖的
對於我們來說,學習重點自然是步驟4了,但是在看它做了什麼之前不妨先想想,建立EventHub之後,第一次getEvents時,mEpollFd只監聽了mINotifyFd,那mEpollFd要怎麼獲取裝置上的發生的Input事件呢?
掃描裝置
我們在getEvents裡面可以看到,在獲取Input事件之前,會判斷是否需要掃描裝置:
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}複製程式碼
mNeedToScanDevices在建立EventHub時是預設賦值為true的,那麼第一次走getEvents肯定會走進來,scanDevicesLocked最終會呼叫scanDirLocked掃描/dev/input。在這個函式裡面就是迴圈掃描/dev/input下的裝置檔案了:
status_t EventHub::scanDirLocked(const char *dirname)
{
……
dir = opendir(dirname);
……
while((de = readdir(dir))) {
……
openDeviceLocked(devname);
}
……
}複製程式碼
看起來掃描到裝置檔案後就會openDeviceLocked,在這個函式裡其實沒做什麼特別的事情,就是新增裝置以及各種處理,有一個地方需要關注下:
// Register with epoll.
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
if (mUsingEpollWakeup) {
eventItem.events |= EPOLLWAKEUP;
}
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;
}
String8 wakeMechanism("EPOLLWAKEUP");
if (!mUsingEpollWakeup) {
#ifndef EVIOCSSUSPENDBLOCK
// uapi headers don't include EVIOCSSUSPENDBLOCK, and future kernels
// will use an epoll flag instead, so as long as we want to support
// this feature, we need to be prepared to define the ioctl ourselves.
#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
#endif
if (ioctl(fd, EVIOCSSUSPENDBLOCK, 1)) {
wakeMechanism = "<none>";
} else {
wakeMechanism = "EVIOCSSUSPENDBLOCK";
}
}複製程式碼
這裡我們將新裝置的fd註冊到mEpollFd中進行監聽,並且寫入E喚醒epoll。於是前面的問題就解決了,由於每次獲取Input事件前都會更新裝置資訊,因此mEpollFd能監聽到最新的裝置fd。
獲取Input事件
獲取Input事件的整個流程程式碼很多,我就不貼了,主要做了以下事情:
- 迴圈讀取mPendingEventItems中的eventItem
- 判斷eventItem是否為合法的inotify型別(eventItem.data.u32為EPOLL_ID_INOTIFY,且eventItem.events & EPOLLIN為true),如果是,說明有新的裝置增刪事件,則需要更新裝置列表資訊
- 判斷eventItem是否為合法的喚醒管道讀端的事件(eventItem.data.u32為EPOLL_ID_WAKE),若合法,則喚醒管道的讀端,也就是InputReader
- 判斷eventItem的接收裝置是否合法
- 如果eventItem不屬於EPOLL_ID_INOTIFY、EPOLL_ID_WAKE型別,且是epoll的輸入事件(EPOLLIN),則開始事件讀取的邏輯
事件讀取的邏輯裡做了以下事情:
- 首先通過裝置的readSize判斷裝置是否被移除、裝置讀取的事件總大小是否為input_event倍數(是否符合讀取格式)、readSize是否合法,通過以上檢查則獲取裝置id
- 迴圈讀取readBuffer中的input_event
- 對input_event的發生時間進行處理,儲存到RawEvent的when中
- 將deviceId、type、code、value封裝到RawEvent中
於是EventHub就成功地得到了來自裝置的事件,併成功將它們轉化為RawEvent,交給InputReader。
至此,本文結束。
題外話
如果你覺得我的分享有幫助到你的話,請我吃個零食/喝杯咖啡唄~