歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> 更多Linux >> 使用Inotify 監控Linux 文件系統事件

使用Inotify 監控Linux 文件系統事件

日期:2017/2/27 14:13:42   编辑:更多Linux

Inotify 是文件系統事件監控機制,計劃包含在即將發布的 Linux 內核中作為 dnotify 的有效替代。dnotify 是較早內核支持的文件監控機制。Inotify 是一種強大的、細粒度的、異步的機制,它滿足各種各樣的文件監控需要,不僅限於安全和性能。下面讓我們一起學習如何安裝 inotify 和如何構建一個示例用戶空間應用程序來響應文件系統事件。

文件系統事件監控對於從文件管理器到安全工具的各種程序都是必要的,但是 dnotify(早期內核中的標准)存在一些局限性,這使我們期待出現一種更加完善的機制。抱著這種期待,我們發現了 inotify,一種更加現代化的文件系統事件監控替代品。

為什麼使用 inotify?

使用 inotify 取代 dnotify 的原因有很多。第一個原因是,dnotify 需要您為每個打算監控是否發生改變的目錄打開一個文件描述符。當同時監控多個目錄時,這會消耗大量的資源,因為有可能達到每個進程的文件描述符限制。

除此之外,文件描述符會鎖定目錄,不允許卸載(unmount)支持的設備,這在存在可移動介質的環境中會引發問題。在使用 inotify 時,如果正在監控被卸載的文件系統上的文件,那麼監控會被自動移除並且您會接收到一個卸載事件。

dnotify 不如 inotify 的第二個原因是 dnotify 有點復雜。注意,使用 dnotify 基礎設施的簡單文件系統監控粒度只停留於目錄級別。為了使用 dnotify 進行更細粒度的監控,應用程序編程人員必須為每個受監控的目錄保留一個 stat 結構的緩存。該用戶空間的 stat 結構緩存需要用來明確確定當接收到通知信號時目錄發生了什麼變化。當獲得通知信號時,生成 stat 結構列表並與最新的狀態相比較。顯而易見,這種技術是不理想的。

inotify 的另一個優點是它使用文件描述符作為基本接口,使應用程序開發者使用 select 和 poll 來監控設備。這允許有效的多路 I/O 和與 Glib 的 mainloop 的集成。相反,dnotify 所使用的信號常常使程序員頭疼並且感覺不太優雅。

inotify 通過提供一個更優雅的 API 解決了這些問題,該 API 使用最少的文件描述符,並確保更細粒度的監控。與 inotify 的通信是通過設備節點提供的。基於以上原因,對於監控 Linux 2.6 平台上的文件,inotify 是您最明智的選擇。

安裝 inotify

安裝 inotify 的第一步是確定您使用的 Linux 內核是否支持它。檢查發行版的最簡單方法是,尋找是否存在 /dev/inotify 設備。如果存在該設備,您可以跳到 在簡單應用程序中使用 inotify 一節。

在撰寫本文時,inotify 包含在 Andrew Morton 的 Linux 2.6-mm 目錄樹中,而且一些 Linux 發行版正在提供支持 inotify 的內核(包括 Gentoo 和 Ubuntu)或者具有提供支持的補充內核包(例如 Fedora 和 SuSE)。因為 Andrew 可能會根據需要從目錄樹刪除對 inotify 的支持,並且 inotify 版本還處於頻繁的開發階段,所以強烈建議您從頭開始打補丁。

如果缺少該設備,您可能需要對內核打補丁並創建該設備。

為 inotify 對內核打補丁

可以從 Linux Kernel Archives 獲得 inotify 補丁。您應該為特定的內核應用最高版本編號的補丁。每個發行版處理內核的安裝都有所不同,但以下介紹的是一個通用指導。注意:從 Linux Kernel Archives 獲取發行版 2.6 Linux 內核源文件,如果合適,請獲取最新的穩定版本。

從進入內核源文件目錄開始:

bash:~$ cd /usr/src

因為您早先安裝了內核源文件,現在需要將它解壓縮:

bash:~$ sudo tar jxvf linux-source-2.6.8.1.tar.bz2

現在,使您的 symlink 指向新的源文件目錄樹:

bash:~$ sudo ln -sf linux-source-2.6.8.1 linux

改變當前目錄到剛才創建的內核源文件目錄:

bash:~$ cd linux

拷貝 inotify 補丁:

bash:~$ sudo cp ~/inotify* /usr/src

將內核打補丁:

bash:~$ sudo patch -p1 < ../inotify*.patch

構建內核:

bash:~$ sudo make menUConfig

像平時一樣配置您的內核,確保 inotify 工作正常。如果必要,請將新內核添加到引導加載程序中,但是一定要記住維護舊內核的映像和引導加載程序選項。這一步對於不同引導加載程序有所不同(請參閱 參考資料 了解關於特定引導加載程序的更多信息)。重新引導計算機並選擇啟用 inotify 的新內核。在繼續往下操作前,測試您的新內核以確保它工作正常。

創建 inotify 設備

接下來,您需要確保創建 /dev/inotify 設備。以下步驟帶領您完成這個過程。重要注意:次設備編號可能會發生改變,所以您需要多加注意以確保它隨時更新!如果 Linux 安裝支持 udev 功能,它將會自動保持更新。

在重新引導到新內核後,您必須獲取次設備編號:

bash:~$ dmesg grep ^inotify

返回結果示例如下:

inotify device minor=63

因為 inotify 是 misc 設備,所以主設備編號是 10。要創建設備節點作為根用戶,請執行以下命令:

bash:~$ mknod /dev/inotify c 10 63

注意:如有必要,請使用合適的次設備編號替換“63”。

您可以隨意設置您想要的權限。一個示例權限設置如下所示:

bash:~$ chown root:root /dev/inotify bash:~$ chmod 666 /dev/inotify

現在准備使用 inotify 設備進行文件系統監控。

在簡單應用程序中使用 inotify

為演示 inotify 的使用,我將展示如何為文件系統事件構造一個監控任意目錄(或單個文件)的示例程序。我將站在一個較高的層次上來展示 inotify 使文件系統監控變得多麼容易。

Main 方法

這個簡單的示例向我們展示 inotify 在任意目錄上設置監控是多麼容易。稍後我們將看到主要的幫助器例程。您可以在本文的 下載 一節獲取這些例子中使用的示例代碼。

/* This program will take as argument a Directory name and monitor it, printing event notifications to the console. */ int main (int argc, char **argv) { /* This is the file descriptor for the inotify device */ int inotify_fd; /* First we open the inotify dev entry */ inotify_fd = open_inotify_dev(); if (inotify_fd < 0) { return 0; } /* We will need a place to enqueue inotify events, this is needed because if you do not read events fast enough, you will miss them. */ queue_t q; q = queue_create (128); /* Watch the directory passed in as argument Read on for why you might want to alter this for more efficient inotify use in your app. */ watch_dir (inotify_fd, argv[1], ALL_MASK); process_inotify_events (q, inotify_fd); /* Finish up by destroying the queue, closing the fd, and returning a proper code */ queue_destroy (q); close_inotify_dev (inotify_fd); return 0; }

清單 1. 在目錄上設置監控

重要的幫助器方法

以下是每個基於 inotify 的應用程序共同的最重要的幫助器例程:

為讀取而打開 inotify 設備。

對從該設備讀取的事件進行排隊。

允許應用程序對事件通知進行有用處理的實際的每事件處理器。

我不會深入鑽研事件排隊的細節,因為我們能夠使用一些策略來避免排隊。提供的代碼中就展示了一個這樣的方法;更先進的多線程方法可以並且已經在其他地方實現。在那些實現中,讀者線程簡單地在 inotify 設備上執行 select(),然後將事件拷貝到一些線程共享的存儲空間(或者一些像 Glib 的異步消息隊列的東西),以後處理器線程會處理這裡的事件。

/* This simply opens the inotify node in dev (read only) */ int open_inotify_dev () { int fd; fd = open("/dev/inotify", O_RDONLY); if (fd < 0) { perror ("open(\"/dev/inotify\", O_RDONLY) = "); } return fd; }

清單 2. 打開 inotify 設備

這對任何一個在 Linux 系統上進行過文件編程的人來說都應該是熟悉的。

/* This method does the dirty work of determining what happened, then allows us to act appropriately */ void handle_event (struct inotify_event *event) { /* If the event was associated with a filename, we will store it here */ char * cur_event_filename = NULL; /* This is the watch descriptor the event occurred on */ int cur_event_wd = event->wd; if (event->len) { cur_event_filename = event->filename; } printf("FILENAME=%s\n", cur_event_filename); printf("\n"); /* Perform event dependent handler routines */ /* The mask is the magic that tells us what file operation occurred */ switch (event->mask) { /* File was Accessed */ case IN_ACCESS: printf("ACCESS EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was modified */ case IN_MODIFY: printf("MODIFY EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File changed attributes */ case IN_ATTRIB: printf("ATTRIB EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was closed */ case IN_CLOSE: printf("CLOSE EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was opened */ case IN_OPEN: printf("OPEN EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was moved from X */ case IN_MOVED_FROM: printf("MOVE_FROM EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was moved to X */ case IN_MOVED_TO: printf("MOVE_TO EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* Subdir was deleted */ case IN_DELETE_SUBDIR: printf("DELETE_SUBDIR EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was deleted */ case IN_DELETE_FILE: printf("DELETE_FILE EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* Subdir was created */ case IN_CREATE_SUBDIR: printf("CREATE_SUBDIR EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* File was created */ case IN_CREATE_FILE: printf("CREATE_FILE EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* Watched entry was deleted */ case IN_DELETE_SELF: printf("DELETE_SELF EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* Backing FS was unmounted */ case IN_UNMOUNT: printf("UNMOUNT EVENT OCCURRED: File \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; /* Too many FS events were received without reading them some event notifications were potentially lost. */ case IN_Q_OVERFLOW: printf("Warning: AN OVERFLOW EVENT OCCURRED: \n"); break; case IN_IGNORED: printf("IGNORED EVENT OCCURRED: \n"); break; /* Some unknown message received */ default: printf ("UNKNOWN EVENT OCCURRED for file \"%s\" on WD #%i\n", cur_event_filename, cur_event_wd); break; } }

清單 3. 實際的事件處理例程

在每一條 case 語句中,您可以隨意執行任意已實現並且滿足需要的方法。

至於性能監控,您可以確定哪些文件是最經常被讀取的和它們打開的持續時間。這種監控非常方便,因為在某些情況下,如果文件在短時間內被應用程序重復地讀取,它會將文件緩存在內存中而不用返回磁盤去讀取,從而提高性能。

很容易舉出一些執行有趣操作的特定於事件的處理器的例子。比如,如果您是在為底層文件系統實現一個元數據存儲索引,您可能會尋找文件創建事件,不久還會在該文件上觸發一個元數據挖掘操作。在安全環境中,如果文件被寫入一個無人可以寫入的目錄,您會觸發某些形式的系統警報。

請注意,inotify 支持許多非常細粒度的事件 —— 例如 CLOSE 與 CLOSE_WRITE。

本文中的代碼所列舉的許多事件,可能您並不希望在每次代碼運行時都看到。實際上,只要可能,您可以並且應該只請求對您的應用程序有用的事件子集。出於測試目的,本文章提供的代碼通過嚴格使用完整掩碼(如可下載的示例代碼[請參閱 參考資料] 中 main 方法的第 51 行附近或者上面的 清單 1 中的第 29 行所執行的)展示了許多事件。應用程序員通常想要有更多選擇,而您則需要更特定的掩碼來滿足您的需要。這使您可以從上述的 handle_event() 方法中的 catch 語句刪除不感興趣的條目。

結束語

當應用於性能監控、調試和自動化領域時,inotify 是一種用於監控 Linux 文件系統的、強大且細粒度的機制。使用本文提供的代碼,您就可以編寫能夠以最低的性能開銷響應或記錄文件系統事件的應用程序。




Copyright © Linux教程網 All Rights Reserved