使用inotifyAPI的幾個關鍵步驟:
1、使用inotify_init()創建一個inotify實例,返回一個文件描述符
2、使用inotify_add_watch()向inotify實例的監控列表添加條目。每個監控項都包含一個路徑名以及相關的位掩碼。位掩碼針對路徑名指明瞭所要監控的事件集合。函數返回一個監控描述符,用於指代該監控項
3、針對inotify文件描述符執行read()操作,每次對read()的成功調用,都會返回一個或多個inotify_event結構,其中各自記錄了處於inotify實例監控之下的某一路徑名所發生的事件。
4、關閉inotify文件描述符。這會自動清除與inotify實例相關的所有監控項
inotify機制可用於監控文件或目錄,當監控目錄時,與路徑自身及其所含文件相關的事件都會通知給應用程序。但inotify機制是非遞歸的,若需要監控整個目錄子樹,則需對該樹中中的每個目錄發起inotify_add_watch()調用。可使用select()、poll()、epoll()以及由信號驅動的I/O來監控inotify文件描述符。只要有事件可供讀取,上述API便會將inotify文件描述符標記爲可讀。
inotify機制屬於可選的Linux內核組件,通過CONFIG_INOTIFY和CONFIG_INOTIFY_USER選項配置。
Inotify API
#include <sys/inotify.h>
int inotify_init(void);
#include <sys/inotify.h>
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
可追加新的監控項,也可修改現有監控項
pathname標識欲創建或修改的監控項所對應的文件,調用程序必須對該文件具有讀權限(調用inotify_add_watch()時,會對文件權限做一次性檢查。只要監控項繼續存在,即便有人更改了文件權限,使調用程序不再對文件具有讀權限,調用程序依然會繼續收到文件的通知消息)。
#include <sys/inotify.h>
int inotify_rm_watch(int fd, uint32_t wd);
刪除監控項會爲該監控描述符生成IN_IGNORED事件。
Inotify事件
位值 |
In |
Out |
描述 |
IN_ACCESS |
● |
● |
文件被訪問(read) |
IN_ATTRIB |
● |
● |
文件元數據改變 |
IN_CLOSE_WRITE |
● |
● |
關閉爲了寫入而打開的文件 |
IN_CLOSE_NOWRITE |
● |
● |
關閉以只讀方式打開的文件 |
IN_CREATE |
● |
● |
在受監控的目錄內創建了文件/目錄 |
IN_DELETE |
● |
● |
在受監控目錄內刪除文件/目錄 |
IN_DELETE_SELF |
● |
● |
刪除受控目錄/文件本身 |
IN_MODIFY |
● |
● |
文件被修改 |
IN_MOVE_SELF |
● |
● |
移動受監控目錄/文件本身 |
IN_MOVED_FROM |
● |
● |
文件移出到受控目錄之外 |
IN_MOVED_TO |
● |
● |
將文件移入受控目錄 |
IN_OPEN |
● |
● |
文件被打開 |
IN_ALL_EVENTS |
● |
以上所有輸出事件的統稱 |
|
IN_MOVE |
● |
IN_MOVED_ FROM|TO的統稱 |
|
IN_CLOSE |
● |
IN_CLOSE_ WRITE|NOWRITE的統稱 |
|
IN_DONT_FOLLOW |
● |
不對符號鏈接解引用 |
|
IN_MASK_ADD |
● |
將事件追加到pathname的當前監控掩碼 |
|
IN_ONESHOT |
● |
只監控pathname的一個事件 |
|
IN_ONLYDIR |
● |
pathname不是目錄會失敗 |
|
IN_IGNORED |
● |
監控項爲內核或應用程序所移除 |
|
IN_ISDIR |
● |
name中返回的所有文件名爲路徑 |
|
IN_Q_OVERFLOW |
● |
事件隊列溢出 |
|
IN_UNMOUNT |
● |
包含對象的文件系統遭卸載 |
細節:
當文件元數據(比如,權限、所有權、鏈接計數、擴展屬性、用戶ID、組ID等)改變時,會發生IN_ATTRIB事件。
IN_DONT_FOLLOW、IN_MASK_ADD、IN_ONESHOT和IN_ORLYDIR位並非對監控事件的定義,而是意在控制inotify_add_watch()系統調用的行爲。
IN_DONT_FOLLOW規定,若pathname爲符號鏈接,則不對其解引用,而是監控符號鏈接
若對已爲同一inotify描述符所監控的同一路徑名再次執行inotify_add_watch()調用,那麼默認情況下會用給定的mask掩碼來替換該監控項的當前掩碼。如果指定了IN_MASK_ADD,則會用mask與當前掩碼相或
IN_ONESHOT允許只監控pathname一個事件,事件發生後,監控項自動消失
只有pathname爲目錄時,IN_ONLYDIR才允許應用程序對其進行監控,否則報錯爲ENOTDIR。如要確保監控對象爲目錄,該標誌可以避免競爭條件的發生
讀取inotify事件
可用read()從inotify文件描述符中讀取事件,以判定發生了哪些事件。若時至讀取時尚未發生任何事件,read()會阻塞下去,直至有事件產生(除非對該文件描述符設置了O_NONBLOCK狀態標誌,這時若無任何事件可讀,read()將立即失敗,並報錯EAGAIN)。
事件發生後,每次調用read()會返回一個緩衝區,內含一個或多個如下類型的結構
struct inotify_event {
int wd; \\Watch descriptor on which event occurred
uint32_t mask; \\Bits descriping event that occurred
uint32_t cookie; \\Cookie for related events
uint32_t len; \\Size of 'name' field
char name[]; \\Optional null-terminated filename
};
mask字段返回該事件的位掩碼,注意下列更多的細節:
移出監控項時,會產生IN_IGNORED事件,起因可能由兩個:其一,應用程序使用了inotify_rm_watch()系統調用顯式移除監控項;其二,因受監控對象被刪除或其所駐留的文件系統遭卸載,致使內核隱式刪除監控項,以IN_ONESHOT而建立的監控項因事件觸發而遭自動移除時,不會產生IN_INGORED事件。
如果事件主體爲路徑,那麼除去其它位以外,在mask中還會設置IN_ISDIR位。
IN_UNMOUT事件會通知應用程序包含受監控對象的文件系統已遭卸載。該事件發生後還會產生包含IN_IGNORED置位的附加事件。
cookie字段可將相關事件聯繫在一起。目前,只有在對文件重命名時纔會用到該字段。這種情況下,系統會對重命名文件所在目錄產生IN_MOVED_FORM事件,然後,會針對重命名後文件的所在目錄產生IN_MOVED_TO事件。兩個事件cookie值相等
name,當受監控目錄中有文件發生事件時,name字段返回一個以空字符結尾的字符串,以標識該文件。若受監控對象自身有事件發生,則不使用name字段,將len字段置爲0。
len字段標識name的字節數,name的字符串結尾和下一個inotify_event結構的開始之間,可能會由額外的填充字節,單個inotify事件的長度是sizeof(struct inotify_event)+len
如果傳遞給read()緩衝區過小,將失敗並返回EINVAL,只要確保緩衝區足以容納下至少一個事件即可,傳給read()的緩衝區應至少爲sizeof(struct inotify_event)+NAME_MAX+1。對inotify描述符所執行的read(),將在已發生事件數量與緩衝區可容納事件數量間去最小值並返回之。
針對文件描述符fd調用ioctl(fd,FIONREAD,&namebytes),會返回其所指代的inotify實例中的當前可讀字節數。
從inotify文件描述符中讀取的事件形成了一個有序隊列,在事件隊列末尾追加一個新事件時,如果此新事件與隊列當前尾部事件有相同的wd,mask,cookie值,那麼內核會將兩者合併。
隊列限制和/proc文件
對inotify事件做排隊處理,需要消耗內核內存,所以內核會對inotify機制的操作施以各種限制。超級用戶可配置/proc/sys/fs/inotify路徑中的3個文件來調整這些限制:
max_queued_events 默認值16384
調用inotify_init()時,使用該值來爲新的inotify實例隊列中的事件數量設置上限,一旦超過這一上限,系統將生成IN_Q_OVERFLOW事件,並丟棄多餘事件,溢出事件的wd字段值爲-1。
max_user_instances 默認值128
對由每個真實用戶ID創建inotify實例數的限制值
max_user_watches 默認值8192
對由每個真實用戶ID創建的監控項數量的限制值