inotify監(jiān)控Linux文件系統(tǒng)的必備利器
本文主要向大家介紹了如何使用inotify控制Linux文件系統(tǒng)中的事件。在具體的學(xué)習(xí)之前我們先來(lái)看看什么是inotify以及他的歷史簡(jiǎn)介。我們還會(huì)向大家介紹inotify的具體應(yīng)用情況以及使中遇到問(wèn)題的解決方案。
inotify 介紹
文件系統(tǒng)事件監(jiān)控對(duì)于從文件管理器到安全工具等多種程序來(lái)說(shuō)都是必要的。自 2.6.13 版本內(nèi)核開(kāi)始,Linux 就提供 inotify 功能,這允許監(jiān)控程序打開(kāi)一個(gè)獨(dú)立文件描述符,并針對(duì)一系列特定的事件來(lái)監(jiān)控一個(gè)或多個(gè)文件或者目錄,例如打開(kāi)、關(guān)閉、移動(dòng)/重命名、 刪除、創(chuàng)建或者改變屬性。在以后的版本中還提供更多增強(qiáng)功能,因此在依賴這些特性之前請(qǐng)先檢查系統(tǒng)的內(nèi)核版本。
在本文中,您將學(xué)會(huì)如何在簡(jiǎn)單的監(jiān)控程序當(dāng)中使用 inotify 函數(shù)。下載樣例代碼 并在系統(tǒng)當(dāng)中進(jìn)行編譯,來(lái)為后面的研究做準(zhǔn)備。
歷史簡(jiǎn)介
在 inotify 之前有 dnotify 。不幸的是,dnotify 有局限性,無(wú)法滿足用戶的需求, inotify 的優(yōu)勢(shì)如下:
Inotify 采用簡(jiǎn)單的文件描述符,而 dnotify 需要為每個(gè)受監(jiān)控的目錄打開(kāi)一個(gè)文件描述符。 這使得同時(shí)監(jiān)控多個(gè)目錄成本很高,而且還會(huì)遇到每進(jìn)程文件描述符限制問(wèn)題。
inotify 所采用的文件描述符通過(guò)系統(tǒng)調(diào)用獲得,并且沒(méi)有相關(guān)的設(shè)備或文件。對(duì)于 dnotify ,文件描述符與目錄關(guān)系固定,避免了相關(guān)設(shè)備未被加載的問(wèn)題,這是可移動(dòng)媒體的典型問(wèn)題。對(duì)于 inotify ,如果受監(jiān)控文件或目錄所在的文件系統(tǒng)未被加載, 則會(huì)產(chǎn)生一個(gè)事件,然后該監(jiān)控會(huì)被自動(dòng)移除。
Inotify 能夠監(jiān)控文件或目錄。Dnotify 監(jiān)控目錄, 因此程序員必須保持 stat 結(jié)構(gòu)或者等效的數(shù)據(jù)結(jié)構(gòu),來(lái)反映目錄當(dāng)中被監(jiān)控的文件, 然后在一個(gè)事件發(fā)生時(shí),將其與當(dāng)前狀態(tài)進(jìn)行對(duì)比,來(lái)知曉目錄當(dāng)中的條目發(fā)生了什么情況。
如前面所述,inotify 采用文件描述符,允許程序員采用標(biāo)準(zhǔn) select 或者 poll 函數(shù)來(lái)對(duì)事件進(jìn)行監(jiān)控。 這允許高效的多重 I/O 或者與 Glib 的 mainloop 集成。相比之下,dnotify 采用信號(hào), 這使得程序員感到難度更大或者不夠流暢。 在 2.6.25 版本內(nèi)核中,inotify 也增加了 Signal-drive I.O 通告功能。
用于 inotify 的 API
Inotify 提供簡(jiǎn)單的 API ,采用最小的文件描述符并允許細(xì)粒度監(jiān)控。與 inotify 的通信通過(guò)系統(tǒng)調(diào)用實(shí)現(xiàn)。
inotify_init
是用于創(chuàng)建 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)控當(dāng)中,是否只有路徑代表一個(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)閉以后, 資源和下層的對(duì)象都將釋放,來(lái)供內(nèi)核再次使用。
因此,典型的監(jiān)控程序要進(jìn)行如下操作:
利用 inotify_init 打開(kāi)文件描述符;
增加一個(gè)或多個(gè)監(jiān)控;
等待事件;
處理事件,然后返回并等待更多事件;
當(dāng)沒(méi)有活躍的監(jiān)控時(shí)或者基于某些信號(hào)的指示,關(guān)閉文件描述符,清空,然后退出。
在下一部分中,您將看到可以監(jiān)控的事件,以及它們?nèi)绾卧诤?jiǎn)單程序當(dāng)中運(yùn)行。最后您將看到如何進(jìn)行事件監(jiān)控。
通告
當(dāng)應(yīng)用程序讀取一個(gè)通告時(shí),事件的順序也被讀取到緩存中。事件在一個(gè)變長(zhǎng)結(jié)構(gòu)體中被返回,見(jiàn)清單 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. */
- };
注意,只有當(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 只應(yīng)用到正在被監(jiān)控的項(xiàng)目,然而另一些比如 IN_ATTRIB 或者 IN_OPEN 可以應(yīng)用到監(jiān)控過(guò)的項(xiàng)目, 或者如果該項(xiàng)目是目錄,則可以應(yīng)用到其所包含的目錄或文件。
IN_ACCESS
被監(jiān)控項(xiàng)目或者被監(jiān)控目錄當(dāng)中的條目被訪問(wèn)過(guò)。例如,一個(gè)打開(kāi)的文件被讀取。
IN_MODIFY
被監(jiān)控項(xiàng)目或者被監(jiān)控目錄當(dāng)中的條目被修改過(guò)。例如,一個(gè)打開(kāi)的文件被修改。
IN_ATTRIB
被監(jiān)控項(xiàng)目或者被監(jiān)控目錄當(dāng)中條目的元數(shù)據(jù)被修改過(guò)。例如,時(shí)間戳或者許可被修改。
IN_CLOSE_WRITE
一個(gè)打開(kāi)的,等待寫入的文件或目錄被關(guān)閉。
IN_CLOSE_NOWRITE
一個(gè)以只讀方式打開(kāi)的文件或目錄被關(guān)閉。
IN_CLOSE
是可以很便捷地對(duì)前面提到的兩個(gè)關(guān)閉事件(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)進(jìn)行邏輯或操作的掩碼。
IN_OPEN
文件或目錄被打開(kāi)。
IN_MOVED_FROM
被監(jiān)控項(xiàng)目或者被監(jiān)控目錄當(dāng)中的條目被移出監(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)行。參見(jiàn)下面的 IN_MOVE-SELF 。
IN_MOVE
是可以很便捷地對(duì)前面提到的兩個(gè)移動(dòng)事件(IN_MOVED_FROM | IN_MOVED_TO)進(jìn)行邏輯或操作的掩碼。
IN_CREATE
在被監(jiān)控目錄當(dāng)中創(chuàng)建了子目錄或文件。
IN_DELETE
被監(jiān)控目錄當(dāng)中有子目錄或文件被刪除。
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#
inotify 簡(jiǎn)單應(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)用當(dāng)中,您可能希望用其他(具有更高優(yōu)先級(jí))的線程來(lái)完成這一操作。 這里的列舉的應(yīng)用程序,只是為了對(duì)一般原理進(jìn)行舉例說(shuō)明。我們采用了一個(gè)簡(jiǎn)單的事件鏈表, 隊(duì)列中的每一條目都包含原始事件加上用于存儲(chǔ)指向隊(duì)列中下一事件指針的空間。
主程序
清單 2 中展示了信號(hào)處理程序和主程序。在本例當(dāng)中,對(duì)所有在命令行中出現(xiàn)過(guò)的文件或目錄進(jìn)行監(jiān)控, 并利用事件掩碼 IN_ALL_EVENTS 來(lái)監(jiān)控每一對(duì)象的所有事件。 在真實(shí)的應(yīng)用程序中,您可能只希望追蹤文件與目錄的創(chuàng)建或刪除事件,因此您可以掩去打開(kāi)、關(guān)閉以及屬性改變事件。 如果您對(duì)文件或目錄的重命名和移動(dòng)不感興趣,您也可以掩去各種移動(dòng)事件。關(guān)于更多細(xì)節(jié),參見(jiàn) 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 打開(kāi)文件描述符
清單 3 展示了用于創(chuàng)建 inotify 實(shí)例的簡(jiǎn)單應(yīng)用函數(shù),并為其獲得一個(gè)文件描述符。文件描述符返回給了調(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 來(lái)增加監(jiān)控
有了用于 inotify 實(shí)例的文件描述符之后,就需要增加一個(gè)或多個(gè)監(jiān)控。 可以使用掩碼來(lái)設(shè)置想要監(jiān)控的事件。在本例當(dāng)中,采用掩碼 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;
- }
#p#事件處理循環(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ì)采用一個(gè)線程將事件放入隊(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;
- }
等待事件
在本程序中,循環(huán)會(huì)不停地進(jìn)行下去,直至發(fā)生被監(jiā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)處理。 樣例代碼對(duì)于事件量大于 16.384-byte 之類的問(wèn)題不作處理。 想處理這類問(wèn)題,需要在緩存末端處理局部事件。當(dāng)前對(duì)名字長(zhǎng)度所做的限制沒(méi)有問(wèn)題,但是優(yōu)秀的防御式編程會(huì)檢查名字,來(lái)確保不會(huì)出現(xiàn)緩存區(qū)溢出。
清單 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ì)事件做處理。本例中的應(yīng)用程序不處理事件,只報(bào)告發(fā)生了哪些事件。如果事件結(jié)構(gòu)體中有一個(gè)名字出現(xiàn), 程序?qū)?bào)告其是目錄還是文件。在發(fā)生移動(dòng)操作時(shí),還會(huì)報(bào)告與移動(dòng)或重命名事件相關(guān)的 cookie 信息。 清單 8 展示了部分代碼,包括對(duì)一些事件的處理。參見(jiàn) 下載 部分可獲得全部代碼。
清單 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);
- }
- }
該例子用來(lái)舉例說(shuō)明 inotify 如何工作以及能夠監(jiān)控哪些事件。 您的實(shí)際需求將決定對(duì)哪些事件進(jìn)行監(jiān)控以及如何處理這些事件。#p#
用法舉例
在這部分當(dāng)中,我們創(chuàng)建一個(gè)簡(jiǎn)單的兩級(jí)目錄結(jié)構(gòu),目錄中有一個(gè)文件, 然后運(yùn)行簡(jiǎn)單程序來(lái)舉例說(shuō)明 inotify 所能監(jiān)控的一些事件。 我們將在一個(gè)終端會(huì)話中啟動(dòng) inotify 樣例程序, 但是因?yàn)樗窃诤笈_(tái)運(yùn)行的(利用 &)因此程序的輸出與我們輸入的命令會(huì)交替出現(xiàn)。 應(yīng)該在一個(gè)終端窗口運(yùn)行該程序,而在其他窗口輸入命令。清單 9 展示了簡(jiǎn)單文件結(jié)構(gòu)和空文件的創(chuàng)建,以及最初啟動(dòng)該簡(jiǎn)單程序時(shí)的輸出。
清單 9. 創(chuàng)建樣例環(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 當(dāng)中,展示了列舉 dir2 內(nèi)容時(shí)的輸出。 第一個(gè)事件報(bào)告與 dir1 有關(guān),顯示出有些東西,名字叫做 dir2 ,在與監(jiān)控描述符 1 相關(guān)的被監(jiān)控目錄當(dāng)中被打開(kāi)了。 第二個(gè)條目與監(jiān)控描述符 2 有關(guān),顯示出被監(jiān)控項(xiàng)目(在本例中為 dir2 )被打開(kāi)了。如果正在監(jiān)控目錄樹(shù)中的多個(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 當(dāng)中,在 file1 中加入一些文字。需要再次強(qiáng)調(diào)對(duì)于文件以及該文件所在目錄的雙重打開(kāi),關(guān)閉,和修改事件。還要注意不是全部事件都會(huì)被立刻讀取。排隊(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 當(dāng)中,改變 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è)。 注意 cookie (569) 允許將 MOVED-FROM 事件與 MOVED_TO 事件關(guān)聯(lián)起來(lái)。
清單 13. 將文件 file1 移入 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 的硬鏈接。由于到 inode 的鏈接數(shù)量變了,我們得到針對(duì) file1 的 ATTRIB 事件,還得到一個(gè)針對(duì) 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 事件,可見(jiàn)我們只在監(jiān)控兩個(gè)項(xiàng)目。
清單 16. 移動(dòng) 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
移動(dòng)文件 file3。注意這次我們沒(méi)有得到 IGNORED 事件。為什么呢?為什么得到了關(guān)于 file 3 的 ATTRIB 事件(就是原來(lái)的 dir1/dir2/file1)?
清單 17. 刪除 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,盡管最開(kāi)始不存在文件 2!
清單 18. 仍對(duì) file2 進(jìn)行監(jiān)控!
- 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)在刪除 dir1 來(lái)監(jiān)控事件級(jí)聯(lián),因?yàn)槌绦蛩槐O(jiān)控任何事,它結(jié)束了自己。
清單 19. 刪除 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
inotify 的使用情況
可將 inotify 用于多種目標(biāo)。下面列舉一些可能的情況:
性能監(jiān)控
您可能想確定應(yīng)用程序打開(kāi)最頻繁的文件是哪個(gè)。如果發(fā)現(xiàn)一個(gè)小文件被頻繁打開(kāi)與關(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)控文件的打開(kāi)與關(guān)閉事件,但對(duì)于目錄只想監(jiān)控創(chuàng)建與刪除事件。在任何可能的時(shí)候,您可以監(jiān)控您所感興趣的最小事件集。
結(jié)束語(yǔ)
在應(yīng)用到性能監(jiān)控,程序調(diào)試,以及自動(dòng)化等領(lǐng)域時(shí),inotify 是功能強(qiáng)大,高級(jí)粒度機(jī)制的 Linux 文件系統(tǒng)監(jiān)控工具。利用本文提供的樣例代碼,您可以開(kāi)始編寫用來(lái)實(shí)時(shí)記錄文件系統(tǒng)事件并最小化性能開(kāi)銷的應(yīng)用程序。
【編輯推薦】