Linux inotify功能及實現原理
1. inotify主要功能
它是一個內核用于通知用戶空間程序文件系統變化的機制。
眾所周知,Linux 桌面系統與 MAC 或 Windows 相比有許多不如人意的地方,為了改善這種狀況,開源社區提出用戶態需要內核提供一些機制,以便用戶態能夠及時地得知內核或底層硬件設備發生了什么,從而能夠更好地管理設備,給用戶提供更好的服務,如 hotplug、udev 和 inotify 就是這種需求催生的。Hotplug 是一種內核向用戶態應用通報關于熱插拔設備一些事件發生的機制,桌面系統能夠利用它對設備進行有效的管理,udev 動態地維護 /dev 下的設備文件,inotify 是一種文件系統的變化通知機制,如文件增加、刪除等事件可以立刻讓用戶態得知,該機制是著名的桌面搜索引擎項目 beagle 引入的,并在 Gamin 等項目中被應用。
2. 用戶接口
在用戶態,inotify 通過三個系統調用和在返回的文件描述符上的文件 I/ 操作來使用,使用 inotify 的***步是創建 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);
fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函數的返回值。
文件事件用一個 inotify_event 結構表示,它通過由 inotify_init() 返回的文件描述符使用通常文件讀取函數 read 來獲得:
- 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 inotify_init (void);
- int inotify_add_watch (int fd, const char *path, __u32 mask);
- int inotify_rm_watch (int fd, __u32 mask);
3. 內核實現原理
在內核中,每一個 inotify 實例對應一個 inotify_device 結構:
- 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 */
- };
d_list 指向所有 inotify_device 組成的列表的,i_list 指向所有被監視 inode 組成的列表,count 是引用計數,dev 指向該 watch 所在的 inotify 實例對應的 inotify_device 結構,inode 指向該 watch 要監視的 inode,wd 是分配給該 watch 的描述符,mask 是該 watch 的事件掩碼,表示它對哪些文件系統事件感興趣。
結構 inotify_device 在用戶態調用 inotify_init() 時創建,當關閉 inotify_init()返回的文件描述符時將被釋放。結構 inotify_watch 在用戶態調用 inotify_add_watch()時創建,在用戶態調用 inotify_rm_watch() 或 close(fd) 時被釋放。
無論是目錄還是文件,在內核中都對應一個 inode 結構,inotify 系統在 inode 結構中增加了兩個字段:
- 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 的事件掩碼,表示它對哪些文件系統事件感興趣。
結構 inotify_device 在用戶態調用 inotify_init() 時創建,當關閉 inotify_init()返回的文件描述符時將被釋放。結構 inotify_watch 在用戶態調用 inotify_add_watch()時創建,在用戶態調用 inotify_rm_watch() 或 close(fd) 時被釋放。
無論是目錄還是文件,在內核中都對應一個 inode 結構,inotify 系統在 inode 結構中增加了兩個字段:
- #ifdef CONFIG_INOTIFY
- struct list_head inotify_watches; /* watches on this inode */
- struct semaphore inotify_sem; /* protects the watches list */
- #endif
inotify_watches 是在被監視目標上的 watch 列表,每當用戶調用 inotify_add_watch()時,內核就為添加的 watch 創建一個 inotify_watch 結構,并把它插入到被監視目標對應的 inode 的 inotify_watches 列表。inotify_sem 用于同步對 inotify_watches 列表的訪問。當文件系統發生***部分提到的事件之一時,相應的文件系統代碼將顯示調用fsnotify_* 來把相應的事件報告給 inotify 系統,其中*號就是相應的事件名,目前實現包括:
fsnotify_move,文件從一個目錄移動到另一個目錄fsnotify_nameremove,文件從目錄中刪除fsnotify_inoderemove,自刪除fsnotify_create,創建新文件fsnotify_mkdir,創建新目錄fsnotify_access,文件被讀fsnotify_modify,文件被寫fsnotify_open,文件被打開fsnotify_close,文件被關閉fsnotify_xattr,文件的擴展屬性被修改fsnotify_change,文件被修改或原數據被修改有一個例外情況,就是 inotify_unmount_inodes,它會在文件系統被 umount 時調用來通知 umount 事件給 inotify 系統。
以上提到的通知函數***都調用 inotify_inode_queue_event(inotify_unmount_inodes直接調用 inotify_dev_queue_event ),該函數首先判斷對應的inode是否被監視,這通過查看 inotify_watches 列表是否為空來實現,如果發現 inode 沒有被監視,什么也不做,立刻返回,反之,遍歷 inotify_watches 列表,看是否當前的文件操作事件被某個 watch 監視,如果是,調用 inotify_dev_queue_event,否則,返回。函數inotify_dev_queue_event 首先判斷該事件是否是上一個事件的重復,如果是就丟棄該事件并返回,否則,它判斷是否 inotify 實例即 inotify_device 的事件隊列是否溢出,如果溢出,產生一個溢出事件,否則產生一個當前的文件操作事件,這些事件通過kernel_event 構建,kernel_event 將創建一個 inotify_kernel_event 結構,然后把該結構插入到對應的 inotify_device 的 events 事件列表,然后喚醒等待在inotify_device 結構中的 wq 指向的等待隊列。想監視文件系統事件的用戶態進程在inotify 實例(即 inotify_init() 返回的文件描述符)上調用 read 時但沒有事件時就掛在等待隊列 wq 上。
4. 使用示例
下面是一個使用 inotify 來監視文件系統事件的例子:
- #include
- #include
- #include
- _syscall0(int, inotify_init)
- _syscall3(int, inotify_add_watch, int, fd, const char *, path, __u32, mask)
- _syscall2(int, inotify_rm_watch, int, fd, __u32, mask)
- char * monitored_files[] = {
- "./tmp_file",
- "./tmp_dir",
- "/mnt/sda3/windows_file"
- };
- struct wd_name {
- int wd;
- char * name;
- };
- #define WD_NUM 3
- struct wd_name wd_array[WD_NUM];
- char * event_array[] = {
- "File was accessed",
- "File was modified",
- "File attributes were changed",
- "writtable file closed",
- "Unwrittable file closed",
- "File was opened",
- "File was moved from X",
- "File was moved to Y",
- "Subfile was created",
- "Subfile was deleted",
- "Self was deleted",
- "Self was moved",
- "",
- "Backing fs was unmounted",
- "Event queued overflowed",
- "File was ignored"
- };
- #define EVENT_NUM 16
- #define MAX_BUF_SIZE 1024
- int main(void)
- {
- int fd;
- int wd;
- char buffer[1024];
- char * offset = NULL;
- struct inotify_event * event;
- int len, tmp_len;
- char strbuf[16];
- int i = 0;
- fd = inotify_init();
- if (fd < 0) {
- printf("Fail to initialize inotify.\n");
- exit(-1);
- }
- for (i=0; i<WD_NUM; wd="inotify_add_watch(fd," add (event- if { len) < buffer) - *)event (((char while *)buffer; inotify_event event len); len='%d.\n",' happens, printf(?Some offset="buffer;" MAX_BUF_SIZE)) buffer, while(len="read(fd," } wd_array[i].wd="wd;" exit(-1); wd_array[i].name); %s.\n?, for watch printf(?Can?t 0) (wd IN_ALL_EVENTS); wd_array[i].name, wd_array[i].name="monitored_files[i];" i++)>mask & IN_ISDIR) {
- memcpy(strbuf, "Direcotory", 11);
- }
- else {
- memcpy(strbuf, "File", 5);
- }
- printf("Object type: %s\n", strbuf);
- for (i=0; iwd != wd_array[i].wd) continue;
- printf("Object name: %s\n", wd_array[i].name);
- break;
- }
- printf("Event mask: %08X\n", event->mask);
- for (i=0; imask & (1<len;
- event = (struct inotify_event *)(offset + tmp_len);
- offset += tmp_len;
- }
- }
- }