linux --- inotify 檔案系統變化通知機制
在linux下開發過程中,使用者態需要核心提供一些機制,以便使用者態能夠及時地得知核心或底層硬體裝置發生了什麼,從而能夠更好地管理裝置,給使用者提供更好的服務,如 hotplug、udev 和 inotify 就是這種需求催生的。
Hotplug 是一種核心向使用者態應用通報關於熱插拔裝置一些事件發生的機制,案頭系統能夠利用它對裝置進行有效管理,udev 動態地維護 /dev 下的裝置檔案
inotify 是一種檔案系統的變化通知機制,如檔案增加、刪除等事件可以立刻讓使用者態得知,該機制是著名的案頭搜尋引擎項目beagle 引入的,並在 Gamin 等項目中被應用。
事實上,在 inotify 之前已經存在一種類似的機制叫 dnotify,但是它存在許多缺陷:
1. 對於想監視的每一個目錄,使用者都需要開啟一個檔案描述符,因此如果需要監視的目錄較多,將導致開啟許多檔案描述符,特別是,如果被監視目錄在移動介質上(如光碟片和 USB 盤),將導致無法 umount 這些檔案系統,因為使用 dnotify 的應用開啟的檔案描述符在使用該檔案系統。
2. dnotify 是基於目錄的,它只能得到目錄變化事件,當然在目錄內的檔案的變化會影響到其所在目錄從而引發目錄變化事件,但是要想通過目錄事件來得知哪個檔案變化,需要緩衝許多 stat 結構的資料。
3. Dnotify 的介面非常不友好,它使用 signal。
Inotify 是為替代 dnotify 而設計的,它克服了 dnotify 的缺陷,提供了更好用的,簡潔而強大的檔案變化通知機制:
1. Inotify 不需要對被監視的目標開啟檔案描述符,而且如果被監視目標在可移動介質上,那麼在 umount 該介質上的檔案系統後,被監視目標對應的 watch 將被自動刪除,並且會產生一個 umount 事件。
2. Inotify 既可以監視檔案,也可以監視目錄。
3. Inotify 使用系統調用而非 SIGIO 來通知檔案系統事件。
4. Inotify 使用檔案描述符作為介面,因而可以使用通常的檔案 I/O 操作select 和 poll 來監視檔案系統的變化。
1、android 對於inotify的使用方式
以上就是android中主要實現代碼,層次很簡單都只有一個檔案
2、inotify 使用者介面使用
在使用者態,inotify 通過三個系統調用和在返回的檔案描述符上的檔案 I/O 操作來使用
建立 inotify 執行個體:
int fd = inotify_init ();
每一個 inotify 執行個體對應一個獨立的排序的隊列。
檔案系統的變化事件被稱做 watches 的一個對象管理,每一個 watch 是一個二元組(目標,事件掩碼),目標可以是檔案或目錄,事件掩碼錶示應用希望關注的 inotify 事件,每一個位對應一個 inotify 事件。Watch 對象通過 watch描述符引用,watches 通過檔案或目錄的路徑名來添加。目錄 watches 將返回在該目錄下的所有檔案上面發生的事件。
添加一個watch:
int wd = inotify_add_watch (fd, path, mask);
fd 是 inotify_init() 返回的檔案描述符,path 是被監視的目標的路徑名(即檔案名稱或目錄名),mask 是事件掩碼,
在標頭檔 linux/inotify.h 中定義了每一位代表的事件。可以使用同樣的方式來修改事件掩碼,即改變希望被通知的inotify 事件。wd 是 watch 描述符。
刪除一個 watch:
int ret = inotify_rm_watch (fd, wd);
檔案事件變化的讀取:
struct inotify_event {
__s32 wd; /* watch descriptor */
__u32 mask; /* watch mask */
__u32 cookie; /* cookie to synchronize two events */
__u32 len; /* length (including nulls) of name */
char name[0]; /* stub for possible name */
};
結構中的 wd 為被監視目標的 watch 描述符,mask 為事件掩碼,len 為 name字串的長度,name 為被監視目標的路徑名,該結構的 name 欄位為一個樁,它只是為了使用者方面引用檔案名稱,檔案名稱是變長的,它實際緊跟在該結構的後面,檔案名稱將被 0 填充以使下一個事件結構能夠 4 位元組對齊。注意,len 也把填充位元組數統計在內。
通過 read 調用可以一次獲得多個事件,只要提供的 buf 足夠大。
size_t len = read (fd, buf, BUF_LEN);
buf 是一個 inotify_event 結構的數組指標,BUF_LEN 指定要讀取的總長度,buf 大小至少要不小於 BUF_LEN,該調用返回的事件數目取決於 BUF_LEN 以及事件中檔案名稱的長度。Len 為實際讀去的位元組數,即獲得的事件的總長度。可以在函數 inotify_init() 返回的檔案描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 來得到當前隊列的長度。close(fd)將刪除所有添加到
fd 中的 watch 並做必要的清理。
總結整體用法:
int fd = inotify_init();wfd = inotify_add_watch(fd, path, mask);for(;;){// 可以使用 select() / poll() 進行輪詢char event_buf[512]; struct inotify_event* event; int num_bytes = read(fd, event_buf, sizeof(event_buf)); while (num_bytes >= (int)sizeof(*event)){ int event_size; event = (struct inotify_event *)(event_buf + event_pos); //TODO 可以返回正在監控的 event->wd, event->mask ,event->name(path) event_size = sizeof(*event) + event->len; num_bytes -= event_size;event_pos += event_size; } }inotify_rm_watch(fd, wfd);
3、核心實現機理
源碼在:
fs\notify\inotify\inotify.c
fs\notify\inotify\inotify_user.c
先看重要的幾個資料資料
struct inotify_device { wait_queue_head_t wq; /* wait queue for i/o */ struct idr idr; /* idr mapping wd -> watch */ struct semaphore sem; /* protects this bad boy */ struct list_head events; /* list of queued events */ struct list_head watches; /* list of watches */ atomic_t count; /* reference count */ struct user_struct *user; /* user who opened this dev */ unsigned int queue_size; /* size of the queue (bytes) */ unsigned int event_count; /* number of pending events */ unsigned int max_events; /* maximum number of events */ u32 last_wd; /* the last wd allocated */};
在核心中,每一個 inotify 執行個體對應一個 inotify_device 結構,這個裡面有個重要的東東:idr其功能:idr 用於把 watch 描述符映射到對應的 inotify_watch ,如此對於wfd就有具體的描述符才能實現監控。這個 idr 是在 inotify_add_watch 中調用 idr_get_new_above 將 idr 與 wfd 進行映射的
所謂IDR,其實就是和身份證的含義差不多,我們知道,每個人有一個身份證,身份證只是 一串數字,從數字,我們就能知道這個人的資訊。同樣道理,idr的要完成的任務是給要管理的對象分配一個數字,可以通過這個數字找到要管理的對象。
events 為該 inotify 執行個體上發生的事件的列表,被該 inotify 執行個體監視的所有事件在發生後都將插入到這個列表,watches 是給 inotify 執行個體監視的 watch 列表,inotify_add_watch 將把新添加的 watch 插入到該列表
每一個 watch 對應一個 inotify_watch 結構:
struct inotify_watch { struct list_head d_list; /* entry in inotify_device's list */ struct list_head i_list; /* entry in inode's list */ atomic_t count; /* reference count */ struct inotify_device *dev; /* associated device */ struct inode *inode; /* associated inode */ s32 wd; /* watch descriptor */ u32 mask; /* event mask for this watch */};
d_list 指向所有 inotify_device 組成的列表的,i_list 指向所有被監視 inode 組成的列表,count 是引用計數,dev 指向該 watch 所在的 inotify 執行個體對應的 inotify_device 結構,inode 指向該 watch 要監視的 inode,wd 是分配給該 watch 的描述符,mask 是該 watch
的事件掩碼,表示它對哪些檔案系統事件感興趣。
基本的資料結構理清楚,代碼明了不介紹了。
還有一個問題就是如何對sdcard做到unmount事件通知呢?其實就是在核心中有個函數: inotify_unmount_inodes
它會在檔案系統被 umount 時調用來通知 umount 事件給 inotify 系統