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