1 Overview
最近公司的一個任務需要實時監控文件系統中某個文件的內容變化。由於程序本身由Java編寫,因此使用了inotify- java(http://code.google.com/p/inotify-java/)。inotify-java只是對Linux中 inotify相關的內核調用進行了封裝,因此在使用inotify-java之前有必要了解一下inotify。
inotify是一種基於inode的文件系統監控機制。從2.6.13-rc3版本起被集成到Linux 內核中,作為dnotify的替代。跟dnotify相比,inotify除了更易於使用之外還有以下主要的優點:
- inotify使用異步的事件通知機制。
- 可以監控文件系統中的任何對象(dnotify只能監控目錄)。如果inotify監控的是目錄,那么在目錄中的某個文件發生變化時,inotify可以通知發生變化的文件名(dnotify只能報告有變化,應用程序本身需要判斷是哪個文件發生變化)。
- 對於每個被監控的文件,inotify不需要維護一個打開的文件描述符,因此不會影響unmount之類的操作,相反會在被監控文件所在的文件系統被unmount時得到一個通知。
2 inotify
2.1 interfaces
如果C庫支持inotify,那么在C程序中直接#include <sys/inotify.h> 即可。
通過int inotify_init (void)系統調用進行初始化。返回值小於0說明調用失敗;否則會在內核中創建一個inotify實例,並且返回對應的文件描述符。該文件描述符用於讀 取inotify事件(inotify event),讀取的方式既可以是阻塞式,例如read,也可以是非阻塞式,例如select,poll和epoll(java6已經支持epoll,但 是不支持對FileChannel進行select)。
通過int inotify_add_watch (int fd, const char *path, __u32 mask)系統調用添加監控(watch)。參數fd是inotify_init調用返回的文件描述符;參數path是監控對象的路徑(文件,目錄等); 參數mask是期望得到通知的事件類型的位掩碼。inotify可以監控以下類型的事件:opens、closes、reads、writes、 creates、deletes、moves、metadata changes 和 unmounts。可以向一個inotify實例添加多個監控。該系統調用在成功情況下返回一個監控描述符(watch descriptor),它被用來標識不同的監控。mask參數的可選值如下:
Event | Description |
IN_ACCESS | File was read from. |
IN_MODIFY | File was written to. |
IN_ATTRIB | File's metadata (inode or xattr) was changed. |
IN_CLOSE_WRITE | File was closed (and was open for writing). |
IN_CLOSE_NOWRITE | File was closed (and was not open for writing). |
IN_OPEN | File was opened. |
IN_MOVED_FROM | File was moved away from watch. |
IN_MOVED_TO | File was moved to watch. |
IN_DELETE | File was deleted. |
IN_DELETE_SELF | The watch itself was deleted. |
IN_CLOSE | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE |
IN_MOVE | IN_MOVED_FROM | IN_MOVED_TO |
IN_ALL_EVENTS | Bitwise OR of all events. |
IN_ONESHOT | One shot support |
假設希望監控/home/user1/data.txt文件的讀取和修改事件,那么可以使用IN_ACCESS和IN_MODIFY,例如:
- int wd;
- wd = inotify_add_watch (fd, "/home/user1/data.txt", IN_ACCESS | IN_MODIFY);
假設希望只監控/home/user1/data.txt文件的修改事件一次,那么可以使用IN_MODIFY 和IN_ONESHOT,例如:
- int wd;
- wd = inotify_add_watch (fd, "/home/user1/data.txt", IN_MODIFY | IN_ONESHOT);
通過int inotify_rm_watch (int fd, int wd)系統調用移除監控。參數fd是inotify_init調用返回的文件描述符;參數wd是要被移除的監控描述符。如果調用成功,那么返回0;否則返回負值。
通過int close (int fd)系統調用銷毀inotify實例,以及關聯的所有監控和未決事件。參數fd是inotify_init調用返回的文件描述符。
2.2 configuration
inotify可以通過procfs和sysctl進行配置。/proc/sys/fs/inotify/目錄下有以下三個文件:
- max_queued_events 最大排隊的事件個數。如果排隊事件個數達到此值,那么新到的時間會被丟棄,並發送IN_Q_OVERFLOW事件。默認值16,384。
- max_user_instances 每個用戶可以創建的inotify實例最大值。默認值128。
- max_user_watches 每個用戶可以創建的監控的最大值。默認值8,192。
2.3 notifications
inotify的事件是異步通知的,並且在內部進行了排隊。但是對事件的讀取必須以同步方式進行。如果以read讀取,那么該方法一直阻塞到有事件到達,並且一次會讀入所有排隊中的事件。inotify的通知事件由inotify_event結構體定義,如下:
- struct inotify_event {
- __s32 wd; /* watch descriptor */
- __u32 mask; /* watch mask */
- __u32 cookie; /* cookie to synchronize two events */
- __u32 len; /* length (including nulls) of name */
- char name[0]; /* stub for possible name */
- };
其中wd是監控描述符,跟調用inotify_add_watch()時返回的監控描述符對應。如果應用程序想知道與之對應的文件,那么應用程序本身需要 建立監控描述符和文件之間的對應關系;mask是事件的位掩碼;cookie用於關聯兩個獨立的事件(例如IN_MOVED_FROM和 IN_MOVED_TO事件);len是name的長度;name是發生事件的對象名,如果監控的是目錄,那么name是發生事件的文件名。如果監控的是 文件,那么name是null。
如果被監控的目錄或者文件被unmount卸載,那么inotify會發送IN_UNMOUNT。假設監控對象是個目錄,如果將被監控目錄中某個文件移動 到被監控目錄外,那么inotify會發送IN_MOVED_FROM事件;如果將被監控目錄外的某個文件移動到被監控目錄中,那么inotify會發送 IN_MOVED_TO;如果將被監控目錄中的某個文件改名(即移動到相同目錄中),那么inotify會發送IN_MOVED_FROM和 IN_MOVED_TO兩個事件,並且這兩個事件的cookie相同。
需要注意的是,如果使用vi對被監控的文件進行編輯,那么不會得到IN_MODIFY事件,而是會得到IN_DELETE_SELF、 IN_MOVE_SELF和IN_IGNORED三個事件。這是因為vi其實是在一個副本上進行編輯,保存的時候將原文件覆蓋。收到IN_IGNORED 事件說明監控已經自動地從inotify實例中移除(由於監控對象已經被刪除,或者所在的文件系統被unmount)。由於監控已被移除,所以 inotify實例以后也不會再發送此文件相關的任何事件。
3 inotify-java
inotify-java並不復雜,每個Inotify實例都會創建兩個線程:readerThread和queueThread。 readerThread在循環中調用native read方法接收事件,並將接收到的事件放入BlockingQueue中。queueThread從BlockingQueue中take事件,回調 InotifyEventListener。
inotify-java使用起來也比較簡單。首先需要實例化inotify對象(在其構造函數中會調用 System.loadLibrary("inotify-java"));然后在inotify對象上注冊InotifyEventListener; 最后通過inotify對象的addWatch方法添加監控即可。以下是段示例代碼:
- import java.util.HashMap;
- import java.util.Map;
- import com.den_4.inotify_java.Constants;
- import com.den_4.inotify_java.EventQueueFull;
- import com.den_4.inotify_java.Inotify;
- import com.den_4.inotify_java.InotifyEvent;
- import com.den_4.inotify_java.InotifyEventListener;
- public class Test {
- //
- private static final Map<Integer, String> MASKS = new HashMap<Integer, String>();
- static {
- MASKS.put(Constants.IN_ACCESS, "IN_ACCESS");
- MASKS.put(Constants.IN_MODIFY, "IN_MODIFY");
- MASKS.put(Constants.IN_ATTRIB, "IN_ATTRIB");
- MASKS.put(Constants.IN_CLOSE_WRITE, "IN_CLOSE_WRITE");
- MASKS.put(Constants.IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE");
- MASKS.put(Constants.IN_OPEN, "IN_OPEN");
- MASKS.put(Constants.IN_MOVED_FROM, "IN_MOVED_FROM");
- MASKS.put(Constants.IN_MOVED_TO, "IN_MOVED_TO");
- MASKS.put(Constants.IN_CREATE, "IN_CREATE");
- MASKS.put(Constants.IN_DELETE, "IN_DELETE");
- MASKS.put(Constants.IN_DELETE_SELF, "IN_DELETE_SELF");
- MASKS.put(Constants.IN_MOVE_SELF, "IN_MOVE_SELF");
- MASKS.put(Constants.IN_UNMOUNT, "IN_UNMOUNT");
- MASKS.put(Constants.IN_Q_OVERFLOW, "IN_Q_OVERFLOW");
- MASKS.put(Constants.IN_IGNORED, "IN_IGNORED");
- MASKS.put(Constants.IN_ONLYDIR, "IN_ONLYDIR");
- MASKS.put(Constants.IN_DONT_FOLLOW, "IN_DONT_FOLLOW");
- MASKS.put(Constants.IN_MASK_ADD, "IN_MASK_ADD");
- MASKS.put(Constants.IN_ISDIR, "IN_ISDIR");
- MASKS.put(Constants.IN_ONESHOT, "IN_ONESHOT");
- }
- public static void main(String args[]) {
- try {
- Inotify i = new Inotify();
- InotifyEventListener e = new InotifyEventListener() {
- public void filesystemEventOccurred(InotifyEvent e) {
- System.out.println("inotify event, mask: " + getMask(e.getMask()) + ", name: " + e.getName() + ", now: " + System.currentTimeMillis());
- }
- public void queueFull(EventQueueFull e) {
- System.out.println("inotify event queue: " + e.getSource() + " is full");
- }
- };
- i.addInotifyEventListener(e);
- i.addWatch("./test/", Constants.IN_MODIFY);
- } catch (Throwable e) {
- e.printStackTrace();
- }
- }
- public static String getMask(int mask) {
- StringBuilder sb = new StringBuilder();
- for(Integer m : MASKS.keySet()) {
- if((mask & m) != 0) {
- if(sb.length() > 0) {
- sb.append("|");
- }
- sb.append(MASKS.get(m));
- }
- }
- return sb.toString();
- }
- }