標籤:des 使用 os io 檔案 資料 ar 代碼
瞭解 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 個系統調用,它們可以構建各種各樣的檔案系統監控器:
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(API)調用,以及簡單、熟悉的範例 “所有內容都是檔案”。現在,我們看看應用程式範例。
應用程式範例:事件監控
清單 1 是一個監控兩個事件的目錄的簡短 C 程式:檔案的建立和刪除。
清單 1. 簡單的 inotify 應用程式,它監控建立、刪除和修改事件的目錄
#include
#include
#include
#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。
使用 inotify 的技巧
您還可以使用 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 執行個體和所有相關聯的監視器,並清空隊列中的所有等待事件。