Inotify 是一個 Linux® 特性,它監控文件系統操作,比如讀取、寫入和創建。Inotify 反應靈敏,用法非常簡單,並且比 cron 任務的繁忙輪詢高效得多。學習如何將 inotify 集成到您的應用程序中,並發現一組可用來進一步自動化系統管理的命令行工具。
系統管理就像日常生活一樣。就像刷牙和吃蔬菜一樣,日常的維護能保持機器的良好狀態。您必須定期清空廢物,比如臨時文件或無用的日誌文件,以及花時間填寫表單、回複電話、下載更新和監控進程等。幸好自動化 shell 腳本、使用 Nagios 等工具進行監控、通過常見的 cron 進行任務調度可以減輕這個負擔。
但奇怪的是,這些工具沒有一個具有響應性。當然,您可以安排一個頻繁運行的 cron 任務來監控條件,但這樣繁忙的輪詢 — 消耗大量資源並且具有不確定性 — 並不是很理想。例如,如果您必須監控輸入數據的幾個 Transfer Protocol(FTP)收存箱,您可能要通過 find 命令掃描每個目標目錄,列舉新的內容。然而,儘管這個操作看起來並沒有什麼害處,但每個調用都產生一個新的 shell 和 find 命令,這需要許多系統調用來打開目錄,然後掃描目錄,等等。這會造成過於頻繁的或大量的輪詢任務(更糟糕的是,繁忙的輪詢並不總是很好。想象一下一個文件系統瀏覽器,比如 Mac OS X 的 Finder,輪詢更新時需要的大量資源及其複雜性)。
那麼,管理員應該怎麼辦呢?令人高興的是,您可以再次求助於可以信賴的計算機。
了解 inotify
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 C API
Inotify 提供 3 個系統調用,它們可以構建各種各樣的文件系統監控器:
此外,還需要 read() 和 close() 系統調用。如果描述符由 inotify_init() 生成,則調用 read() 等待警告。假設有一個典型的文件描述符,應用程序將阻塞對事件的接收,這些事件在流中表現為數據。文件描述符上的由 inotify_init() 生成的通用 close() 刪除所有活動監視器,並釋放與 inotify 實例相關聯的所有內存(這裡也用到典型的引用計數警告。與實例相關聯的所有文件描述符必須在監視器和 inotify 消耗的內存被釋放之前關閉)。
這個強大的工具提供 3 個應用程序編程介面(API)調用,以及簡單、熟悉的範例 “所有內容都是文件”。現在,我們看看示例應用程序。
示例應用程序:事件監控
清單 1 是一個監控兩個事件的目錄的簡短 C 程序:文件的創建和刪除。
#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 所示。
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 所示。
% cc -o watcher watcher.c % ./watcher |
在監視程序運行時,打開第二個終端窗口並使用 touch、cat 和 rm 來更改主目錄的內容,如清單 4 所示。完成之後,重新啟動您的新應用程序。
% 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。
使用 inotify 的技巧
您還可以使用 select()、pselect()、poll() 和 epoll() 來避免阻塞。如果您想將監視器的監控作為圖形應用程序的主事件處理循環的一部分,或作為監視其他輸入連接的守護進程的一部分,這是很有用的。將該 inotify 描述符添加到這組描述符中,進行併發監控。清單 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 的其他技巧:
安裝 inotify 工具套件
inotify 編程界面很容易使用,但如果您不想編寫自己的工具,可以使用一種開源的靈活的代替方法。Inotify 工具庫(參見下面的 參考資料 獲得鏈接)提供一對監控文件系統活動的命令行實用程序:
在撰寫本文時,最新版本的 inotify 庫是 version 3.13,於 2008 年 1 月發布。安裝 inotify 工具有兩種方法:可以下載並親自構建該軟體,或使用 Linux 發布版的包管理器安裝一組二進位文件(如果已知庫包含 inotify 工具)。要在基於 Debian 的發布版上使用后一種方法,請運行 apt-cache search inotify,並查找匹配的工具,如清單 6 所示。在本文的示例系統 Ubuntu Desktop version 8.04 上,這些工具已經可用。
% 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 分鐘。
% 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)。
繼續使用 inotify
就像上面揭示的 apt-cache ,您還可以考慮使用許多其他基於 inotify 的實用程序。incron 實用程序源自於 cron,但它響應 inotify 事件,而不是調度。inoticoming 實用程序專門用於監控收存箱。如果您是 Perl、Ruby 或 Python 開發人員,您可以找到從您喜歡的腳本語言調用 inotify 的模塊和庫。
例如,Perl 編程人員可以使用 Linux::Inotify2(參見 參考資料 獲得詳細信息)來將 inotify 特性嵌入到任何 Perl 應用程序中。這些取自 Linux::Inotify2 README 文件的代碼演示了監控事件的回調介面,如清單 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 有大量的用法。
因此,問題可以歸結為 “誰監視監視器?”(責任編輯:A6)
[火星人 ] 使用 inotify 監控文件系統的活動已經有1257次圍觀