Linux inotify使用安裝創(chuàng)建設(shè)備
想知道到Linux inotify系統(tǒng)的真相么,想知道Linux inotify系統(tǒng)中藏有的內(nèi)在奧義么,只有我來給大家全面講解介紹Linux inotify系統(tǒng)使用 Linux inotify 監(jiān)控 Linux 文件系統(tǒng)事件.
Linux inotify 是文件系統(tǒng)事件監(jiān)控機(jī)制,計(jì)劃包含在即將發(fā)布的 Linux 內(nèi)核中作為 dnotify 的有效替代。dnotify 是較早內(nèi)核支持的文件監(jiān)控機(jī)制。Linux inotify一種強(qiáng)大的、細(xì)粒度的、異步的機(jī)制,它滿足各種各樣的文件監(jiān)控需要,不僅限于安全和性能。
下面讓我們一起學(xué)習(xí)如何安裝 Linux inotify 和如何構(gòu)建一個(gè)示例用戶空間應(yīng)用程序來響應(yīng)文件系統(tǒng)事件。文件系統(tǒng)事件監(jiān)控對于從文件管理器到安全工具的各種程序都是必要的,但是 dnotify(早期內(nèi)核中的標(biāo)準(zhǔn))存在一些局限性,這使我們期待出現(xiàn)一種更加完善的機(jī)制。抱著這種期待,我們發(fā)現(xiàn)了 Linux inotify,一種更加現(xiàn)代化的文件系統(tǒng)事件監(jiān)控替代品。
為什么使用 Linux inotify?
使用 Linux inotify 取代 dnotify 的原因有很多。***個(gè)原因是,dnotify 需要您為每個(gè)打算監(jiān)控是否發(fā)生改變的目錄打開一個(gè)文件描述符。當(dāng)同時(shí)監(jiān)控多個(gè)目錄時(shí),這會(huì)消耗大量的資源,因?yàn)橛锌赡苓_(dá)到每個(gè)進(jìn)程的文件描述符限制。
除此之外,文件描述符會(huì)鎖定目錄,不允許卸載(unmount)支持的設(shè)備,這在存在可移動(dòng)介質(zhì)的環(huán)境中會(huì)引發(fā)問題。在使用 Linux inotify 時(shí),如果正在監(jiān)控被卸載的文件系統(tǒng)上的文件,那么監(jiān)控會(huì)被自動(dòng)移除并且您會(huì)接收到一個(gè)卸載事件。
dnotify 不如 Linux inotify 的第二個(gè)原因是 dnotify 有點(diǎn)復(fù)雜。注意,使用 dnotify 基礎(chǔ)設(shè)施的簡單文件系統(tǒng)監(jiān)控粒度只停留于目錄級別。為了使用 dnotify 進(jìn)行更細(xì)粒度的監(jiān)控,應(yīng)用程序編程人員必須為每個(gè)受監(jiān)控的目錄保留一個(gè) stat 結(jié)構(gòu)的緩存。
該用戶空間的 stat 結(jié)構(gòu)緩存需要用來明確確定當(dāng)接收到通知信號(hào)時(shí)目錄發(fā)生了什么變化。當(dāng)獲得通知信號(hào)時(shí),生成 stat 結(jié)構(gòu)列表并與***的狀態(tài)相比較。顯而易見,這種技術(shù)是不理想的。
Linux inotify 的另一個(gè)優(yōu)點(diǎn)是它使用文件描述符作為基本接口,使應(yīng)用程序開發(fā)者使用 select 和 poll 來監(jiān)控設(shè)備。這允許有效的多路 I/O 和與 Glib 的 mainloop 的集成。相反,dnotify 所使用的信號(hào)常常使程序員頭疼并且感覺不太優(yōu)雅。
Linux inotify 通過提供一個(gè)更優(yōu)雅的 API 解決了這些問題,該 API 使用最少的文件描述符,并確保更細(xì)粒度的監(jiān)控。與 Linux inotify 的通信是通過設(shè)備節(jié)點(diǎn)提供的。基于以上原因,對于監(jiān)控 Linux 2.6 平臺(tái)上的文件,Linux inotify 是您最明智的選擇。
回頁首安裝 Linux inotify
安裝 Linux inotify 的***步是確定您使用的 Linux 內(nèi)核是否支持它。檢查發(fā)行版的最簡單方法是,尋找是否存在 /dev/Linux inotify 設(shè)備。如果存在該設(shè)備,您可以跳到 在簡單應(yīng)用程序中使用 Linux inotify 一節(jié)。
在撰寫本文時(shí),Linux inotify 包含在 Andrew Morton 的 Linux 2.6-mm 目錄樹中,而且一些 Linux 發(fā)行版正在提供支持 Linux inotify 的內(nèi)核(包括 Gentoo 和 Ubuntu)或者具有提供支持的補(bǔ)充內(nèi)核包(例如 Fedora 和 SuSE)。
因?yàn)?Andrew 可能會(huì)根據(jù)需要從目錄樹刪除對 Linux inotify 的支持,并且 Linux inotify 版本還處于頻繁的開發(fā)階段,所以強(qiáng)烈建議您從頭開始打補(bǔ)丁。 如果缺少該設(shè)備,您可能需要對內(nèi)核打補(bǔ)丁并創(chuàng)建該設(shè)備。
為 Linux inotify 對內(nèi)核打補(bǔ)丁可以從 Linux Kernel Archives 獲得 Linux inotify 補(bǔ)?。ㄕ垍㈤?參考資料 一節(jié)的鏈接)。 您應(yīng)該為特定的內(nèi)核應(yīng)用***版本編號(hào)的補(bǔ)丁。每個(gè)發(fā)行版處理內(nèi)核的安裝都有所不同,但以下介紹的是一個(gè)通用指導(dǎo)。注意:從 Linux Kernel Archives 獲取發(fā)行版 2.6 Linux 內(nèi)核源文件,如果合適,請獲取***的穩(wěn)定版本。
從進(jìn)入內(nèi)核源文件目錄開始: bash:~$ cd /usr/src 因?yàn)槟缦劝惭b了內(nèi)核源文件,現(xiàn)在需要將它解壓縮: bash:~$ sudo tar jxvf linux-source-2.6.8.1.tar.bz2 現(xiàn)在,使您的 symlink 指向新的源文件目錄樹: bash:~$ sudo ln -sf linux-source-2.6.8.1 linux 改變當(dāng)前目錄到剛才創(chuàng)建的內(nèi)核源文件目錄:
bash:~$ cd linux 拷貝 Linux inotify 補(bǔ)?。?bash:~$ sudo cp ~/Linux inotify* /usr/src 將內(nèi)核打補(bǔ)?。?bash:~$ sudo patch -p1 < ../Linux inotify*.patch 構(gòu)建內(nèi)核: bash:~$ sudo make menuconfig
像平時(shí)一樣配置您的內(nèi)核,確保 Linux inotify 工作正常。如果必要,請將新內(nèi)核添加到引導(dǎo)加載程序中,但是一定要記住維護(hù)舊內(nèi)核的映像和引導(dǎo)加載程序選項(xiàng)。這一步對于不同引導(dǎo)加載程序有所不同(請參閱 參考資料 了解關(guān)于特定引導(dǎo)加載程序的更多信息)。
重新引導(dǎo)計(jì)算機(jī)并選擇啟用 Linux inotify 的新內(nèi)核。在繼續(xù)往下操作前,測試您的新內(nèi)核以確保它工作正常。
創(chuàng)建 Linux inotify 設(shè)備
接下來,您需要確保創(chuàng)建 /dev/Linux inotify 設(shè)備。以下步驟帶領(lǐng)您完成這個(gè)過程。重要注意:次設(shè)備編號(hào)可能會(huì)發(fā)生改變,所以您需要多加注意以確保它隨時(shí)更新!如果 Linux 安裝支持 udev 功能,它將會(huì)自動(dòng)保持更新。
在重新引導(dǎo)到新內(nèi)核后,您必須獲取次設(shè)備編號(hào): bash:~$ dmesg | grep ^Linux inotify 返回結(jié)果示例如下: Linux inotify device minor=63 因?yàn)?Linux inotify 是 misc 設(shè)備,所以主設(shè)備編號(hào)是 10。要?jiǎng)?chuàng)建設(shè)備節(jié)點(diǎn)作為根用戶,請執(zhí)行以下命令: bash:~$ mknod /dev/Linux inotify c 10 63
注意:如有必要,請使用合適的次設(shè)備編號(hào)替換“63”。 您可以隨意設(shè)置您想要的權(quán)限。一個(gè)示例權(quán)限設(shè)置如下所示: bash:~$ chown root:root /dev/Linux inotify bash:~$ chmod 666 /dev/Linux inotify 現(xiàn)在準(zhǔn)備使用 Linux inotify 設(shè)備進(jìn)行文件系統(tǒng)監(jiān)控。 回頁首
在簡單應(yīng)用程序中使用 Linux inotify為演示 Linux inotify 的使用,我將展示如何為文件系統(tǒng)事件構(gòu)造一個(gè)監(jiān)控任意目錄(或單個(gè)文件)的示例程序。我將站在一個(gè)較高的層次上來展示 Linux inotify 使文件系統(tǒng)監(jiān)控變得多么容易。
Main 方法這個(gè)簡單的示例向我們展示 Linux inotify 在任意目錄上設(shè)置監(jiān)控是多么容易。稍后我們將看到主要的幫助器例程。您可以在本文的 下載 一節(jié)獲取這些例子中使用的示例代碼。
清單 1. 在目錄上設(shè)置監(jiān)控
- /* This program will take as argument a directory name and monitor it,
- printing event notifications to the console.
- */
- int main (int argc, char **argv)
- {
- /* This is the file descriptor for the Linux inotify device */
- int Linux inotify_fd;
- /* First we open the Linux inotify dev entry */
- Linux inotify_fd = open_Linux inotify_dev();
- if (Linux inotify_fd < 0)
- {
- return 0;
- }
- /* We will need a place to enqueue Linux inotify events,
- this is needed because if you do not read events
- fast enough, you will miss them.
- */
- queue_t q;
- q = queue_create (128);
- /* Watch the directory passed in as argument
- Read on for why you might want to alter this for
- more efficient Linux inotify use in your app.
- */
- watch_dir (Linux inotify_fd, argv[1], ALL_MASK);
- process_Linux inotify_events (q, Linux inotify_fd);
- /* Finish up by destroying the queue, closing the fd,
- and returning a proper code
- */
- queue_destroy (q);
- close_Linux inotify_dev (Linux inotify_fd);
- return 0;
- }
重要的幫助器方法
以下是每個(gè)基于 Linux inotify 的應(yīng)用程序共同的最重要的幫助器例程: 為讀取而打開 Linux inotify 設(shè)備。 對從該設(shè)備讀取的事件進(jìn)行排隊(duì)。 允許應(yīng)用程序?qū)κ录ㄖM(jìn)行有用處理的實(shí)際的每事件處理器。
我不會(huì)深入鉆研事件排隊(duì)的細(xì)節(jié),因?yàn)槲覀兡軌蚴褂靡恍┎呗詠肀苊馀抨?duì)。提供的代碼中就展示了一個(gè)這樣的方法;更先進(jìn)的多線程方法可以并且已經(jīng)在其他地方實(shí)現(xiàn)。在那些實(shí)現(xiàn)中,讀者線程簡單地在 Linux inotify 設(shè)備上執(zhí)行 select(),然后將事件拷貝到一些線程共享的存儲(chǔ)空間(或者一些像 Glib 的異步消息隊(duì)列的東西),以后處理器線程會(huì)處理這里的事件。
清單 2. 打開 Linux inotify 設(shè)備
- /* This simply opens the Linux inotify node in dev (read only) */
- int open_Linux inotify_dev ()
- {
- int fd;
- fd = open("/dev/Linux inotify", O_RDONLY);
- if (fd < 0)
- {
- perror ("open(\"/dev/Linux inotify\", O_RDONLY) = ");
- }
- return fd;
- }
這對任何一個(gè)在 Linux 系統(tǒng)上進(jìn)行過文件編程的人來說都應(yīng)該是熟悉的。
清單 3. 實(shí)際的事件處理例程
- /* This method does the dirty work of determining what happened,
- then allows us to act appropriately
- */
- void handle_event (struct Linux inotify_event *event)
- {
- /* If the event was associated with a filename, we will store it here */
- char * cur_event_filename = NULL;
- /* This is the watch descriptor the event occurred on */
- int cur_event_wd = event->wd;
- if (event->len)
- {
- cur_event_filename = event->filename;
- }
- printf("FILENAME=%s\n", cur_event_filename);
- printf("\n");
- /* Perform event dependent handler routines */
- /* The mask is the magic that tells us what file operation occurred */
- switch (event->mask)
- {
- /* File was accessed */
- case IN_ACCESS:
- printf("ACCESS EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was modified */
- case IN_MODIFY:
- printf("MODIFY EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File changed attributes */
- case IN_ATTRIB:
- printf("ATTRIB EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was closed */
- case IN_CLOSE:
- printf("CLOSE EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was opened */
- case IN_OPEN:
- printf("OPEN EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was moved from X */
- case IN_MOVED_FROM:
- printf("MOVE_FROM EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was moved to X */
- case IN_MOVED_TO:
- printf("MOVE_TO EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* Subdir was deleted */
- case IN_DELETE_SUBDIR:
- printf("DELETE_SUBDIR EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was deleted */
- case IN_DELETE_FILE:
- printf("DELETE_FILE EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* Subdir was created */
- case IN_CREATE_SUBDIR:
- printf("CREATE_SUBDIR EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* File was created */
- case IN_CREATE_FILE:
- printf("CREATE_FILE EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* Watched entry was deleted */
- case IN_DELETE_SELF:
- printf("DELETE_SELF EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* Backing FS was unmounted */
- case IN_UNMOUNT:
- printf("UNMOUNT EVENT OCCURRED: File \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- /* Too many FS events were received without reading them
- some event notifications were potentially lost. */
- case IN_Q_OVERFLOW:
- printf("Warning: AN OVERFLOW EVENT OCCURRED: \n");
- break;
- case IN_IGNORED:
- printf("IGNORED EVENT OCCURRED: \n");
- break;
- /* Some unknown message received */
- default:
- printf ("UNKNOWN EVENT OCCURRED for file \"%s\" on WD #%i\n",
- cur_event_filename, cur_event_wd);
- break;
- }
- }
在每一條 case 語句中,您可以隨意執(zhí)行任意已實(shí)現(xiàn)并且滿足需要的方法。 至于性能監(jiān)控,您可以確定哪些文件是最經(jīng)常被讀取的和它們打開的持續(xù)時(shí)間。這種監(jiān)控非常方便,因?yàn)樵谀承┣闆r下,如果文件在短時(shí)間內(nèi)被應(yīng)用程序重復(fù)地讀取,它會(huì)將文件緩存在內(nèi)存中而不用返回磁盤去讀取,從而提高性能。
很容易舉出一些執(zhí)行有趣操作的特定于事件的處理器的例子。比如,如果您是在為底層文件系統(tǒng)實(shí)現(xiàn)一個(gè)元數(shù)據(jù)存儲(chǔ)索引,您可能會(huì)尋找文件創(chuàng)建事件,不久還會(huì)在該文件上觸發(fā)一個(gè)元數(shù)據(jù)挖掘操作。在安全環(huán)境中,如果文件被寫入一個(gè)無人可以寫入的目錄,您會(huì)觸發(fā)某些形式的系統(tǒng)警報(bào)。
請注意,Linux inotify 支持許多非常細(xì)粒度的事件 —— 例如 CLOSE 與 CLOSE_WRITE。 本文中的代碼所列舉的許多事件,可能您并不希望在每次代碼運(yùn)行時(shí)都看到。實(shí)際上,只要可能,您可以并且應(yīng)該只請求對您的應(yīng)用程序有用的事件子集。
出于測試目的,本文章提供的代碼通過嚴(yán)格使用完整掩碼(如可下載的示例代碼[請參閱 參考資料] 中 main 方法的第 51 行附近或者上面的 清單 1 中的第 29 行所執(zhí)行的)展示了許多事件。應(yīng)用程序員通常想要有更多選擇,而您則需要更特定的掩碼來滿足您的需要。這使您可以從上述的 handle_event() 方法中的 catch 語句刪除不感興趣的條目。
【編輯推薦】