使用 inotify 監控文件系統的活動
編寫自己的應用程序或使用開源工具套件
系統管理就像日常生活一樣。就像刷牙和吃蔬菜一樣,日常的維護能保持機器的良好狀態。您必須定期清空廢物,比如臨時文件或無用的日誌文件,以及花時間填寫表單、回覆電話、下載更新和監控進程等。幸好自動化 shell 腳本、使用 Nagios 等工具進行監控、通過常見的 cron 進行任務調度可以減輕這個負擔。
但奇怪的是,這些工具沒有一個具有響應性。當然,您可以安排一個頻繁運行的 cron 任務來監控條件,但這樣繁忙的輪詢 — 消耗大量資源並且具有不確定性 — 並不是很理想。例如,如果您必須監控輸入數據的幾個 Transfer Protocol(FTP)收存箱,您可能要通過 find
命令掃描每個目標目錄,列舉新的內容。然而,儘管這個操作看起來並沒有什麼害處,但每個調用都產生一個新的 shell 和 find
命令,這需要許多系統調用來打開目錄,然後掃描目錄,等等。這會造成過於頻繁的或大量的輪詢任務(更糟糕的是,繁忙的輪詢並不總是很好。想象一下一個文件系統瀏覽器,比如
Mac OS X 的 Finder,輪詢更新時需要的大量資源及其複雜性)。
那麼,管理員應該怎麼辦呢?令人高興的是,您可以再次求助於可以信賴的計算機。
Inotify 是一個 Linux 內核特性,它監控文件系統,並且及時向專門的應用程序發出相關的事件警告,比如刪除、讀、寫和卸載操作等。您還可以跟蹤活動的源頭和目標等細節。
使用 inotify 很簡單:創建一個文件描述符,附加一個或多個監視器(一個監視器 是一個路徑和一組事件),然後使用 read()
方法從描述符獲取事件信息。read()
並不會用光整個週期,它在事件發生之前是被阻塞的。
更好的是,因爲 inotify 通過傳統的文件描述符工作,您可以利用傳統的 select()
系統調用來被動地監控監視器和許多其他輸入源。兩種方法 — 阻塞文件描述符和使用 select()
— 都避免了繁忙輪詢。
現在,讓我們深入瞭解 inotify,寫一些 C 代碼,然後看看一組命令行工具,您可以構建並使用它們將命令和腳本附加到文件系統事件。Inotify 不會在中途失去控制,但它可以運行 cat
和 wget
,並且在必要時嚴格執行。
要使用 inotify,您必須具備一臺帶有 2.6.13 或更新內核的 Linux 機器(以前的 Linux 內核版本使用更低級的文件監控器dnotify)。如果您不知道內核的版本,請轉到 shell,輸入 uname -a
:
% uname -a Linux ubuntu-desktop 2.6.24-19-generic #1 SMP ... i686 GNU/Linux |
如果列出的內核版本不低於 2.6.13,您的系統就支持 inotify。您還可以檢查機器的 /usr/include/sys/inotify.h 文件。如果它存在,表明您的內核支持 inotify。
注意:FreeBSD 和 Mac OS X 提供一個類似於 inotify 的 kqueue。在 FreeBSD 機器上輸入 man 2 kqueue
獲取更多信息。
本文基於 Ubuntu Desktop version 8.04.1(即 Hardy),它運行在 Mac OS X version 10.5 Leopard 的 Parallels Desktop version 3.0。
Inotify 提供 3 個系統調用,它們可以構建各種各樣的文件系統監控器:
-
inotify_init()
在內核中創建 inotify 子系統的一個實例,成功的話將返回一個文件描述符,失敗則返回 -1。就像其他系統調用一樣,如果inotify_init()
失敗,請檢查errno
以獲得診斷信息。 -
顧名思義,
inotify_add_watch()
用於添加監視器。每個監視器必須提供一個路徑名和相關事件的列表(每個事件由一個常量指定,比如 IN_MODIFY)。要監控多個事件,只需在事件之間使用邏輯操作符或 — C 語言中的管道線(|
)操作符。如果inotify_add_watch()
成功,該調用會爲已註冊的監視器返回一個惟一的標識符;否則,返回 -1。使用這個標識符更改或刪除相關的監視器。 -
inotify_rm_watch()
刪除一個監視器。
此外,還需要 read()
和 close()
系統調用。如果描述符由 inotify_init()
生成,則調用 read()
等待警告。假設有一個典型的文件描述符,應用程序將阻塞對事件的接收,這些事件在流中表現爲數據。文件描述符上的由 inotify_init()
生成的通用close()
刪除所有活動監視器,並釋放與
inotify 實例相關聯的所有內存(這裏也用到典型的引用計數警告。與實例相關聯的所有文件描述符必須在監視器和 inotify 消耗的內存被釋放之前關閉)。
這個強大的工具提供 3 個應用程序編程接口(API)調用,以及簡單、熟悉的範例 “所有內容都是文件”。現在,我們看看示例應用程序。
清單 1 是一個監控兩個事件的目錄的簡短 C 程序:文件的創建和刪除。
清單 1. 簡單的 inotify 應用程序,它監控創建、刪除和修改事件的目錄
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/inotify.h> #define EVENT_SIZE ( sizeof (struct inotify_event) ) #define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) int main( int argc, char **argv ) { int length, i = 0; int fd; int wd; char buffer[BUF_LEN]; fd = inotify_init(); if ( fd < 0 ) { perror( "inotify_init" ); } wd = inotify_add_watch( fd, "/home/strike", IN_MODIFY | IN_CREATE | IN_DELETE ); length = read( fd, buffer, BUF_LEN ); if ( length < 0 ) { perror( "read" ); } while ( i < length ) { struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ]; if ( event->len ) { if ( event->mask & IN_CREATE ) { if ( event->mask & IN_ISDIR ) { printf( "The directory %s was created.\n", event->name ); } else { printf( "The file %s was created.\n", event->name ); } } else if ( event->mask & IN_DELETE ) { if ( event->mask & IN_ISDIR ) { printf( "The directory %s was deleted.\n", event->name ); } else { printf( "The file %s was deleted.\n", event->name ); } } else if ( event->mask & IN_MODIFY ) { if ( event->mask & IN_ISDIR ) { printf( "The directory %s was modified.\n", event->name ); } else { printf( "The file %s was modified.\n", event->name ); } } } i += EVENT_SIZE + event->len; } ( void ) inotify_rm_watch( fd, wd ); ( void ) close( fd ); exit( 0 ); } |
這個應用程序通過 fd = inotify_init();
創建一個 inotify 實例,並添加一個監視器來監控修改、新文件和 /home/strike 中的損壞文件(由 wd = inotify_add_watch(...)
指定)。read()
方法在一個或多個警告到達之前是被阻塞的。警告的詳細內容
— 每個文件、每個事件 — 是以字節流的形式發送的;因此,應用程序中的循環將字節流轉換成一系列事件結構。
在文件 /usr/include/sys/inotify.h. 中,您可以找到事件結構的定義,它是一種 C 結構,如清單 2 所示。
清單 2. 事件結構的定義
struct inotify_event { int wd; /* The watch descriptor */ uint32_t mask; /* Watch mask */ uint32_t cookie; /* A cookie to tie two events together */ uint32_t len; /* The length of the filename found in the name field */ char name __flexarr; /* The name of the file, padding to the end with NULs */ } |
wd
字段是指與事件相關聯的監視器。如果每個 inotify 有一個以上的實例,您可以使用這個字段確定如何繼續以後的處理過程。mask
字段由幾個部分組成,它說明發生的事情。分別測試每個部分。
當把一個文件從一個目錄移動到另一個目錄時,您可以使用 cookie
將兩個事件綁在一起。僅當您監視源和目標目錄時,inotify 才生成兩個移動事件 — 分別針對源和目標 —,並通過設置 cookie
將它們綁定在一起。要監視一個移動操作,必須指定IN_MOVED_FROM
或 IN_MOVED_TO
,或使用簡短的 IN_MOVE
,它可以監視兩個操作。使用 IN_MOVED_FROM
和 IN_MOVED_TO
來測試事件類型。
最後,name
和 len
包含文件的名稱(但不包括路徑)和受影響文件的名稱的長度。
要構建這些代碼,請將目錄 /home/strike 更改到您的主目錄,即將這些代碼保存到一個文件中,然後調用 C 編譯器 — 在大部分 Linux 系統中爲 gcc。然後,運行這個可執行文件,如清單 3 所示。
清單 3. 運行可執行文件
% cc -o watcher watcher.c % ./watcher |
在監視程序運行時,打開第二個終端窗口並使用 touch
、cat
和 rm
來更改主目錄的內容,如清單 4 所示。完成之後,重新啓動您的新應用程序。
清單 4. 使用 touch、cat 和 rm
% cd $HOME % touch a b c The file a was created. The file b was created. The file c was created. % ./watcher & % rm a b c The file a was deleted. The file b was deleted. The file c was deleted. % ./watcher & % touch a b c The file a was created. The file b was created. The file c was created. % ./watcher & % cat /etc/passwd >> a The file a was modified. % ./watcher & % mkdir d The directory d was created. |
試用其他可用的監視標誌。要捕捉權限的更改,請將 IN_ATTRIB
添加到 mask。
您還可以使用 select()
、pselect()
、poll()
和 epoll()
來避免阻塞。如果您想將監視器的監控作爲圖形應用程序的主事件處理循環的一部分,或作爲監視其他輸入連接的守護進程的一部分,這是很有用的。將該
inotify 描述符添加到這組描述符中,進行併發監控。清單 5 展示了 select()
的標準形式。
清單 5. select() 的標準形式
int return_value; fd_set descriptors; struct timeval time_to_wait; FD_ZERO ( &descriptors ); FD_SET( ..., &descriptors ); FD_SET ( fd, &descriptors ); ... time_to_wait.tv_sec = 3; time.to_waittv_usec = 0; return_value = select ( fd + 1, &descriptors, NULL, NULL, &time_to_wait); if ( return_value < 0 ) { /* Error */ } else if ( ! return_value ) { /* Timeout */ } else if ( FD_ISSET ( fd, &descriptors ) ) { /* Process the inotify events */ ... } else if ... |
select()
方法在 time_to_wait
期間暫停程序。然而,如果在這個延遲期間這組描述符的任意一個文件描述符發生活動,將立即恢復執行程序。否則,調用就會超時,允許應用程序執行其他進程,比如在圖形用戶界面(GUI)工具中響應鼠標或鍵盤事件。
下面是使用 inotify 的其他技巧:
- 如果監視中的文件或目錄被刪除,它的監視器也會被自動刪除(在刪除事件發出之後)。
- 如果在已卸載的文件系統上監控文件或目錄,監視器將在刪除所有受影響的監視之前收到一個卸載事件。
-
將
IN_ONESHOT
標誌添加到監視器標記中,設置一個一次性警告。警告在發送之後將被刪除。 - 要修改一個事件,必須提供相同的路徑名和不同的標記。新監視器將取代老監視器。
-
考慮到實用性,不可能耗盡任何一個 inotify 實例的監視器。然而,您可能會耗盡事件隊列的空間,這取決於處理事件的頻率。隊列溢出會引起
IN_Q_OVERFLOW
事件。 -
close()
方法毀壞 inotify 實例和所有相關聯的監視器,並清空隊列中的所有等待事件。
inotify 編程界面很容易使用,但如果您不想編寫自己的工具,可以使用一種開源的靈活的代替方法。Inotify 工具庫(參見下面的 參考資料 獲得鏈接)提供一對監控文件系統活動的命令行實用程序:
-
inotifywait
僅執行阻塞,等待 inotify 事件。您可以監控任何一組文件和目錄,或監控整個目錄樹(目錄、子目錄、子目錄的子目錄等等)。在 shell 腳本中使用inotifywait
。 -
inotifywatch
收集關於被監視的文件系統的統計數據,包括每個 inotify 事件發生多少次。
在撰寫本文時,最新版本的 inotify 庫是 version 3.13,於 2008 年 1 月發佈。安裝 inotify 工具有兩種方法:可以下載並親自構建該軟件,或使用 Linux 發佈版的包管理器安裝一組二進制文件(如果已知庫包含 inotify 工具)。要在基於 Debian 的發佈版上使用後一種方法,請運行 apt-cache search inotify
,並查找匹配的工具,如清單 6 所示。在本文的示例系統
Ubuntu Desktop version 8.04 上,這些工具已經可用。
清單 6. 搜索 inotify 工具
% apt-cache search inotify incron - cron-like daemon which handles filesystem events inotail - tail replacement using inotify inoticoming - trigger actions when files hit an incoming directory inotify-tools - command-line programs providing a simple interface to inotify iwatch - realtime filesystem monitoring program using inotify libinotify-ruby - Ruby interface to Linux's inotify system libinotify-ruby1.8 - Ruby interface to Linux's inotify system libinotify-ruby1.9 - Ruby interface to Linux's inotify system libinotifytools0 - utility wrapper around inotify libinotifytools0-dev - Development library and header files for libinotifytools0 liblinux-inotify2-perl - scalable directory/file change notification muine-plugin-inotify - INotify Plugin for the Muine music player python-kaa-base - Base Kaa Framework for all Kaa Modules python-pyinotify - Simple Linux inotify Python bindings python-pyinotify-doc - Simple Linux inotify Python bindings % sudo apt-get install inotify-tools ... Setting up inotify-tools. |
但是構建代碼也是很容易的。下載並解壓縮源文件;然後配置、編譯和安裝它,如清單 7 所示。整個過程可能需要 3 分鐘。
清單 7. 構建代碼
% wget \ http://internap.dl.sourceforge.net/sourceforge/inotify-tools/inotify-tools-3.13.tar.gz % tar zxvf inotify-tools-3.13.tar.gz inotify-tools-3.13/ inotify-tools-3.13/missing inotify-tools-3.13/src/ inotify-tools-3.13/src/Makefile.in ... inotify-tools-3.13/ltmain.sh % cd inotify-tools.3.13 % ./configure % make % make install |
現在,您可以使用這個工具了。例如,如果您想監控整個主目錄的更改,請運行 inotifywait
。最簡單的調用是 inotifywait -r -m
,它循環監控參數(-r
),並使該實用程序在每個事件(-m
)之後保持運行:
% inotifywait -r -m $HOME Watches established. |
運行另一個終端窗口,並修改您的主目錄。有趣的是,即使一個簡單的通過 Is
列出的目錄也生成一個事件:
/home/strike OPEN,ISDIR |
閱讀 inotifywait
手冊頁獲得將事件限制到特定列表(反覆地使用 -e event_name
選項來創建列表)的選項,並從循環的監視器中排除匹配的文件(--exclude pattern
)。
就像上面揭示的 apt-cache
,您還可以考慮使用許多其他基於 inotify 的實用程序。incron
實用程序源自於 cron,但它響應 inotify 事件,而不是調度。inoticoming
實用程序專門用於監控收存箱。如果您是
Perl、Ruby 或 Python 開發人員,您可以找到從您喜歡的腳本語言調用 inotify 的模塊和庫。
例如,Perl 編程人員可以使用 Linux::Inotify2
(參見 參考資料 獲得詳細信息)來將 inotify 特性嵌入到任何 Perl 應用程序中。這些取自 Linux::Inotify2
README
文件的代碼演示了監控事件的回調接口,如清單 8 所示。
清單 8. 監控事件的回調接口
use Linux::Inotify2; my $inotify = new Linux::Inotify2 or die "Unable to create new inotify object: $!"; # for Event: Event->io (fd =>$inotify->fileno, poll => 'r', cb => sub { $inotify->poll }); # for Glib: add_watch Glib::IO $inotify->fileno, in => sub { $inotify->poll }; # manually: 1 while $inotify->poll; # add watchers $inotify->watch ("/etc/passwd", IN_ACCESS, sub { my $e = shift; my $name = $e->fullname; print "$name was accessed\n" if $e->IN_ACCESS; print "$name is no longer mounted\n" if $e->IN_UNMOUNT; print "$name is gone\n" if $e->IN_IGNORED; print "events for $name have been lost\n" if $e->IN_Q_OVERFLOW; # cancel this watcher: remove no further events $e->w->cancel; }); |
因爲 Linux 中的所有東西都是一個文件,所以您將發現 inotify 有大量的用法。
因此,問題可以歸結爲 “誰監視監視器?”
學習
-
您可以參閱本文在 developerWorks 全球站點上的 英文原文。
-
更多地瞭解 inotify 的歷史。
-
您現在仍然使用 cron 嗎?“Linux 技巧: 用 cron 和 at 調度作業”(developerWorks,2007 年 7 月)和 “Linux
技巧: 控制預定作業的持續時間”(developerWorks,2007 年 7 月)將幫助您更好地利用 cron。
-
閱讀 “系統管理員工具包: 日誌文件基礎知識”(developerWorks,2008 年 2 月)獲得關於管理日誌文件的更多信息。
-
更多地瞭解 incron,它源自 cron,運行的任務針對於文件系統事件。
-
在 developerWorks Linux 專區 可以找到更多針對 Linux 開發人員的資源。瀏覽我們的 最流行的文章和教程。
-
在 developerWorks 上查閱所有 Linux 技巧 和 Linux
教程。
-
隨時關注 developerWorks 技術活動和網絡廣播。
獲得產品和技術
-
下載 inotify 工具的源代碼,這是一組監控文件系統的命令行實用程序。
-
訂購 SEK for Linux,共包含兩張 DVD,其中有用於 Linux 的最新 IBM 試用軟件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
-
使用可直接從 developerWorks 下載的 IBM 試用軟件 構建您的下一個 Linux 開發項目。
討論
-
通過 blog、論壇、podcast 和空間加入 developerWorks 社區。
Martin Streicher 是一位自由 Web 開發人員,並且是 Linux Magazine 的前任主編。Martin 擁有 Purdue University 計算機科學的碩士學位,並從 1986 年開始編寫 UNIX 風格的系統。他喜歡收藏藝術品和玩