Linux inotify功能及實現(xiàn)原理
1. inotify主要功能
它是一個內(nèi)核用于通知用戶空間程序文件系統(tǒng)變化的機制。
眾所周知,Linux 桌面系統(tǒng)與 MAC 或 Windows 相比有許多不如人意的地方,為了改善這種狀況,開源社區(qū)提出用戶態(tài)需要內(nèi)核提供一些機制,以便用戶態(tài)能夠及時地得知內(nèi)核或底層硬件設(shè)備發(fā)生了什么,從而能夠更好地管理設(shè)備,給用戶提供更好的服務(wù),如 hotplug、udev 和 inotify 就是這種需求催生的。Hotplug 是一種內(nèi)核向用戶態(tài)應(yīng)用通報關(guān)于熱插拔設(shè)備一些事件發(fā)生的機制,桌面系統(tǒng)能夠利用它對設(shè)備進行有效的管理,udev 動態(tài)地維護 /dev 下的設(shè)備文件,inotify 是一種文件系統(tǒng)的變化通知機制,如文件增加、刪除等事件可以立刻讓用戶態(tài)得知,該機制是著名的桌面搜索引擎項目 beagle 引入的,并在 Gamin 等項目中被應(yīng)用。
2. 用戶接口
在用戶態(tài),inotify 通過三個系統(tǒng)調(diào)用和在返回的文件描述符上的文件 I/ 操作來使用,使用 inotify 的***步是創(chuàng)建 inotify 實例:
- int fd = inotify_init ();
每一個 inotify 實例對應(yīng)一個獨立的排序的隊列。
文件系統(tǒng)的變化事件被稱做 watches 的一個對象管理,每一個 watch 是一個二元組(目標,事件掩碼),目標可以是文件或目錄,事件掩碼表示應(yīng)用希望關(guān)注的 inotify 事件,每一個位對應(yīng)一個 inotify 事件。Watch 對象通過 watch描述符引用,watches 通過文件或目錄的路徑名來添加。目錄 watches 將返回在該目錄下的所有文件上面發(fā)生的事件。
下面函數(shù)用于添加一個 watch:
- int wd = inotify_add_watch (fd, path, mask);
fd 是 inotify_init() 返回的文件描述符,path 是被監(jiān)視的目標的路徑名(即文件名或目錄名),mask 是事件掩碼, 在頭文件 linux/inotify.h 中定義了每一位代表的事件??梢允褂猛瑯拥姆绞絹硇薷氖录诖a,即改變希望被通知的inotify 事件。Wd 是 watch 描述符。
下面的函數(shù)用于刪除一個 watch:
- int ret = inotify_rm_watch (fd, wd);
fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函數(shù)的返回值。
文件事件用一個 inotify_event 結(jié)構(gòu)表示,它通過由 inotify_init() 返回的文件描述符使用通常文件讀取函數(shù) 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 */
- };
結(jié)構(gòu)中的 wd 為被監(jiān)視目標的 watch 描述符,mask 為事件掩碼,len 為 name字符串的長度,name 為被監(jiān)視目標的路徑名,該結(jié)構(gòu)的 name 字段為一個樁,它只是為了用戶方面引用文件名,文件名是變長的,它實際緊跟在該結(jié)構(gòu)的后面,文件名將被 0 填充以使下一個事件結(jié)構(gòu)能夠 4 字節(jié)對齊。注意,len 也把填充字節(jié)數(shù)統(tǒng)計在內(nèi)。
通過 read 調(diào)用可以一次獲得多個事件,只要提供的 buf 足夠大。
- size_t len = read (fd, buf, BUF_LEN);
buf 是一個 inotify_event 結(jié)構(gòu)的數(shù)組指針,BUF_LEN 指定要讀取的總長度,buf 大小至少要不小于 BUF_LEN,該調(diào)用返回的事件數(shù)取決于 BUF_LEN 以及事件中文件名的長度。Len 為實際讀去的字節(jié)數(shù),即獲得的事件的總長度。
可以在函數(shù) 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. 內(nèi)核實現(xiàn)原理
在內(nèi)核中,每一個 inotify 實例對應(yīng)一個 inotify_device 結(jié)構(gòu):
- 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 指向所有被監(jiān)視 inode 組成的列表,count 是引用計數(shù),dev 指向該 watch 所在的 inotify 實例對應(yīng)的 inotify_device 結(jié)構(gòu),inode 指向該 watch 要監(jiān)視的 inode,wd 是分配給該 watch 的描述符,mask 是該 watch 的事件掩碼,表示它對哪些文件系統(tǒng)事件感興趣。
結(jié)構(gòu) inotify_device 在用戶態(tài)調(diào)用 inotify_init() 時創(chuàng)建,當關(guān)閉 inotify_init()返回的文件描述符時將被釋放。結(jié)構(gòu) inotify_watch 在用戶態(tài)調(diào)用 inotify_add_watch()時創(chuàng)建,在用戶態(tài)調(diào)用 inotify_rm_watch() 或 close(fd) 時被釋放。
無論是目錄還是文件,在內(nèi)核中都對應(yīng)一個 inode 結(jié)構(gòu),inotify 系統(tǒng)在 inode 結(jié)構(gòu)中增加了兩個字段:
- 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 指向所有被監(jiān)視 inode 組成的列表,count 是引用計數(shù),dev 指向該 watch 所在的 inotify 實例對應(yīng)的 inotify_device 結(jié)構(gòu),inode 指向該 watch 要監(jiān)視的 inode,wd 是分配給該 watch 的描述符,mask 是該 watch 的事件掩碼,表示它對哪些文件系統(tǒng)事件感興趣。
結(jié)構(gòu) inotify_device 在用戶態(tài)調(diào)用 inotify_init() 時創(chuàng)建,當關(guān)閉 inotify_init()返回的文件描述符時將被釋放。結(jié)構(gòu) inotify_watch 在用戶態(tài)調(diào)用 inotify_add_watch()時創(chuàng)建,在用戶態(tài)調(diào)用 inotify_rm_watch() 或 close(fd) 時被釋放。
無論是目錄還是文件,在內(nèi)核中都對應(yīng)一個 inode 結(jié)構(gòu),inotify 系統(tǒng)在 inode 結(jié)構(gòu)中增加了兩個字段:
- #ifdef CONFIG_INOTIFY
- struct list_head inotify_watches; /* watches on this inode */
- struct semaphore inotify_sem; /* protects the watches list */
- #endif
inotify_watches 是在被監(jiān)視目標上的 watch 列表,每當用戶調(diào)用 inotify_add_watch()時,內(nèi)核就為添加的 watch 創(chuàng)建一個 inotify_watch 結(jié)構(gòu),并把它插入到被監(jiān)視目標對應(yīng)的 inode 的 inotify_watches 列表。inotify_sem 用于同步對 inotify_watches 列表的訪問。當文件系統(tǒng)發(fā)生***部分提到的事件之一時,相應(yīng)的文件系統(tǒng)代碼將顯示調(diào)用fsnotify_* 來把相應(yīng)的事件報告給 inotify 系統(tǒng),其中*號就是相應(yīng)的事件名,目前實現(xiàn)包括:
fsnotify_move,文件從一個目錄移動到另一個目錄fsnotify_nameremove,文件從目錄中刪除fsnotify_inoderemove,自刪除fsnotify_create,創(chuàng)建新文件fsnotify_mkdir,創(chuàng)建新目錄fsnotify_access,文件被讀fsnotify_modify,文件被寫fsnotify_open,文件被打開fsnotify_close,文件被關(guān)閉fsnotify_xattr,文件的擴展屬性被修改fsnotify_change,文件被修改或原數(shù)據(jù)被修改有一個例外情況,就是 inotify_unmount_inodes,它會在文件系統(tǒng)被 umount 時調(diào)用來通知 umount 事件給 inotify 系統(tǒng)。
以上提到的通知函數(shù)***都調(diào)用 inotify_inode_queue_event(inotify_unmount_inodes直接調(diào)用 inotify_dev_queue_event ),該函數(shù)首先判斷對應(yīng)的inode是否被監(jiān)視,這通過查看 inotify_watches 列表是否為空來實現(xiàn),如果發(fā)現(xiàn) inode 沒有被監(jiān)視,什么也不做,立刻返回,反之,遍歷 inotify_watches 列表,看是否當前的文件操作事件被某個 watch 監(jiān)視,如果是,調(diào)用 inotify_dev_queue_event,否則,返回。函數(shù)inotify_dev_queue_event 首先判斷該事件是否是上一個事件的重復(fù),如果是就丟棄該事件并返回,否則,它判斷是否 inotify 實例即 inotify_device 的事件隊列是否溢出,如果溢出,產(chǎn)生一個溢出事件,否則產(chǎn)生一個當前的文件操作事件,這些事件通過kernel_event 構(gòu)建,kernel_event 將創(chuàng)建一個 inotify_kernel_event 結(jié)構(gòu),然后把該結(jié)構(gòu)插入到對應(yīng)的 inotify_device 的 events 事件列表,然后喚醒等待在inotify_device 結(jié)構(gòu)中的 wq 指向的等待隊列。想監(jiān)視文件系統(tǒng)事件的用戶態(tài)進程在inotify 實例(即 inotify_init() 返回的文件描述符)上調(diào)用 read 時但沒有事件時就掛在等待隊列 wq 上。
4. 使用示例
下面是一個使用 inotify 來監(jiān)視文件系統(tǒng)事件的例子:
- #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;
- }
- }
- }