淺談 Linux Kernel FS Notifier

What’s Notifier?

如果你要寫一個 Linux Application,其中必須要監視一個檔案的變化,當此檔案被修改或是更新時,需要發送一個即時網路封包通知管理者,在這樣的情況之下你會怎麼實作?Endless polling?

為了解決諸如此類的情境,Linux Kernel 提供一個檔案系統 notification 機制給 User Space 應用程式使用。Application 在 User Mode 對核心註冊後,核心就會監看該特定目錄或是檔案的狀態,當特定的變化發生(稱之為 EVENT),就會通知該 Application。而這樣的一個機制在 Linux Kernel 中被稱為 Notifier。

目前的 Linux Kernel 提供幾種 Notifier,分別為 dnotify, inotify, 與 fanotify,簡述如下。

dnotify

開始出現於 linux kernel 2.4 版本,作為一個 directory notifier,故稱為 dnotify。當一個特定目錄下的檔案發生 EVENT (讀取,修改,改變權限…)時,kernel 就會發送 signal 給 user application。核心在編譯時必須先選上 CONFIG_DNOTIFY 才可支援 dnotify,一般 Linux distribution 預設都會包含,但若是嵌入式系統則需留意。

dnotify 支援的 EVENT 如下:

        DN_ACCESS	該目錄下的檔案被存取(read)
	DN_MODIFY	該目錄下的檔案被修改(write)
	DN_CREATE	該目錄下有檔案被建立
	DN_DELETE	該目錄下有檔案被刪除(unlink)
	DN_RENAME	該目錄下有檔案被更名
	DN_ATTRIB	該目錄下有檔案屬性被修改(chmod,chown)

User Application 實作 dnotify 的方式為:

  1. 預先設置好 signal handler。
  2. 開啟目錄的 file descriptor。
  3. 藉由 fcnctl() 操作該 fd,對核心註冊要監控的 EVENT。

範例程式碼:

#define _GNU_SOURCE	/* needed to get the defines */
#include <fcntl.h>	/* in glibc 2.2 this has the needed
				   values defined */
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

static volatile int event_fd;

static void handler(int sig, siginfo_t *si, void *data)
{
	event_fd = si->si_fd;
}

int main(void)
{
	struct sigaction act;
	int fd;

	act.sa_sigaction = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;
	sigaction(SIGRTMIN + 1, &act, NULL);

	fd = open(".", O_RDONLY);
	fcntl(fd, F_SETSIG, SIGRTMIN + 1);
	fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_MULTISHOT);
	/* we will now be notified if any of the files
	   in "." is modified or new files are created */
	while (1) {
		pause();
		printf("Got event on fd=%d\n", event_fd);
	}
}

inotify

Kernel 版本 2.6.13 開始支援 inotify。取代了 directory-based 的 dnotify, 改變為 inode-based ,因此稱為 inotify。inotify 提供比 dnotify 還要強大的功能,其所支援的 EVENT:

 IN_ACCESS         檔案被存取(read) (*).
 IN_ATTRIB         Metadata 已改變,包含權限, 時戳, 屬性, UID, GID 等.
 IN_CLOSE_WRITE    可寫入的檔案被關閉 (*).
 IN_CLOSE_NOWRITE  不可寫入的檔案被關閉 (*).
 IN_CREATE         監控的資料夾下有檔案被建立 (*).
 IN_DELETE         監控的資料夾下有檔案被刪除 (*).
 IN_DELETE_SELF    監控的目標被刪除
 IN_MODIFY         檔案被修改 (*).
 IN_MOVE_SELF      監控的目標被移動.
 IN_MOVED_FROM     檔案被移出監控的資料夾外 (*).
 IN_MOVED_TO       檔案被移入監控的資料夾內 (*).
 IN_OPEN           檔案被開啟 (*).
(*)代表可以套用在

inotify 有專屬的 system call 提供給 user space 呼叫。

#include <sys/inotify.h>
int inotify_init(void);
int inotify_init1(int flags);
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
int inotify_rm_watch(int fd, int wd);

除此之外,甚至還有 kernel API 提供給 kernel 其他 module 使用:

	struct inotify_handle *inotify_init(struct inotify_operations *ops);
	inotify_init_watch(struct inotify_watch *watch);
	s32 inotify_add_watch(struct inotify_handle *ih,
		              struct inotify_watch *watch,
			      struct inode *inode, u32 mask);
	s32 inotify_find_watch(struct inotify_handle *ih, struct inode *inode,
			       struct inotify_watch **watchp);
	s32 inotify_find_update_watch(struct inotify_handle *ih,
				      struct inode *inode, u32 mask);
	int inotify_rm_wd(struct inotify_handle *ih, u32 wd);
	int inotify_rm_watch(struct inotify_handle *ih,
			     struct inotify_watch *watch);
	void inotify_remove_watch_locked(struct inotify_handle *ih,
					 struct inotify_watch *watch);
	void inotify_destroy(struct inotify_handle *ih);
	void get_inotify_watch(struct inotify_watch *watch);
	void put_inotify_watch(struct inotify_watch *watch);

這麼強大的 inotify 其實還是有些限制的,第一,無法監控目錄下的子目錄,所有的子目錄都必須獨立監控。第二,無法監控 /proc 與 /sys。同樣地,核心要支援 inotify 必須在編譯時選上 CONFIG_INOTIFY_USER

dnotify v.s inotify

以下要點方式敘述兩者之間的差異

  • dnotify 只能針對目錄最監控,inotify 可以針對任意檔案做監控。
  • dnotify 要對 fd 操作,因此受限於單一 process 最大 fd 上限。inotify 則不會。
  • dnotify 提供給 user-space 的界面是個災難(不是我說的XD),kernel 只能使用 signal 來通知 AP,況且有些 signal 還並非 real-time 。inotify 則有專屬且良好的 system call 提供給 user-space。
  • dnotify 已註冊的情況下不允許 unmount。inotify 在 unmount 時會發送訊息。
  • inotify 支援更多的 EVENT。

fanotify

全名為 fscking all notifiction and file access system。自 Kernel 版本 2.6.36 開始支援。主要 API 為:

#include <sys/fanotify.h>
int fanotify_init(unsigned int flags, unsigned int event_f_flags);
int fanotify_mark (int fanotify_fd, unsigned int flags, uint64_t mask, int dfd, const char *pathname);

相較於 inotify 有以下差異:

  • fanotify 有 global 模式可以監控整個檔案系統的變化,藉以彌補 inotify 無法監控子目錄的不足。即便如此,fanotify 還是需要加以判斷以便後續功能使用。
  • fanotify 可以阻斷 EVENT。
  • fanotify 支援的 EVENT 較 inotfiy 少得許多。如不支援 Delete, move 等等。

如同前面兩個 notifier,如果要支援 fanotify,核心在編譯時要選上 CONFIG_FANOTIFY

小結

截至目前為止,對於 fanotify 相關的實作與文件(包含 man page)還不如 inotify 來得多。許多人說 fanotify 是要替代 inotify (就如同 inotify 替代 dontify 一樣),但我並不這麼認為,最重要的一點就是 inotify 還是比 fanotify 強大好用(至少現在是如此)。最初 fanotify 作者 Eric Paris 會增加此機制最大的原因是為了商業防毒軟體所撰寫,我想這也是目前 fanotify 功能進步緩慢的原因了。於此,如果要實作檔案監控,目前最佳的選擇仍然是 inotify。

延伸閱讀

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *


6 − = 五

你可以使用這些 HTML 標籤與屬性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>