使用背景
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$