Android Input 學(xué)習(xí)-INotify與Epoll機(jī)制

使用背景

Android Input需要使用InputReader去監(jiān)控設(shè)備節(jié)點(diǎn)的一些動作,包括節(jié)點(diǎn)的新建和刪除動作以及如何去確定節(jié)點(diǎn)中是否有內(nèi)容可以去讀.最簡單的方法是起一個線程在循環(huán)中不斷地去做輪詢(polling),但是這樣做的效率比較低,而且會導(dǎo)致設(shè)備的電量在無意義的輪詢中消耗掉.眾所周知Android使用的Linux內(nèi)核,因此面對這種問題,Android使用了Linux提供的INotify和Epoll機(jī)制,異步的去讀取消息,而不是一直在輪詢.

1. INotify機(jī)制

  • INotify是Linux提供的用于檢測文件系統(tǒng)變化的通知機(jī)制,INotify可以用于檢測單個文件,也可以用于檢測整個目錄,當(dāng)檢測的對象是一個目錄的時(shí)候,目錄本身和目錄里的內(nèi)容都會成為檢測的對象.
  • INotify機(jī)制用兩個基本對象,分別是inotify對象和watch對象,都是用文件描述符表示.
  • inotify對象對應(yīng)一個隊(duì)列,應(yīng)用程序可以往inotify對象添加多個監(jiān)聽.當(dāng)監(jiān)聽的事件發(fā)生的時(shí)候,可以通過read()函數(shù)(read函數(shù)是一個阻塞函數(shù))從inotify對象中將事件信息讀取出來.inotify創(chuàng)建方式:
int inotifyfd = inotify_init();
  • watch對象是用來描述文件系統(tǒng)的變化事件的監(jiān)聽.它是一個二元組,包括監(jiān)聽目標(biāo)和事件掩碼兩個元素.監(jiān)聽目標(biāo)是文件系統(tǒng)的路徑,包括文件夾和文件.事件掩碼則表示了要去監(jiān)聽的事件類型,包括文件的創(chuàng)建(IN_CREATE)與刪除 (IN_DELETE).watch對象的創(chuàng)建如下:
int wd = inotify_add_watch(inotifyfd, "/dev/input",  IN_CREATE | IN_DELETE);

以上代碼完成后,當(dāng)/dev/input下的設(shè)備節(jié)點(diǎn)發(fā)生創(chuàng)建與刪除操作的時(shí)候,會將相應(yīng)的信息寫入到inotifyfd所描述的inotify對象中,此時(shí)就可以通過read()函數(shù)從inotifyfd描述符中將事件信息讀取出來.
事件信息使用的結(jié)構(gòu)體inotify_event 如下:

struct inotify_event {
            __s32        wd; /*watch descriptor,事件對應(yīng)watch對象的描述符*/
            __u32        mask; /*watch mask,事件類型,就是我們需要監(jiān)聽的,例如文件創(chuàng)建的話,此時(shí)的mask就是IN_CREATE*/
            __u32        cookie; /*cookie to sychronize two events*/
            __u32        len; /*length (including nulls) of name,name字段的長度*/
            char         name[0]; /*stub for possibale name,name字段的長度是0,也就是說是可變長的,用于存儲產(chǎn)生此事件的文件路徑*/
};

當(dāng)監(jiān)聽事件發(fā)生事件,可以通過read()函數(shù)將一個或多個未讀取的事件信息讀取出來:

struct inotify_event *event;
char event_buf[512];
event = read(inotifyfd, event_buf, sizeof(event_buf));

能夠讀取的事件數(shù)量取決于數(shù)組的長度,成功讀取事件信息后,便可以根據(jù)inotify_event結(jié)構(gòu)體的字段判斷事件類型,以及產(chǎn)生事件的文件路徑.

//demo 代碼
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

int watch_inotify_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    /*讀事件是否發(fā)生,沒有發(fā)生則會阻塞*/
    ret = read(fd, event_buf, sizeof(event_buf));
    /*若read的返回值,小于inotify_event大小,說明我們連最基本的一個event都讀取出來,是錯誤的結(jié)果*/
    if(ret < (int)sizeof(struct inotify_event)) {
        printf("read error,could get event");
        return -1;
    }
    /*一次讀取可能會去讀取多個事件,需要一個循環(huán)全部讀取出來*/
    while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        if(event->len) {
            if(event->mask & IN_CREATE) {
                printf("create file:%s successfully \n", event->name);
            } else {
                printf("delete file:%s successfully \n", event->name);
            }
        }
        //event 的真實(shí)大小,name是可變的,所以要加上event->len
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        event_pos += event_size;
    }
    return 0;
}
int main(int argc, char** argv) {
    int inotifyFd;
    int ret;
    if (argc != 2) {
        printf("useage: %s <dir> \n", argv[0]);
    }

    /*inotify初始化*/
    inotifyFd = inotify_init();
    if(inotifyFd == -1) {
        printf("inotify_init error!\n");
        return -1;
    }
    ret = inotify_add_watch(inotifyFd, argv[1], IN_CREATE | IN_DELETE) ;
    watch_inotify_events(inotifyFd);
    if(inotify_rm_watch(inotifyFd, ret) == -1) {
        printf("notify_rm_watch error!\n");
        return -1;
    }
    //關(guān)閉描述符
    close(inotifyFd);
    return 0;
}
  • 總結(jié)
    整個使用過程如下:

  • 通過inotify_init()創(chuàng)建一個inotify對象

  • 通過inotify_add_watch將一個或多個監(jiān)聽添加進(jìn)inotify對象中.(IN_CREATEE,IN_DELETE等)

  • 通過read函數(shù)從inotify對象中去讀取監(jiān)聽事件,當(dāng)沒有新事件發(fā)生時(shí)間,inotify對象中無任何可讀數(shù)據(jù)

  • 注意點(diǎn)
    雖然inotify機(jī)制避免了輪詢文件系統(tǒng)的麻煩,但是inotify不是通過回調(diào)的方式去通知事件的,而是需要使用者自動去從inotify對象中去讀取事件,這就有一個問題發(fā)生了,使用者什么時(shí)候去主動讀取事件呢?
    這個就需要去借助Linux的Epoll了.

2.Epoll機(jī)制

  • Epoll可以使用一次等待監(jiān)聽多個描述符的可讀/可寫狀態(tài).等待返回時(shí)攜帶了可讀的描述符或者自定義的數(shù)據(jù).不需要為每個描述符創(chuàng)建獨(dú)立的線程進(jìn)行阻塞讀取,避免了資源浪費(fèi).
  • Epoll機(jī)制有三個函數(shù)
  • int epoll_create(int max_fds):創(chuàng)建一個epoll對象的描述符,以供之后去使用.max_fds參數(shù)表示可以監(jiān)聽的最大描述符變量.
  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event):用于等待事件到來.當(dāng)此函數(shù)返
    回時(shí),events數(shù)組參數(shù)中將會包含產(chǎn)生事件的文件描述符.
    • epfd: epoll_create()的返回值
    • op: 表示動作,包括三個宏:EPOLL_CTL_ADD:注冊新的fd到efpd,EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件,EPOLL_CTL_DEL:從epfd中刪除一個fd
    • fd: 需要監(jiān)聽的文件描述符
    • event: 告訴內(nèi)核需要監(jiān)聽什么事件.
    typedef union epoll_data {
        void* ptr;
        int fd;
        __unint32_t u32;
        __unint64_t u64;
    } epoll_data_t;
    
    struct epoll_event {
        __unint32_t events;/*事件掩碼,指明需要監(jiān)聽的事件種類*/
        epoll_data_t data;/*使用者自定義的數(shù)據(jù),當(dāng)此事發(fā)生時(shí),該數(shù)據(jù)將原封不動地返回給使用者*/
    }
    
    events可以是以下幾個宏的集合:
    EPOLLIN :表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉)搓谆;
    EPOLLOUT:表示對應(yīng)的文件描述符可以寫猪半;
    EPOLLPRI:表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)瓤荔;
    EPOLLERR:表示對應(yīng)的文件描述符發(fā)生錯誤日熬;
    EPOLLHUP:表示對應(yīng)的文件描述符被掛斷吭服;
    EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式腻扇,這是相對于水平觸發(fā)(Level Triggered)來說    
    的糠悼。
    EPOLLONESHOT:只監(jiān)聽一次事件藐不,當(dāng)監(jiān)聽完這次事件之后滋恬,如果還需要繼續(xù)監(jiān)聽這個socket的話聊训,  
    需要再次把這個socket加入到EPOLL隊(duì)列里
    
  • epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout):
    • epfd:
    • events: 分配好的epoll_event結(jié)構(gòu)體數(shù)組,epoll將會把發(fā)生的事件賦值到events數(shù)組中(events不能是空指針,因?yàn)閮?nèi)核只負(fù)責(zé)把數(shù)據(jù)賦值到這個events數(shù)組中,不會去幫助我們在用戶態(tài)中分配內(nèi)存).
    • maxevents: 告訴內(nèi)核events有多大,最大不能大于創(chuàng)建epoll_create()時(shí)的size.
    • timeout: 超時(shí)時(shí)間(ms, 0立即返回,-1將不確定).
    • 返回值: 若函數(shù)調(diào)用成功,返回對應(yīng)I/O上已準(zhǔn)備好的文件描述符數(shù)目,若返回0則表示超時(shí).
    //demo代碼
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;//需要監(jiān)聽的事件
    eventItem.data.fd = inotifyId;//需要監(jiān)聽的fd
    int epfd = epoll_create(8);
    int epctl = epoll_ctl(epfd, EPOLL_CTL_ADD, inotifyId, &eventItem);//epoll_ctl可以注冊多次,去注冊不同需要監(jiān)聽的事件,主要就是不同的文件描述符注冊到epoll對象中.注冊完之后就可以通過epoll_wait函數(shù)等待事件到來.
    struct epoll_event mPendingEventItems[16];
    int result = epoll_wait(epfd, mPendingEventItems, 16, 20*1000);
    
  • 總結(jié),使用步驟如下:
    • 1.使用epoll_create()創(chuàng)建一個epoll對象
    • 2.為需要監(jiān)聽的描述符填充epoll_events結(jié)構(gòu)體,并使用epoll_ctl注冊到epoll對象中.
    • 3.使用epoll_wait()等待事件發(fā)生
    • 4.根據(jù)epoll_wait()返回的epoll_events結(jié)構(gòu)體數(shù)組判斷事件的類型與來源進(jìn)行處理
    • 5.繼續(xù)使用epoll_wait()等待新事件發(fā)生.
//完整demo代碼
#include <sys/inotify.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

int watch_inotify_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    /*讀事件是否發(fā)生,沒有發(fā)生則會阻塞*/
    ret = read(fd, event_buf, sizeof(event_buf));
    /*若read的返回值,小于inotify_event大小,說明我們連最基本的一個event都讀取出來,是錯誤的結(jié)果*/
    if(ret < (int)sizeof(struct inotify_event)) {
        printf("read error,could get event");
        return -1;
    }
    /*一次讀取可能會去讀取多個事件,需要一個循環(huán)全部讀取出來*/
    while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        if(event->len) {
            if(event->mask & IN_CREATE) {
                printf("create file:%s successfully \n", event->name);
            } else {
                printf("delete file:%s successfully \n", event->name);
            }
        }
        //event 的真實(shí)大小,name是可變的,所以要加上event->len
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        event_pos += event_size;
    }
    return 0;
}


int main(int argc, char** argv) {
    int epollFd;
    int inotifyFd;
    int pendingEventCount;
    int pendingEventIndex;
    epollFd = epoll_create(8);
    inotifyFd = inotify_init();
    int result_notify = inotify_add_watch(inotifyFd, argv[1], IN_CREATE | IN_DELETE);
    if(result_notify < 0) {
        printf("Could not register INotify. \n");
        return -1;
    }
    if(epollFd < 0) {
        printf("Could not create epoll instance. \n");
        return -1;
    }
    struct epoll_event eventItem;
    eventItem.events = EPOLLIN;
    int result_epoll = epoll_ctl(epollFd, EPOLL_CTL_ADD, inotifyFd, &eventItem);
    if(result_epoll != 0) {
        printf("Could not add INotify to epoll instance.  \n");
    }
    struct epoll_event pendingEventItems[16];
    int pollResult = epoll_wait(epollFd, pendingEventItems, 16, 30*1000ll);
    if(pollResult > 0) {
        pendingEventCount = size_t(pollResult);
    } else {
        pendingEventCount = 0;
    }
    while(pendingEventIndex < pendingEventCount) {
        const struct epoll_event& eventItem = pendingEventItems[pendingEventIndex++];
        if (eventItem.events & EPOLLIN) {
            watch_inotify_events(inotifyFd);
        }
    }
    return 0;
}
123@123-good-man:./a.out ~/Documents/cpptest/ &

//運(yùn)行結(jié)果
123@123-good-man:~/Documents/cpptest$ touch 201808.txt
create file:201808.txt successfully 
[1]+  Done                    ./a.out ~/Documents/cpptest/
123@123-good-man:~/Documents/cpptest$ 

參考:《深入理解Android卷|||》 第五章 深入理解Android輸入系統(tǒng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓤球,一起剝皮案震驚了整個濱河市嘉抓,隨后出現(xiàn)的幾起案子矾削,更是在濱河造成了極大的恐慌憎亚,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件括荡,死亡現(xiàn)場離奇詭異悼枢,居然都是意外死亡犀概,警方通過查閱死者的電腦和手機(jī)敢靡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門挂滓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啸胧,你說我怎么就攤上這事赶站。” “怎么了纺念?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵贝椿,是天一觀的道長。 經(jīng)常有香客問我柠辞,道長团秽,這世上最難降的妖魔是什么主胧? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任叭首,我火速辦了婚禮,結(jié)果婚禮上踪栋,老公的妹妹穿的比我還像新娘焙格。我一直安慰自己,他們只是感情好夷都,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布眷唉。 她就那樣靜靜地躺著,像睡著了一般囤官。 火紅的嫁衣襯著肌膚如雪冬阳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天党饮,我揣著相機(jī)與錄音肝陪,去河邊找鬼。 笑死刑顺,一個胖子當(dāng)著我的面吹牛氯窍,可吹牛的內(nèi)容都是我干的饲常。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼狼讨,長吁一口氣:“原來是場噩夢啊……” “哼贝淤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起政供,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤播聪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后布隔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犬耻,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年执泰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枕磁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡术吝,死狀恐怖计济,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情排苍,我是刑警寧澤沦寂,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站淘衙,受9級特大地震影響传藏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彤守,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一毯侦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧具垫,春花似錦侈离、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至起宽,卻和暖如春洲胖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坯沪。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工绿映, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屏箍。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓绘梦,卻偏偏與公主長得像橘忱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子卸奉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 看到網(wǎng)上有不少討論epoll榄棵,但大多不夠詳細(xì)準(zhǔn)確凝颇,以前面試有被問到這個問題。不去更深入的了解疹鳄,只能停留在知其然...
    電臺_Fang閱讀 11,487評論 0 8
  • epoll概述 epoll是linux中IO多路復(fù)用的一種機(jī)制拧略,I/O多路復(fù)用就是通過一種機(jī)制,一個進(jìn)程可以監(jiān)視多...
    發(fā)仔很忙閱讀 10,863評論 4 35
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作瘪弓。那么我們對與外部設(shè)...
    VD2012閱讀 1,017評論 0 2
  • 同步IO和異步IO,阻塞IO和非阻塞IO分別是什么呛占,到底有什么區(qū)別虑乖?不同的人在不同的上下文下給出的答案是不同的。所...
    lxqfirst閱讀 2,141評論 0 47
  • 臂彎 走了那么久晾虑,那么遠(yuǎn)疹味,那么多年 步履蹣跚,你依然是我 伸手不見的夜晚里 和氣成冰的嚴(yán)寒里 一籠燃得正旺的火焰帜篇。...
    悅讀你的美閱讀 444評論 0 0