淺談 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。

延伸閱讀