Inotify --2.6內核中的文件系統變化通知機制

轉摘

Inotify --2.6內核中的文件系統變化通知機制

一、 引言

    衆所周知,Linux 桌面系統與 MAC 或 Windows 相比有許多不如人意的地方,爲了改善這種狀況,開源社區提出用戶態需要內核提供一些機制,以便用戶態能夠及時地得知內核或底層硬件設備發生了什麼,從而能夠更好地管理設備,給用戶提供更好的服務,如 hotplug、udev 和 inotify 就是這種需求催生的。Hotplug 是一種內核向用戶態應用通報關於熱插拔設備一些事件發生的機制,桌面系統能夠利用它對設備進行有效的管理,udev 動態地維護 /dev 下的設備文件,inotify 是一種文件系統的變化通知機制,如文件增加、刪除等事件可以立刻讓用戶態得知,該機制是著名的桌面搜索引擎項目 beagle 引入的,並在 Gamin 等項目中被應用。

    事實上,在 inotify 之前已經存在一種類似的機制叫 dnotify,但是它存在許多缺陷:

    1. 對於想監視的每一個目錄,用戶都需要打開一個文件描述符,因此如果需要監視的目錄較多,將導致打開許多文件描述符,特別是,如果被監視目錄在移動介質上(如光盤和 USB 盤),將導致無法 umount 這些文件系統,因爲使用 dnotify 的應用打開的文件描述符在使用該文件系統。

    2. dnotify 是基於目錄的,它只能得到目錄變化事件,當然在目錄內的文件的變化會影響到其所在目錄從而引發目錄變化事件,但是要想通過目錄事件來得知哪個文件變化,需要緩存許多 stat 結構的數據。

    3. Dnotify 的接口非常不友好,它使用 signal.

    Inotify 是爲替代 dnotify 而設計的,它克服了 dnotify 的缺陷,提供了更好用的,簡潔而強大的文件變化通知機制:

    1. Inotify 不需要對被監視的目標打開文件描述符,而且如果被監視目標在可移動介質上,那麼在 umount 該介質上的文件系統後,被監視目標對應的 watch 將被自動刪除,並且會產生一個 umount 事件。

    2. Inotify 既可以監視文件,也可以監視目錄。

    3. Inotify 使用系統調用而非 SIGIO 來通知文件系統事件。

    4. Inotify 使用文件描述符作爲接口,因而可以使用通常的文件 I/O 操作select 和 poll 來監視文件系統的變化。

    Inotify 可以監視的文件系統事件包括:

    IN_ACCESS,即文件被訪問IN_MODIFY,文件被 write IN_ATTRIB,文件屬性被修改,如 chmod、chown、touch 等IN_CLOSE_WRITE,可寫文件被 close IN_CLOSE_NOWRITE,不可寫文件被 close IN_OPEN,文件被 open IN_MOVED_FROM,文件被移走,如 mv IN_MOVED_TO,文件被移來,如 mv、cp IN_CREATE,創建新文件IN_DELETE,文件被刪除,如 rm IN_DELETE_SELF,自刪除,即一個可執行文件在執行時刪除自己IN_MOVE_SELF,自移動,即一個可執行文件在執行時移動自己IN_UNMOUNT,宿主文件系統被 umount IN_CLOSE,文件被關閉,等同於(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)

    IN_MOVE,文件被移動,等同於(IN_MOVED_FROM | IN_MOVED_TO)

    注:上面所說的文件也包括目錄。

    二、用戶接口

    在用戶態,inotify 通過三個系統調用和在返回的文件描述符上的文件 I/ 操作來使用,使用 inotify 的第一步是創建 inotify 實例:

 

 

 


 

                int fd = inotify_init ();
       
 

 

    每一個 inotify 實例對應一個獨立的排序的隊列。

    文件系統的變化事件被稱做 watches 的一個對象管理,每一個 watch 是一個二元組(目標,事件掩碼),目標可以是文件或目錄,事件掩碼錶示應用希望關注的 inotify 事件,每一個位對應一個 inotify 事件。Watch 對象通過 watch描述符引用,watches 通過文件或目錄的路徑名來添加。目錄 watches 將返回在該目錄下的所有文件上面發生的事件。

    下面函數用於添加一個 watch:


                int wd = inotify_add_watch (fd, path, mask);
       
 

 

    fd 是 inotify_init() 返回的文件描述符,path 是被監視的目標的路徑名(即文件名或目錄名),mask 是事件掩碼, 在頭文件 linux/inotify.h 中定義了每一位代表的事件。可以使用同樣的方式來修改事件掩碼,即改變希望被通知的inotify 事件。Wd 是 watch 描述符。

    下面的函數用於刪除一個 watch:


        int ret = inotify_rm_watch (fd, wd);
       
 

 

    fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函數的返回值。

    文件事件用一個 inotify_event 結構表示,它通過由 inotify_init() 返回的文件描述符使用通常文件讀取函數 read 來獲得


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 爲被監視目標的 watch 描述符,mask 爲事件掩碼,len 爲 name字符串的長度,name 爲被監視目標的路徑名,該結構的 name 字段爲一個樁,它只是爲了用戶方面引用文件名,文件名是變長的,它實際緊跟在該結構的後面,文件名將被 0 填充以使下一個事件結構能夠 4 字節對齊。注意,len 也把填充字節數統計在內。

    通過 read 調用可以一次獲得多個事件,只要提供的 buf 足夠大。


                size_t len = read (fd, buf, BUF_LEN);
       
 

 

    buf 是一個 inotify_event 結構的數組指針,BUF_LEN 指定要讀取的總長度,buf 大小至少要不小於 BUF_LEN,該調用返回的事件數取決於 BUF_LEN 以及事件中文件名的長度。Len 爲實際讀去的字節數,即獲得的事件的總長度。

    可以在函數 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 來得到當前隊列的長度。close(fd)將刪除所有添加到 fd 中的 watch 並做必要的清理。


                int inotify_init (void);
        int inotify_add_watch (int fd, const char *path, __u32 mask);
        int inotify_rm_watch (int fd, __u32 mask);
       
 
三、內核實現機理
    在內核中,每一個 inotify 實例對應一個 inotify_device 結構:

struct inotify_device {
        wait_queue_head_t       wq;             /* wait queue for i/o */
        struct idr              idr;            /* idr mapping wd -> watch */
        struct semaphore        sem;            /* protects this bad boy */
        struct list_head        events;         /* list of queued events */
        struct list_head        watches;        /* list of watches */
        atomic_t                count;          /* reference count */
        struct user_struct      *user;          /* user who opened this dev */
        unsigned int            queue_size;     /* size of the queue (bytes) */
        unsigned int            event_count;    /* number of pending events */
        unsigned int            max_events;     /* maximum number of events */
        u32                     last_wd;        /* the last wd allocated */
};

 


    d_list 指向所有 inotify_device 組成的列表的,i_list 指向所有被監視 inode 組成的列表,count 是引用計數,dev 指向該 watch 所在的 inotify 實例對應的 inotify_device 結構,inode 指向該 watch 要監視的 inode,wd 是分配給該 watch 的描述符,mask 是該 watch 的事件掩碼,表示它對哪些文件系統事件感興趣。

    結構 inotify_device 在用戶態調用 inotify_init() 時創建,當關閉 inotify_init()返回的文件描述符時將被釋放。結構 inotify_watch 在用戶態調用 inotify_add_watch()時創建,在用戶態調用 inotify_rm_watch() 或 close(fd) 時被釋放。

    無論是目錄還是文件,在內核中都對應一個 inode 結構,inotify 系統在 inode 結構中增加了兩個字段:

 

struct inotify_watch {
        struct list_head        d_list; /* entry in inotify_device's list */
        struct list_head        i_list; /* entry in inode's list */
        atomic_t                count;  /* reference count */
        struct inotify_device   *dev;   /* associated device */
        struct inode            *inode; /* associated inode */
        s32                     wd;     /* watch descriptor */
        u32                     mask;   /* event mask for this watch */
};

 


    d_list 指向所有 inotify_device 組成的列表的,i_list 指向所有被監視 inode 組成的列表,count 是引用計數,dev 指向該 watch 所在的 inotify 實例對應的 inotify_device 結構,inode 指向該 watch 要監視的 inode,wd 是分配給該 watch 的描述符,mask 是該 watch 的事件掩碼,表示它對哪些文件系統事件感興趣。

    結構 inotify_device 在用戶態調用 inotify_init() 時創建,當關閉 inotify_init()返回的文件描述符時將被釋放。結構 inotify_watch 在用戶態調用 inotify_add_watch()時創建,在用戶態調用 inotify_rm_watch() 或 close(fd) 時被釋放。

    無論是目錄還是文件,在內核中都對應一個 inode 結構,inotify 系統在 inode 結構中增加了兩個字段:

 

#ifdef CONFIG_INOTIFY
 struct list_head inotify_watches; /* watches on this inode */
 struct semaphore inotify_sem; /* protects the watches list */
#endif

 


    inotify_watches 是在被監視目標上的 watch 列表,每當用戶調用 inotify_add_watch()時,內核就爲添加的 watch 創建一個 inotify_watch 結構,並把它插入到被監視目標對應的 inode 的 inotify_watches 列表。inotify_sem 用於同步對 inotify_watches 列表的訪問。當文件系統發生第一部分提到的事件之一時,相應的文件系統代碼將顯示調用fsnotify_* 來把相應的事件報告給 inotify 系統,其中*號就是相應的事件名,目前實現包括:

    fsnotify_move,文件從一個目錄移動到另一個目錄fsnotify_nameremove,文件從目錄中刪除fsnotify_inoderemove,自刪除fsnotify_create,創建新文件fsnotify_mkdir,創建新目錄fsnotify_access,文件被讀fsnotify_modify,文件被寫fsnotify_open,文件被打開fsnotify_close,文件被關閉fsnotify_xattr,文件的擴展屬性被修改fsnotify_change,文件被修改或原數據被修改有一個例外情況,就是 inotify_unmount_inodes,它會在文件系統被 umount 時調用來通知 umount 事件給 inotify 系統。

    以上提到的通知函數最後都調用 inotify_inode_queue_event(inotify_unmount_inodes直接調用 inotify_dev_queue_event ),該函數首先判斷對應的inode是否被監視,這通過查看 inotify_watches 列表是否爲空來實現,如果發現 inode 沒有被監視,什麼也不做,立刻返回,反之,遍歷 inotify_watches 列表,看是否當前的文件操作事件被某個 watch 監視,如果是,調用 inotify_dev_queue_event,否則,返回。函數inotify_dev_queue_event 首先判斷該事件是否是上一個事件的重複,如果是就丟棄該事件並返回,否則,它判斷是否 inotify 實例即 inotify_device 的事件隊列是否溢出,如果溢出,產生一個溢出事件,否則產生一個當前的文件操作事件,這些事件通過kernel_event 構建,kernel_event 將創建一個 inotify_kernel_event 結構,然後把該結構插入到對應的 inotify_device 的 events 事件列表,然後喚醒等待在inotify_device 結構中的 wq 指向的等待隊列。想監視文件系統事件的用戶態進程在inotify 實例(即 inotify_init() 返回的文件描述符)上調用 read 時但沒有事件時就掛在等待隊列 wq 上。

四、使用示例
    下面是一個使用 inotify 來監視文件系統事件的例子:
 

#include
#include
#include

_syscall0(int, inotify_init)
_syscall3(int, inotify_add_watch, int, fd, const char *, path, __u32, mask)
_syscall2(int, inotify_rm_watch, int, fd, __u32, mask)

char * monitored_files[] = {
 "./tmp_file",
 "./tmp_dir",
 "/mnt/sda3/windows_file"
};

struct wd_name {
 int wd;
 char * name;
};

#define WD_NUM 3
struct wd_name wd_array[WD_NUM];

char * event_array[] = {
 "File was accessed",
 "File was modified",
 "File attributes were changed",
 "writtable file closed",
 "Unwrittable file closed",
 "File was opened",
 "File was moved from X",
 "File was moved to Y",
 "Subfile was created",
 "Subfile was deleted",
 "Self was deleted",
 "Self was moved",
 "",
 "Backing fs was unmounted",
 "Event queued overflowed",
 "File was ignored"
};
#define EVENT_NUM 16
#define MAX_BUF_SIZE 1024
 
int main(void)
{
 int fd;
 int wd;
 char buffer[1024];
 char * offset = NULL;
 struct inotify_event * event;
 int len, tmp_len;
 char strbuf[16];
 int i = 0;
 
 fd = inotify_init();
 if (fd < 0) {
  printf("Fail to initialize inotify./n");
  exit(-1);
 }

 for (i=0; imask & IN_ISDIR) {
    memcpy(strbuf, "Direcotory", 11);
   }
   else {
    memcpy(strbuf, "File", 5);
   }
   printf("Object type: %s/n", strbuf);
   for (i=0; iwd != wd_array[i].wd) continue;
    printf("Object name: %s/n", wd_array[i].name);
    break;
   }
   printf("Event mask: %08X/n", event->mask);
   for (i=0; imask & (1<len;
   event = (struct inotify_event *)(offset + tmp_len);
   offset += tmp_len;
  }
 }
}

 


    該程序將監視發生在當前目錄下的文件 tmp_file 與當前目錄下的目錄 tmp_dir 上的所有文件系統事件, 同時它也將監視發生在文件 /mnt/sda3/windows_file 上的文件系統事件,注意,/mnt/sda3 是 SATA 硬盤分區 3 的掛接點。

    細心的讀者可能注意到,該程序首部使用 _syscallN 來聲明 inotify 系統調用,原因是這些系統調用是在最新的穩定內核 2.6.13 中引入的,glibc 並沒有實現這些系統調用的庫函數版本,因此,爲了能在程序中使用這些系統調用,必須通過 _syscallN 來聲明這些新的系統,其中的 N 爲要聲明的系統調用實際的參數數。還有需要注意的地方是系統的頭文件必須與被啓動的內核匹配,爲了讓上面的程序能夠成功編譯,必須讓 2.6.13 的內核頭文件(包括 include/linux/*, include/asm/* 和 include/asm-generic/*)在頭文件搜索路徑內,並且是第一優先搜索的頭文件路徑,因爲 _syscallN 需要用到這些頭文件中的 linux/unistd.h 和 asm/unistd.h,它們包含了 inotify 的三個系統調用的系統調用號 __NR_inotify_init、__NR_inotify_add_watch 和 __NR_inotify_rm_watch.

    因此,要想成功編譯此程序,只要把用戶編譯好的內核的頭文件拷貝到該程序所在的路徑,並使用如下命令編譯即可:

 

$gcc -o inotify_example  -I. inotify_example.c

 


    注意:當前目錄下應當包含 linux、asm 和 asm-generic 三個已編譯好的 2.6.13 內核的有文件目錄,asm 是一個鏈接,因此拷貝 asm 頭文件的時候需要拷貝 asm 與 asm-ARCH(對於 x86 平臺應當是 asm-i386)。然後,爲了運行該程序,需要在當前目錄下創建文件 tmp_file 和目錄 tmp_dir,對於/mnt/sda3/windows_file 文件,用戶需要依自己的實際情況而定,可能是/mnt/dosc/windows_file,即 /mnt/dosc 是一個 FAT32 的 windows 硬盤,因此用戶在編譯該程序時需要根據自己的實際情況來修改 /mnt/sda3.Windows_file 是在被 mount 硬盤上創建的一個文件,爲了運行該程序,它必須被創建。

    以下是作者在 redhat 9.0 上運行此程序得到的一些結果:

    當運行此程序的時候在另一個虛擬終端執行 cat ./tmp_file,此程序的輸出爲:


Some event happens, len = 48.
Object type: File
Object name: ./tmp_file
Event mask: 00000020
Event: File was opened
Object type: File
Object name: ./tmp_file
Event mask: 00000001
Event: File was accessed
Object type: File
Object name: ./tmp_file
Event mask: 00000010
Event: Unwrittable file closed

 


    以上事件清楚地說明了 cat 指令執行了文件 open 和 close 操作,當然 open 和 close操作都屬於 access 操作,任何對文件的操作都是 access 操作。

    此外,運行 vi ./tmp_file,發現 vi實際在編輯文件時複製了一個副本,在未保存之前是對副本進行操作。運行 vi ./tmp_file, 修改並保存退出時,發現 vi 實際在保存修改時刪除了最初的文件並把那個副本文件名更改爲最初的文件的名稱。注意,事件"File was ignored"表示系統把該文件對應的 watch 從 inotify 實例的 watch 列表中刪除,因爲文件已經被刪除。讀者可以自己分別執行命令:echo "abc" > ./tmp_file 、rm -f tmp_file、 ls tmp_dir、 cd tmp_dir;touch c.txt、 rm c.txt 、 umount /mnt/sda3(實際用戶需要使用自己當時的 mount 點路徑名),然後分析一下結果。Umount 觸發兩個事件,一個表示文件已經被刪除或不在存在,另一個表示該文件的 watch被從 watch 列表中刪除。

    五、典型應用

    beagle 是 GNOME 的桌面搜索引擎項目,inotify 的引入就是完全受它的驅動而做的。對於桌面搜索引擎,它一般作爲一個優先級很低的後臺進程運行, 只有在系統沒有其他任務可運行時才被調度執行,桌面搜索引擎的主要用途就是爲系統的文件系統的文件建立索引數據庫,以便用戶在需要某文件但又想不起存放在哪裏時能夠根據某些關鍵字或特徵快速地搜索到需要的文件,就象使用網絡搜索引擎 google 一樣便捷。文件系統有個特點就是隻有某些文件會變化,因此桌面搜索引擎在第一次建立完索引數據庫後,沒必要重複遍歷所有的文件建立新的索引,它只需要更新修改了的文件的索引,建立新增加的文件的索引,刪除已經刪除的文件的索引就足夠了,這樣桌面搜索引擎需要做的工作就大大地減少。Inotify 就是爲這一意圖專門設計的,beagle 爲需要監視的目錄或文件創建了inotify 實例,然後它就等待該 inotify 上發生文件系統事件,如果沒有任何文件變化,beagle 將不需要任何開銷,只有在有被監視的事件發生時,beagle 才被喚醒並根據實際事件來更新對應的文件的索引,然後繼續睡眠等待下一個文件系統事件發生。在 SuSe 9.3 和即將發佈的 10.0 中就包含了該桌面搜索引擎,它能夠爲文檔、email、音樂、圖象和應用等建立索引。使用過 windows 下的桌面搜索引擎的讀者對 google 和 yahoo 以及 Microsoft 的桌面搜索引擎有深刻的體會,感興趣讀者可以安裝 SuSe 使用一下。

    六、小結

    inotify 是在 2.6.13 中引入的新功能,它爲用戶態監視文件系統的變化提供了強大的支持,本文詳盡地介紹了其起源、內核實現、用戶接口以及使用,有興趣的讀者可以讀 2.6.13的相關源碼來進一步瞭解其實現細節。


/////////////////////////////////////////////////////////////////////////////////////////////////////////////

 
文章發表於: 2006年 10月02日 11:35    發表主題: 轉貼 :關於Linux 文件系統的異步 I/O 擴展  引用並回復
本文中要介紹一個所謂的"Linux 文件系統的守護神",這是指一個能實時地觀察 Linux 文件系統的變化情況的程序模塊。能夠實時的觀察文件系統的變化情況,並做出及時的適當的反應,這對於應用 Linux 做桌面計算機系統來說,是十分的有趣,也是十分的重要的。本文還要介紹 Linux 文件系統的異步 I/O 的擴展。同樣,這對於 Linux 系統的桌面應用也是關鍵的。

1 Linux 文件系統的守護神
傳統的 Linux 文件系統呈現給用戶程序的界面,確實是十分的乾淨利落。用戶程序可以打開一個文件,向文件中線性的寫入數據,從文件的某一位置開始,線性的讀出數據,關閉一個文件,刪除一個文件,創建一個文件,等等。請看,只有這麼若干個簡潔的操作原語,可是卻能提供這麼多豐富的應用。但是,我們注意到,用於訪問 Linux 的文件系統的這些操作原語,並沒有提供非常複雜的加鎖解鎖的功能。這是一件很奇妙的事情,如果來自不同的用戶程序的請求發生了衝突怎麼辦呢?
我們不妨走的再靠近一點,仔細的看看刪除一個文件是怎樣進行的。如果已經有一個用戶程序在訪問一個文件,而另外一個用戶程序正好要刪除這一個文件,這時會發生些什麼呢?我們知道,Linux 的文件系統是基於所謂的 inode 的,每個文件都相伴有一個 inode。在 inode 中記錄了關於這個文件的一些系統信息,比如文件的所有者,文件相關的一些權限記錄,關於文件的若干個時間戳,等等。在內存中的 inode 還維持着一個關於自己的使用計數。每當一個 inode 所代表的文件被打開一次,這個 inode 就把關於自己的使用計數加一。每當這個 inode 所代表的文件一被關閉,這個 inode 就把關於自己的使用計數減一。當用戶程序刪除一個文件的時候,相關的系統調用很快就返回到這個用戶程序,告訴它,相應的文件已經被刪除了。但是相應的 inode 還是保留在系統中,inode 首先要檢查自己的使用計數,如果使用計數爲零,那麼 Linux Kernel 纔可以真正的去刪除這個文件。如果使用計數大於零,也就是說,還有其它的用戶程序在訪問這一個文件,那麼 Linux Kernel 需要等待這些其他的用戶程序一個個都完成對這一個文件的訪問纔行。也就是說,要等到這個 inode 的使用計數掉到零,才能真正的去刪除這一個文件。
我們可以設想一下,如果有一個 MP3 播放程序在播放一首 MP3 音樂,我們覺得它不好聽,就到硬盤上找到這個文件,把它 rm 掉了。這時候,MP3 播放程序並不受到影響,還是可以繼續播放這首 MP3 音樂,雖然這時候在文件系統上用 ls 已經找不到這個 MP3 音樂文件了。實際上,一直要到 MP3 播放程序停止播放這首 MP3 音樂,然後 Linux 文件系統才真正的從硬盤上刪除這個 MP3 文件。這個經驗和我們在 Windows 平臺上遇到的截然不同。
在 Windows 平臺上,當我們試圖在文件夾窗口中用鼠標點擊右鍵菜單刪除 Winamp 正在播放的一首 MP3 音樂的時候,Windows 系統會用一個彈出對話框告訴我們,這個文件正在被使用,沒辦法刪除。Windows 系統的關於刪除文件的這樣一個解釋,如果使用不當的話,會帶來一個滑稽可笑的問題。我們可以設想一下,用戶的一個 P2P 的文件共享程序提供了一個 MP3 文件以供別人下載,恰巧這個 MP3 音樂文件十分的熱門,不斷的有人來下載,這個用戶最終決定要節省一下帶寬,想要把這個 MP3 音樂文件刪除掉,但是 Windows 系統卻不允許用戶這樣做,因爲這個 P2P 的文件共享程序總是在使用這個 MP3 文件。用戶要想刪除這個文件,不得不先把 P2P 的文件共享程序給停下來!呵呵。
但是 Linux 的文件系統的操作原語也有它自己的問題。我們知道,在一個 Linux Shell 的命令行上,先 rm,然後再 ls,非常的乾淨,被 rm 的文件沒有了,被刪除了。但是我們可以設想有一個圖形界面的文件管理程序,當用戶從 Shell 的命令行上 rm 掉一個文件的時候,這個圖形界面的文件管理程序並沒有收到任何人發給它的任何消息,它還以爲什麼都沒有發生,被刪除掉的文件還在那兒。這實在是很 U.G.L.Y. 啊。
那麼要想解決這個問題,一個明顯的但是非常不好的辦法,就是讓一個後臺進程 Daemon 每隔一個很短的時間間隔,就檢查一下文件系統上這個目錄的情況,看看有沒有發生什麼變化。這個辦法的缺點真的是顯而易見的,不但系統的性能受到影響,而且它的反應也還不是實時的。
如果我們需要用戶程序能夠實時地瞭解文件系統上某一個目錄的變化情況,從實時這個角度出發,顯然,我們需要有一箇中斷機制。我們都知道,硬件中斷能夠實時地把系統某一個部件的情況反映給中央處理器,同樣的,要想把位於系統內核中的文件系統的情況實時地反映給用戶程序,我們也需要一個由操作系統內核到達用戶進程的軟件中斷機制。熟悉 Linux 系統編程的讀者朋友們立即就會想到,這個中斷機制在 Linux 系統中早已就有了,這就是信號傳遞 signal。
找到了信號傳遞這樣一箇中斷用戶進程的機制,一切似乎都已齊備,看來可以動手實現這樣一個 Linux 文件系統的守護神,來實時地監視文件系統的變化情況,並且及時地把消息通知給用戶程序了。不過且慢,讓我們搜索一下 Linux Kernel,看看是否有別人也在做同樣的工作。哈哈,果不其然,原來這樣一個實時地監視文件系統情況的機制早已在 Linux 內核中實現了。下面一段就是取自 Linux Kernel 文檔的一段小小例程,說明了 Linux Kernel 中的 dnotify 功能的用法。dnotify 就是指 directory notification,監視文件系統上一個目錄中的情況。
代碼:
 
#define _GNU_SOURCE /* needed to get the defines */
#include /* in glibc 2.2 this has the needed
values defined */
#include
#include
#include

static volatile int event_fd;

// 信號處理例程
static void handler(int sig, siginfo_t *si, void *data)
{
event_fd = si->si_fd;
}

int main(void)
{
struct sigaction act;
int fd;

// 登記信號處理例程
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN, &act, NULL);

// 需要了解當前目錄"."的情況
fd = open(".", O_RDONLY);
fcntl(fd, F_SETSIG, SIGRTMIN);
fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_MULTISHOT);
/* we will now be notified if any of the files
in "." is modified or new files are created */
while (1) {
// 收到信號後,就會執行信號處理例程。
// 而 pause() 也就結束了。
pause();
printf("Got event on fd=%d/n", event_fd);
}
}


上面這一小段例程,對於熟悉 Linux 系統編程的讀者朋友們來說,是很容易理解的。程序首先註冊一個信號處理例程,然後通知 Kernel,我要觀察 fd 上的 DN_MODIFY 和 DN_CREATE 和 DN_MULTISHOT 事件。(關於這些事件的詳細定義,請讀者朋友們參閱文後所列的參考資料。) Linux Kernel 收到這個請求後,把相應的 fd 的 inode 給做上記號,然後 Linux Kernel 和用戶應用程序就自顧自去處理各自的別的事情去了。等到 inode 上發生了相應的事件,Linux Kernel 就把信號發給用戶進程,於是開始執行信號處理例程,用戶程序對文件系統上的變化也就可以及時的做出反應了。而在這整個過程中,系統以及用戶程序的正常運行基本上未受到性能上的影響。這裏還需要說明的是,dnotify 並沒有通過增加新的系統調用來完成它的功能,而是通過 fcntl 來完成任務的。增加一個系統調用,相對來說是一個很大的手術,而且如果設計不當,處理得不好的話,傷疤會一直留在那裏,這是 Linux Kernel 的開發者們所非常不願意見到的事情。

2 Linux 文件系統的異步 I/O 擴展
對於桌面計算機系統來說,能夠快速的響應用戶的請求,這也是十分關鍵的。換句話說,當用戶移動鼠標的時候,不管系統正在進行什麼天大的、重要的、神聖的、不可打斷的工作,它都得立即停下,並且要讓鼠標立即流暢的在計算機屏幕上完美地運動起來。對於習慣在傳統的 Linux 命令行上工作的讀者朋友們來說,讓鼠標能夠在任何時間都可以在計算機屏幕上向無頭蒼蠅一樣地亂竄,竟然被當成是最重要的系統任務,這實在有一點讓人難以接受。不過,當你從 Linux 命令行上轉移到 GNOME 或者 KDE 這樣的圖形界面的用戶環境的時候,鼠標被鎖死,百分之百的也是會讓你失去理智的。所以,還是讓我們接受這一個現實,看一看如何才能增加系統的響應速度吧。
從文件系統的角度講,特別是考慮到網絡文件系統,它的響應速度有可能會相當的慢。當用戶在文件管理程序中,選擇了對文件進行某一個操作以後,文件系統可能會需要相當長的時間,才能完成這一操作。如果文件管理程序必須要等待文件系統完成這一操作,然後才能繼續的話,這顯然會給文件管理程序的用戶帶來非常不愉快的經歷。解決這一個問題的辦法,就是要實現異步的文件系統 I/O。
在 Linux 的 Gnome 桌面環境中,由 GnomeVFS 包裹了真正的 Linux 文件系統 I/O,實現了一個異步的文件系統 I/O 接口 API。我們可以看到下面這個用 GnomeVFS 打開文件的例子。
代碼:

enum _GnomeVFSOpenMode {
GNOME_VFS_OPEN_NONE = 0,
GNOME_VFS_OPEN_READ = 1 << 0,
GNOME_VFS_OPEN_WRITE = 1 << 1,
GNOME_VFS_OPEN_RANDOM = 1 << 2
};

typedef enum _GnomeVFSOpenMode GnomeVFSOpenMode;

typedef void (* GnomeVFSAsyncOpenCallback)
(GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gpointer callback_data);

GnomeVFSResult gnome_vfs_async_open
(GnomeVFSAsyncHandle **handle_return,
const gchar *text_uri,
GnomeVFSOpenMode open_mode,
GnomeVFSAsyncOpenCallback callback,
gpointer callback_data);

我們注意到,上面的代碼段中,用戶程序爲了打開一個文件,向 GnomeVFS 註冊了一個 call back 例程。在註冊了這一個 call back 例程之後,函數調用就立即返回給用戶程序,用戶程序就可以處理自己的別的事情去了,比如進一步響應來自用戶的其??肭螅?鵲取6?蔽募?低懲瓿啥暈募?拇蚩?僮饕院螅珿nomeVFS 就會調用剛剛註冊的 call back 例程,通知用戶程序,文件已經打開。
3 小結
我們在本文中瞭解了 Linux Kernel 中的 dnotify,可以幫助我們實時地監視文件系統目錄樹中的變化情況;也瞭解了 Gnome 桌面環境的 GnomeVFS 異步文件系統 I/O 擴展;可以幫助用戶程序不至於被文件系統的請求所 Block。這兩個功能對於 Linux 系統在桌面上的應用都是很重要的。


 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章