萬盛學電腦網

 萬盛學電腦網 >> Linux教程 >> 詳解Linux 2.6內核新文件系統變化機制

詳解Linux 2.6內核新文件系統變化機制

class="21613">

  本文詳細地介紹了在 Linux 2.6.13 內核中新引入的文件系統變化通知機制 inotify,並舉例說明了它的使用與典型應用案例。
  一、引言

  眾所周知,Linux 桌面系統與 MAC 或 Windows 相比有許多不如人意的地方,為了改善這種狀況,開源社區提出用戶態需要內核提供一些機制,以便用戶態能夠及時地得知內核或底層硬件設備發生了什麼,從而能夠更好地管理設備,給用戶提供更好的服務,如 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 來監視文件系統的變化。

  Inotify 可以監視的文件系統事件包括:

IN_ACCESS,即文件被訪問
IN_MODIFY,文件被 write
IN_ATTRIB,文件屬性被修改,如 chmod、chown、touch 等
IN_CLOSE_WRITE,可寫文件被 close
IN_CLOSE_NOWRITE,不可寫文件被 close
IN_OPEN,文件被 open
IN_MOVED_FROM,文件被移走,如 mv
IN_MOVED_TO,文件被移來,如 mv、cp
IN_CREATE,創建新文件
IN_DELETE,文件被刪除,如 rm
IN_DELETE_SELF,自刪除,即一個可執行文件在執行時刪除自己
IN_MOVE_SELF,自移動,即一個可執行文件在執行時移動自己
IN_UNMOUNT,宿主文件系統被 umount
IN_CLOSE,文件被關閉,等同於(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
IN_MOVE,文件被移動,等同於(IN_MOVED_FROM | IN_MOVED_TO)
注:上面所說的文件也包括目錄。


  二、用戶接口

  在用戶態,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 {
__s32wd;;;/* watch descriptor */
__u32mask;/* watch mask */
__u32cookie; /* cookie to synchronize two events */
__u32len;;/* 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);

  三、內核實現機理
  在內核中,每一個 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 */
};

  wq 是等待隊列,被 read 調用阻塞的進程將掛在該等待隊列上,idr 用於把 watch 描述符映射到對應的 inotify_watch,sem 用於同步對該結構的訪問,events 為該 inotify 實例上發生的事件的列表,被該 inotify 實例監視的所有事件在發生後都將插入到這個列表,watches 是給 inotify 實例監視的 watch 列表,inotify_add_watch 將把新添加的 watch 插入到該列表,count 是引用計數,user 用於描述創建該 inotify 實例的用戶,queue_size 表示該 inotify 實例的事件隊列的字節數,event_count 是 events 列表的事件數,max_events 為最大允許的事件數,last_wd 是上次分配的 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 的事件掩碼,表示它對哪些文件系統事件感興趣。

  結構 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 創建一個 in

copyright © 萬盛學電腦網 all rights reserved