如何監(jiān)控Linux文件系統(tǒng)事件:inotify使用指南
從文件管理器到安全工具,文件系統(tǒng)監(jiān)控對(duì)于的許多程序來(lái)說(shuō)都是必不可少的。從 Linux 2.6.13 內(nèi)核開始,Linux 就推出了 inotify,允許監(jiān)控程序打開一個(gè)獨(dú)立文件描述符,并針對(duì)事件集監(jiān)控一個(gè)或者多個(gè)文件,例如打開、關(guān)閉、移動(dòng)/重命名、刪除、創(chuàng)建或者改變屬性。在后期的內(nèi)核中有了很多增強(qiáng),因此在依賴這些特性之前,請(qǐng)先檢查您的內(nèi)核版本。
在本文中,您將會(huì)學(xué)習(xí)如何在簡(jiǎn)單的監(jiān)控應(yīng)用程序中使用 inotify 功能。下載樣例代碼,在您的系統(tǒng)上編譯,進(jìn)一步探索。
歷史簡(jiǎn)介
在 inotify 之前有 dnotify。不幸的是,dnotify 有局限性,用戶需要更好的產(chǎn)品。和 dnotify 相比 inotify 的優(yōu)勢(shì)如下:
- Inotify 使用一個(gè)獨(dú)立的文件描述符,而 dnotify 需要為每個(gè)受監(jiān)控的目錄打開一個(gè)文件描述符。當(dāng)您同時(shí)監(jiān)控多個(gè)目錄時(shí)成本會(huì)非常高,而且還會(huì)遇到每進(jìn)程文件描述符限制。
- Inotify 所使用的文件描述符可以通過(guò)系統(tǒng)調(diào)用獲得,并且沒(méi)有相關(guān)設(shè)備或者文件。而使用 dnotify,文件描述符就固定了目錄,妨礙備用設(shè)備卸載,這是可移動(dòng)媒體的一個(gè)典型問(wèn)題。對(duì)于 inotify,卸載的文件系統(tǒng)上的監(jiān)視文件或目錄會(huì)產(chǎn)生一個(gè)事件,而且監(jiān)視也會(huì)自動(dòng)移除。
- Inotify 能夠監(jiān)視文件或者目錄。Dnotify 則只監(jiān)視目錄,因此程序員還必須保持 stat 結(jié)構(gòu)或者一個(gè)等效的數(shù)據(jù)結(jié)構(gòu),來(lái)反映該被監(jiān)視目錄中的文件,然后在一個(gè)事件發(fā)生時(shí),將其與當(dāng)前狀態(tài)進(jìn)行對(duì)比,以此了解當(dāng)前目錄中的條目發(fā)生了什么情況。
- 如上所述,inotify 使用文件描述符,允許程序員使用標(biāo)準(zhǔn) select 或者 poll 函數(shù)來(lái)監(jiān)視事件。這允許高效的多路復(fù)用 I/O 或者與 Glib 的 mainloop 的集成。相比之下,dnotify 使用信號(hào),這使得程序員覺得比較困難或者不夠流暢。在 2.6.25 內(nèi)核中 inotify 還添加了 Signal-drive I.O 通知功能。
用于 inotify 的 API
Inotify 提供一個(gè)簡(jiǎn)單的 API,使用最小的文件描述符,并且允許細(xì)粒度監(jiān)控。與 inotify 的通信是通過(guò)系統(tǒng)調(diào)用實(shí)現(xiàn)??捎玫暮瘮?shù)如下所示:
- inotify_init
- 是用于創(chuàng)建一個(gè) inotify 實(shí)例的系統(tǒng)調(diào)用,并返回一個(gè)指向該實(shí)例的文件描述符。
- inotify_init1
- 與 inotify_init 相似,并帶有附加標(biāo)志。如果這些附加標(biāo)志沒(méi)有指定,將采用與 inotify_init 相同的值。
- inotify_add_watch
- 增加對(duì)文件或者目錄的監(jiān)控,并指定需要監(jiān)控哪些事件。標(biāo)志用于控制是否將事件添加到已有的監(jiān)控中,是否只有路徑代表一個(gè)目錄才進(jìn)行監(jiān)控,是否要追蹤符號(hào)鏈接,是否進(jìn)行一次性監(jiān)控,當(dāng)***事件出現(xiàn)后就停止監(jiān)控。
- inotify_rm_watch
- 從監(jiān)控列表中移出監(jiān)控項(xiàng)目。
- read
- 讀取包含一個(gè)或者多個(gè)事件信息的緩存。
- close
- 關(guān)閉文件描述符,并且移除所有在該描述符上的所有監(jiān)控。當(dāng)關(guān)于某實(shí)例的所有文件描述符都關(guān)閉時(shí),資源和下層對(duì)象都將釋放,以供內(nèi)核再次使用。
因此,典型的監(jiān)控程序需要進(jìn)行如下操作:
- 使用 inotify_init 打開一個(gè)文件描述符
- 添加一個(gè)或者多個(gè)監(jiān)控
- 等待事件
- 處理事件,然后返回并等待更多事件
- 當(dāng)監(jiān)控不再活動(dòng)時(shí),或者接到某個(gè)信號(hào)之后,關(guān)閉文件描述符,清空,然后退出。
在下一部分中,您將看到可以監(jiān)控的事件,它們?nèi)绾卧诤?jiǎn)單的程序中運(yùn)行。***,您將看到事件監(jiān)控如何進(jìn)行。
#p#
通告
當(dāng)您的應(yīng)用程序讀取到一個(gè)通告時(shí),事件的順序也被讀取到您提供的緩存中。事件在一個(gè)變長(zhǎng)結(jié)構(gòu)中被返回,如清單 1 所示。如果數(shù)據(jù)占滿了您的緩存,您可能需要對(duì)***一個(gè)條目進(jìn)行局部事件信息或者局部名處理。
清單 1. 用于 inotify 的事件結(jié)構(gòu)體
struct inotify_event { int wd; /* Watch descriptor. */ uint32_t mask; /* Watch mask. */ uint32_t cookie; /* Cookie to synchronize two events. */ uint32_t len; /* Length (including NULs) of name. */ char name __flexarr; /* Name. */ };
請(qǐng)注意,只有當(dāng)監(jiān)控對(duì)象是一個(gè)目錄并且事件與目錄內(nèi)部相關(guān)項(xiàng)目有關(guān),而與目錄本身無(wú)關(guān)時(shí),才提供 name 字段。如果 IN_MOVED_FROM 事件與相應(yīng)的 IN_MOVED_TO 事件都與被監(jiān)控的項(xiàng)目有關(guān),cookie 就可用于將兩者關(guān)聯(lián)起來(lái)。事件類型在掩碼字段中返回,并伴隨著能夠被內(nèi)核設(shè)置的標(biāo)志。例如,如果事件與目錄有關(guān),則標(biāo)志 IN_ISDIR 將由內(nèi)核設(shè)置。
可監(jiān)控的事件
有幾種事件能夠被監(jiān)控。一些事件,比如 IN_DELETE_SELF 只適用于正在被監(jiān)控的項(xiàng)目,而另一些,比如 IN_ATTRIB 或者 IN_OPEN 則只適用于監(jiān)控過(guò)的項(xiàng)目,或者如果該項(xiàng)目是目錄,則可以應(yīng)用到其所包含的目錄或文件。
- IN_ACCESS
- 被監(jiān)控項(xiàng)目或者被監(jiān)控目錄中的條目被訪問(wèn)過(guò)。例如,一個(gè)打開的文件被讀取。
- IN_MODIFY
- 被監(jiān)控項(xiàng)目或者被監(jiān)控目錄中的條目被修改過(guò)。例如,一個(gè)打開的文件被修改。
- IN_ATTRIB
- 被監(jiān)控項(xiàng)目或者被監(jiān)控目錄中條目的元數(shù)據(jù)被修改過(guò)。例如,時(shí)間戳或者許可被修改。
- IN_CLOSE_WRITE
- 一個(gè)打開的,等待寫入的文件或目錄被關(guān)閉。
- IN_CLOSE_NOWRITE
- 一個(gè)以只讀方式打開的文件或目錄被關(guān)閉。
- IN_CLOSE
- 一個(gè)掩碼,可以很便捷地對(duì)前面提到的兩個(gè)關(guān)閉事件(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)進(jìn)行邏輯操作。
- IN_OPEN
- 文件或目錄被打開。
- IN_MOVED_FROM
- 被監(jiān)控項(xiàng)目或者被監(jiān)控目錄中的條目被移出監(jiān)控區(qū)域。該事件還包含一個(gè) cookie 來(lái)實(shí)現(xiàn) IN_MOVED_FROM 與 IN_MOVED_TO 的關(guān)聯(lián)。
- IN_MOVED_TO
- 文件或目錄被移入監(jiān)控區(qū)域。該事件包含一個(gè)針對(duì) IN_MOVED_FROM 的 cookie。如果文件或目錄只是被重命名,將能看到這兩個(gè)事件,如果它只是被移入或移出非監(jiān)控區(qū)域,將只能看到一個(gè)事件。如果移動(dòng)或重命名一個(gè)被監(jiān)控項(xiàng)目,監(jiān)控將繼續(xù)進(jìn)行。參見下面的 IN_MOVE-SELF。
- IN_MOVE
- 可以很便捷地對(duì)前面提到的兩個(gè)移動(dòng)事件(IN_MOVED_FROM | IN_MOVED_TO)進(jìn)行邏輯操作的掩碼。
- IN_CREATE
- 在被監(jiān)控目錄中創(chuàng)建了子目錄或文件。
- IN_DELETE
- 被監(jiān)控目錄中有子目錄或文件被刪除。
- IN_DELETE_SELF
- 被監(jiān)控項(xiàng)目本身被刪除。監(jiān)控終止,并且將收到一個(gè) IN_IGNORED 事件。
- IN_MOVE_SELF
- 監(jiān)控項(xiàng)目本身被移動(dòng)。
除了事件標(biāo)志以外,還可以在 inotify 頭文件(/usr/include/sys/inotify.h)中找到其他幾個(gè)標(biāo)志。例如,如果只想監(jiān)控***個(gè)事件,可以在增加監(jiān)控時(shí)設(shè)置 IN_ONESHOT 標(biāo)志。
#p#
一個(gè)簡(jiǎn)單的 inotify 應(yīng)用程序
這里的簡(jiǎn)單應(yīng)用程序(參見 下載 部分)遵循以上的通用邏輯。我們使用一個(gè)信號(hào)處理程序來(lái)監(jiān)控 ctrl-c(SIGINT)并且重置一個(gè)標(biāo)志(keep_running)使應(yīng)用程序了解終止操作。真實(shí)的 inotify 調(diào)用在 utility 例程當(dāng)中完成。注意,我們還創(chuàng)建了一個(gè)隊(duì)列,這樣能夠?qū)⑹录?inotify 底層對(duì)象中清除,留著稍后處理。在真實(shí)的應(yīng)用程序中,您可能想用一個(gè)不同于您處理事件所用的線程(具有更高優(yōu)先級(jí))來(lái)完成這一操作。對(duì)于該應(yīng)用程序,只是為了對(duì)一般原理進(jìn)行舉例說(shuō)明。我們采用了一個(gè)簡(jiǎn)單的事件鏈表,在其中我們隊(duì)列中的每個(gè)條目都包含原始事件以及指向隊(duì)列中下一事件指針的空間。
主程序
清單 2 中展示了信號(hào)處理例程和主例程。在這個(gè)簡(jiǎn)單示例中,在命令行對(duì)每個(gè)傳遞進(jìn)來(lái)的文件會(huì)目錄建立監(jiān)控,并利用事件掩碼 IN_ALL_EVENTS 來(lái)監(jiān)控每個(gè)對(duì)象的所有事件。在真實(shí)的應(yīng)用程序中,您可能只希望跟蹤文件與目錄的創(chuàng)建或刪除事件,因此您可以屏蔽打開、關(guān)閉以及屬性改變事件。如果您對(duì)文件或目錄的重命名和移動(dòng)不感興趣,您也可以屏蔽各種移動(dòng)事件。關(guān)于更多細(xì)節(jié),參見 inotify 幫助信息。
清單 2. inotify-test.c 的簡(jiǎn)單主程序
/* Signal handler that simply resets a flag to cause termination */ void signal_handler (int signum) { keep_running = 0; } int main (int argc, char **argv) { /* This is the file descriptor for the inotify watch */ int inotify_fd; keep_running = 1; /* Set a ctrl-c signal handler */ if (signal (SIGINT, signal_handler) == SIG_IGN) { /* Reset to SIG_IGN (ignore) if that was the prior state */ signal (SIGINT, SIG_IGN); } /* First we open the inotify dev entry */ inotify_fd = open_inotify_fd (); if (inotify_fd > 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. This queue is probably too small if you are monitoring something like a directory with a lot of files and the directory is deleted. */ queue_t q; q = queue_create (128); /* This is the watch descriptor returned for each item we are watching. A real application might keep these for some use in the application. This sample only makes sure that none of the watch descriptors is less than 0. */ int wd; /* Watch all events (IN_ALL_EVENTS) for the directories and files passed in as arguments. Read the article for why you might want to alter this for more efficient inotify use in your app. */ int index; wd = 0; printf("\n"); for (index = 1; (index < argc) && (wd >= 0); index++) { wd = watch_dir (inotify_fd, argv[index], IN_ALL_EVENTS); } if (wd > 0) { /* Wait for events and process them until a termination condition is detected */ process_inotify_events (q, inotify_fd); } printf ("\nTerminating\n"); /* Finish up by closing the fd, destroying the queue, and returning a proper code */ close_inotify_fd (inotify_fd); queue_destroy (q); } return 0; }
使用 inotify_init 打開文件描述符
清單 3 展示了用于創(chuàng)建 inotify 實(shí)例以及獲取其文件描述符的簡(jiǎn)單實(shí)用工具函數(shù)。文件描述符返回給了調(diào)用者。如果出現(xiàn)錯(cuò)誤,返回值將為負(fù)。
清單 3. 使用 inotify_init
/* Create an inotify instance and open a file descriptor to access it */ int open_inotify_fd () { int fd; watched_items = 0; fd = inotify_init (); if (fd < 0) { perror ("inotify_init () = "); } return fd; }
使用 inotify_add_watch 添加一個(gè)監(jiān)控
一旦我們有了用于 inotify 實(shí)例的文件描述符之后,就需要增加一個(gè)或多個(gè)監(jiān)控。您可以使用掩碼來(lái)設(shè)置想要監(jiān)控的事件。在本例中,采用掩碼 IN_ALL_EVENTS,來(lái)監(jiān)控所有可用事件。
清單 4. 使用 inotify_add_watch
int watch_dir (int fd, const char *dirname, unsigned long mask) { int wd; wd = inotify_add_watch (fd, dirname, mask); if (wd < 0) { printf ("Cannot add watch for \"%s\" with event mask %lX", dirname, mask); fflush (stdout); perror (" "); } else { watched_items++; printf ("Watching %s WD=%d\n", dirname, wd); printf ("Watching = %d items\n", watched_items); } return wd; }
事件處理循環(huán)
現(xiàn)在我們已經(jīng)設(shè)置了一些監(jiān)控,接下來(lái)就要等待事件。如果還存在監(jiān)控,并且 keep_running 標(biāo)志沒(méi)有被信號(hào)處理程序重置,則循環(huán)會(huì)一直進(jìn)行。循環(huán)進(jìn)程等待事件的發(fā)生,對(duì)有效事件進(jìn)行排隊(duì),并在返回等待狀態(tài)之前處理隊(duì)列。在真實(shí)應(yīng)用程序當(dāng)中,您可能會(huì)將事件放入線程隊(duì)列中,而在另一個(gè)線程中處理它們,清單 5 展示了該循環(huán)。
清單 5. 事件處理循環(huán)
int process_inotify_events (queue_t q, int fd) { while (keep_running && (watched_items > 0)) { if (event_check (fd) > 0) { int r; r = read_events (q, fd); if (r < 0) { break; } else { handle_events (q); } } } return 0; }
等待事件
在我們的示樣例應(yīng)用程序中,循環(huán)會(huì)不停地進(jìn)行下去,直至監(jiān)控事件出現(xiàn)或者收到了中斷信號(hào)。清單 6 展示了相關(guān)代碼。
清單 6. 等待事件或者中斷
int event_check (int fd) { fd_set rfds; FD_ZERO (&rfds); FD_SET (fd, &rfds); /* Wait until an event happens or we get interrupted by a signal that we catch */ return select (FD_SETSIZE, &rfds, NULL, NULL, NULL); }
讀取事件
當(dāng)事件發(fā)生時(shí),程序會(huì)依照緩存區(qū)的大小來(lái)讀取盡量多的事件,然后把這些事件放入隊(duì)列等待事件處理程序來(lái)處理。樣例代碼不能處理這種情況 — 可用事件超出 16.384 byte 緩存中可存儲(chǔ)的事件。要處理這類情況,需要在緩存末端處理部分事件。目前,對(duì)名字長(zhǎng)度進(jìn)行限制不成問(wèn)題,但是優(yōu)秀的防御式編程會(huì)檢查名字,來(lái)確保不會(huì)溢出緩存。
清單 7. 讀取事件并排隊(duì)
int read_events (queue_t q, int fd) { char buffer[16384]; size_t buffer_i; struct inotify_event *pevent; queue_entry_t event; ssize_t r; size_t event_size, q_event_size; int count = 0; r = read (fd, buffer, 16384); if (r <= 0) return r; buffer_i = 0; while (buffer_i < r) { /* Parse events and queue them. */ pevent = (struct inotify_event *) &buffer[buffer_i]; event_size = offsetof (struct inotify_event, name) + pevent->len; q_event_size = offsetof (struct queue_entry, inot_ev.name) + pevent->len; event = malloc (q_event_size); memmove (&(event->inot_ev), pevent, event_size); queue_enqueue (event, q); buffer_i += event_size; count++; } printf ("\n%d events queued\n", count); return count; }
處理事件
***,我們需要對(duì)事件做處理了。對(duì)于該應(yīng)用程序,我們只簡(jiǎn)單地報(bào)告所發(fā)生的事件。如果一個(gè)名字出現(xiàn)在事件結(jié)構(gòu)中,我們就報(bào)告它是一個(gè)文件或目錄。發(fā)生移動(dòng)時(shí),還會(huì)報(bào)告與移動(dòng)或重命名事件相關(guān)的 cookie 信息。清單 8 展示了部分代碼,包括對(duì)一些事件的處理。參見 下載 部分可獲取完整代碼。
清單 8. 處理事件
void handle_event (queue_entry_t event) { /* If the event was associated with a filename, we will store it here */ char *cur_event_filename = NULL; char *cur_event_file_or_dir = NULL; /* This is the watch descriptor the event occurred on */ int cur_event_wd = event->inot_ev.wd; int cur_event_cookie = event->inot_ev.cookie; unsigned long flags; if (event->inot_ev.len) { cur_event_filename = event->inot_ev.name; } if ( event->inot_ev.mask & IN_ISDIR ) { cur_event_file_or_dir = "Dir"; } else { cur_event_file_or_dir = "File"; } flags = event->inot_ev.mask & ~(IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED ); /* Perform event dependent handler routines */ /* The mask is the magic that tells us what file operation occurred */ switch (event->inot_ev.mask & (IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED)) { /* File was accessed */ case IN_ACCESS: printf ("ACCESS: %s \"%s\" on WD #%i\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd); break; /* File was modified */ case IN_MODIFY: printf ("MODIFY: %s \"%s\" on WD #%i\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd); break; /* File changed attributes */ case IN_ATTRIB: printf ("ATTRIB: %s \"%s\" on WD #%i\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd); break; /* File open for writing was closed */ case IN_CLOSE_WRITE: printf ("CLOSE_WRITE: %s \"%s\" on WD #%i\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd); break; /* File open read-only was closed */ case IN_CLOSE_NOWRITE: printf ("CLOSE_NOWRITE: %s \"%s\" on WD #%i\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd); break; /* File was opened */ case IN_OPEN: printf ("OPEN: %s \"%s\" on WD #%i\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd); break; /* File was moved from X */ case IN_MOVED_FROM: printf ("MOVED_FROM: %s \"%s\" on WD #%i. Cookie=%d\n", cur_event_file_or_dir, cur_event_filename, cur_event_wd, cur_event_cookie); break; . . (other cases) . /* Watch was removed explicitly by inotify_rm_watch or automatically because file was deleted, or file system was unmounted. */ case IN_IGNORED: watched_items--; printf ("IGNORED: WD #%d\n", cur_event_wd); printf("Watching = %d items\n",watched_items); break; /* Some unknown message received */ default: printf ("UNKNOWN EVENT \"%X\" OCCURRED for file \"%s\" on WD #%i\n", event->inot_ev.mask, cur_event_filename, cur_event_wd); break; } /* If any flags were set other than IN_ISDIR, report the flags */ if (flags & (~IN_ISDIR)) { flags = event->inot_ev.mask; printf ("Flags=%lX\n", flags); } }
這個(gè)簡(jiǎn)單示例用于說(shuō)明 inotify 如何工作,以及您可以監(jiān)控什么事件。您的實(shí)際需求將決定對(duì)哪些事件進(jìn)行監(jiān)控以及如何處理這些事件。
#p#
用法舉例
在本部分中,我們創(chuàng)建一個(gè)帶有文件的簡(jiǎn)單雙級(jí)目錄結(jié)構(gòu),然后運(yùn)行簡(jiǎn)單程序來(lái)舉例說(shuō)明 inotify 所能監(jiān)控的一些事件。我們將在終端會(huì)話中啟動(dòng) inotify 示例程序,但是在后臺(tái)運(yùn)行(使用 &)因此程序的輸出與我們輸入的命令會(huì)交替出現(xiàn)。您可以在一個(gè)終端窗口運(yùn)行該程序,而在其他一個(gè)或多個(gè)窗口運(yùn)行指令。清單 9 展示了簡(jiǎn)單文件結(jié)構(gòu)和空文件的創(chuàng)建,以及最初啟動(dòng)該示例程序時(shí)的輸出。
清單 9. 創(chuàng)建一個(gè)樣例環(huán)境
ian@attic4:~/inotify-sample$ mkdir -p dir1/dir2 ian@attic4:~/inotify-sample$ touch dir1/dir2/file1 ian@attic4:~/inotify-sample$ ./inotify_test dir1/ dir1/dir2/ dir1/dir2/file1& [2] 8733 ian@attic4:~/inotify-sample$ Watching dir1/ WD=1 Watching = 1 items Watching dir1/dir2/ WD=2 Watching = 2 items Watching dir1/dir2/file1 WD=3 Watching = 3 items ian@attic4:~/inotify-sample$
在清單 10 中,展示了來(lái)自 dir2 內(nèi)容清單的輸出。***個(gè)事件報(bào)告是關(guān)于 dir1 的,展示了一些內(nèi)容,即 dir2 ,在被監(jiān)控描述符 1 監(jiān)控的目錄當(dāng)中被打開了。第二個(gè)條目是關(guān)于監(jiān)控描述符 2 的,顯示出被監(jiān)控項(xiàng)目(在本例中為 dir2 )被打開了。如果正在監(jiān)控目錄樹中的多個(gè)項(xiàng)目,可能會(huì)經(jīng)常遇到這種雙重輸出。
清單 10. 列舉 dir2 的內(nèi)容
ian@attic4:~/inotify-sample$ ls dir1/dir2 file1 4 events queued OPEN: Dir "dir2" on WD #1 OPEN: Dir "(null)" on WD #2 CLOSE_NOWRITE: Dir "dir2" on WD #1 CLOSE_NOWRITE: Dir "(null)" on WD #2
在清單 11 中,我們?cè)?file1 添加了一些文本。請(qǐng)?jiān)俅巫⒁鈱?duì)文件以及該文件所在目錄的雙重打開、關(guān)閉和修改事件。還請(qǐng)注意所有的事件并非立刻讀取。排隊(duì)例程被調(diào)用了3次,每次有兩個(gè)事件。如果再次運(yùn)行該程序,并且每次操作相同,您未必會(huì)再次遇到這一特別情況。
清單 11. 在 file1 中添加文本
ian@attic4:~/inotify-sample$ echo "Some text" >> dir1/dir2/file1 2 events queued OPEN: File "file1" on WD #2 OPEN: File "(null)" on WD #3 2 events queued MODIFY: File "file1" on WD #2 MODIFY: File "(null)" on WD #3 2 events queued CLOSE_WRITE: File "file1" on WD #2 CLOSE_WRITE: File "(null)" on WD #3
在清單 12 中,我們改變了 file1 的屬性。我們?cè)俅蔚玫接嘘P(guān)被監(jiān)控項(xiàng)目以及其所在目錄的雙重輸出。
清單 12. 改變文件屬性
ian@attic4:~/inotify-sample$ chmod a+w dir1/dir2/file1 2 events queued ATTRIB: File "file1" on WD #2 ATTRIB: File "(null)" on WD #3
現(xiàn)在將文件 file1 移動(dòng)到上一級(jí)目錄 dir1 當(dāng)中。在清單 13 中顯示了輸出結(jié)果。這次沒(méi)有雙重條目。我們實(shí)際上得到了 3 個(gè)條目,每個(gè)目錄一個(gè),文件本身一個(gè)。請(qǐng)注意 cookie (569) 允許將 MOVED-FROM 事件與 MOVED_TO 事件關(guān)聯(lián)起來(lái)。
清單 13. 將 file1 移動(dòng)到 dir1
ian@attic4:~/inotify-sample$ mv dir1/dir2/file1 dir1 3 events queued MOVED_FROM: File "file1" on WD #2. Cookie=569 MOVED_TO: File "file1" on WD #1. Cookie=569 MOVE_SELF: File "(null)" on WD #3
現(xiàn)在創(chuàng)建一個(gè) file1 到 file2 的硬鏈接。當(dāng)?shù)?inode 的鏈接數(shù)量該變時(shí),我們會(huì)有一個(gè)關(guān)于 file1 的 ATTRIB 事件,還會(huì)有一個(gè)關(guān)于 file2 的 CREATE 事件。
清單 14. 創(chuàng)建硬鏈接
ian@attic4:~/inotify-sample$ ln dir1/file1 dir1/file2 2 events queued ATTRIB: File "(null)" on WD #3 CREATE: File "file2" on WD #1
現(xiàn)在將文件 file1 移入當(dāng)前目錄,將其重命名為 file3 。當(dāng)前目錄沒(méi)有被監(jiān)控,因此不存在與 MOVED_FROM 事件相關(guān)聯(lián)的 MOVED_TO 事件。
清單 15. 將 file1 移入不受監(jiān)控的目錄當(dāng)中
ian@attic4:~/inotify-sample$ mv dir1/file1 ./file3 2 events queued MOVED_FROM: File "file1" on WD #1. Cookie=572 MOVE_SELF: File "(null)" on WD #3
此時(shí),dir2 是空的,因此可以移動(dòng)它。注意我們得到一個(gè)關(guān)于監(jiān)控描述符 2 的 IGNORED 事件,可見現(xiàn)在我們只監(jiān)控兩個(gè)項(xiàng)目。
清單 16. 移除 dir2
ian@attic4:~/inotify-sample$ rmdir dir1/dir2 3 events queued DELETE: Dir "dir2" on WD #1 DELETE_SELF: File "(null)" on WD #2 IGNORED: WD #2 Watching = 2 items
移除文件 file3。注意這次我們沒(méi)有得到 IGNORED 事件。為什么呢?為什么得到了關(guān)于 file 3 的 ATTRIB 事件(就是原來(lái)的 dir1/dir2/file1)?
清單 16. 刪除 file3
ian@attic4:~/inotify-sample$ rm file3 1 events queued ATTRIB: File "(null)" on WD #3
記住我們創(chuàng)建了 file1 到 file2 的硬鏈接。清單 17 顯示我們還在通過(guò)監(jiān)控描述符 3 來(lái)監(jiān)控 file2,盡管最開始不存在文件 2!
ian@attic4:~/inotify-sample$ touch dir1/file2 6 events queued OPEN: File "file2" on WD #1 OPEN: File "(null)" on WD #3 ATTRIB: File "file2" on WD #1 ATTRIB: File "(null)" on WD #3 CLOSE_WRITE: File "file2" on WD #1 CLOSE_WRITE: File "(null)" on WD #3
因此,現(xiàn)在讓我們來(lái)刪除 dir1,并監(jiān)控事件級(jí)聯(lián),因?yàn)樗辉俦O(jiān)控任何事情,不得不結(jié)束了自己。
清單 18. 刪除 dir1
ian@attic4:~/inotify-sample$ rm -rf dir1 8 events queued OPEN: Dir "(null)" on WD #1 ATTRIB: File "(null)" on WD #3 DELETE_SELF: File "(null)" on WD #3 IGNORED: WD #3 Watching = 1 items DELETE: File "file2" on WD #1 CLOSE_NOWRITE: Dir "(null)" on WD #1 DELETE_SELF: File "(null)" on WD #1 IGNORED: WD #1 Watching = 0 items Terminating
#p#
inotify 的可能使用
您可以將 inotify 用于多種目標(biāo)。下面列舉一些可能的情況:
- 性能監(jiān)控
- 您可能想確定應(yīng)用程序打開最頻繁的文件是哪個(gè)。如果發(fā)現(xiàn)一個(gè)小文件被頻繁打開與關(guān)閉,您可能會(huì)考慮采用內(nèi)存版,或者改變應(yīng)用程序來(lái)采取其他方式共享該數(shù)據(jù)。
- 元信息
- 您可能想記錄文件的附加信息,例如起始創(chuàng)建時(shí)間或者***改變?cè)撐募挠脩?id。
- 安全
- 您可能會(huì)因?yàn)榘踩颍枰獙?duì)特定文件或目錄的所有訪問(wèn)進(jìn)行監(jiān)控。
我們的示例代碼監(jiān)控所有事件并進(jìn)行報(bào)告。實(shí)際上,您可能想依據(jù)您的需要,來(lái)查看這些事件的特定子集。您可能想監(jiān)控不同被監(jiān)控項(xiàng)目的不同事件。例如,您可能想監(jiān)控文件的打開與關(guān)閉事件,但對(duì)于目錄只想監(jiān)控創(chuàng)建與刪除事件。在任何可能的時(shí)候,您可以監(jiān)控您所感興趣的最小事件集。
結(jié)束語(yǔ)
應(yīng)用到性能監(jiān)控、程序調(diào)試、以及自動(dòng)化等領(lǐng)域時(shí),inotify 是監(jiān)控 Linux 文件系統(tǒng)的功能強(qiáng)大的、高粒度的機(jī)制。利用本文提供的樣例代碼,您可以開始編寫用來(lái)實(shí)時(shí)記錄文件系統(tǒng)事件并最小化性能開銷的應(yīng)用程序。