[讀書心得] The Linux Programming Interface 第 19 章:檔案監控事件

1
2
3
4
5
6
7
struct inotify_event {
int wd;
uint32_t mask;
uint32_t cookie;
uint32_t len;
char name[];
};

前言

這一章節,主要是在了解應用程式為了知道監控物件 (object) 發生了哪些事件,需要能夠監視檔案以及目錄,像是說

  • 圖形化的檔案管理程式可以知道檔案何時從目前的正在顯示的目錄中新增或是刪除
  • 一個守護程式 (又稱常駐程式, daemon) 可能會監視一個組態檔案觀察是否該檔案是否有被更動過

從 kernel v2.6.13 開始,Linux 提供了 inotify 機制,接受應用程式監控檔案與目錄事件
inotify 機制取代了舊有的 dnotify 機制,最後會介紹 dnotify 並且解釋 inotify 為何比較好

19.1 概觀

使用 inotify API 步驟

  1. 先使用 inotify_init() 建立一個 inotify instance,之後會回傳一個檔案描述符 (file descriptor)
  2. 針對核心有興趣的檔案,使用 inotify_add_watch() 新增項目到所建立的 inotify instance 中,每個項目都有一個路徑名稱與監控事件集合,透過函式內的位元遮罩 (bit mask) 進行處理
  3. inotify_add_watch() 針對每一個監控項目,回傳一個監控描述符 (watch descriptor, wd)
  4. 透過 read() 操作讀取 inotify instance,每次成功 read() 後都會回傳一個以上的 inotify_event 結構
1
2
3
4
5
6
7
struct inotify_event {
int wd;
uint32_t mask;
uint32_t cookie;
uint32_t len;
char name[];
};

19.2 inotify API

新增 inotify instance

1
2
3
4
#include <sys/inotify.h>

/* Returns file descriptor on success, or -1 on error */
int inotify_init(void);

回傳的 file descriptor 可以對於一個 inotify instance 進行操作

新增監看項目,回傳監看描述符

根據上方的步驟,接下來我們會執行

1
2
3
4
#include <sys/inotify.h>

/* Returns watch descriptor (wd) on success, or -1 on error */
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

此項目會對 fd 所參考的 inotify instance 新增一個監看項目,每一個監看項目有

  • 監看路徑
  • 監看事件集合 (bit mask)
  • 若 pathname 為空,則 inotify_add_watch() 會建立一個新的監看項目並回傳一個非負值的 watch descriptor

移除監看項目

若要移除 inotify instance 中的監看項目,會使用系統呼叫:

1
2
3
4
#include <sys/inotify.h>

/* Returns 0 on success, or -1 on error */
int inotify_rm_watch(int fd, int wd);

19.3 inotify Events

下圖為 inotify 事件列表,可以透過 inotify_add_watch() 中的 mask 參數識別要監視的 pathname 事件集合

  • 當權限、所有者、連結計數、擴充屬性、使用者 ID 或是群組 ID 等 metadata 資料改變時,會發生 IN_ATTRIB 事件
  • 若目標監視刪除時,會發生 IN_DELETE_SELF 事件,若受監視是 目錄 且目錄中的其中一個檔案被刪除時,會發生 IN_DELETE 事件
  • 若受監視的物件重新命名時,會發生 IN_MOVE_SELF 事件,若 監視目錄 中的其中一個物件重新命名時,會發生 IN_MOVE_FROMIN_MOVE_TO 事件

19.4 Read inotify Event

使用 read() 進行讀取事件動作

1
2
3
4
5
6
7
8
#define Max_Event	10
#define BUF_LEN (Max_Event * ((sizeof(struct inotify_event) + NAME_MAX + 1)))
int numRead;
int inotifyFd; //From inotify_init()
char buf[BUF_LEN];

/* In function */
numRead = read(inotifyFd, buf, BUF_LEN);

讀取一次的量為各事件發生時的數目
事件發生數目為 numRead / (BUF_LEN/Max_Event)


inotify_add_watch() 的 mask 可以帶入 sys/inotify.h 中的 IN_ALL_EVENT ,定義如下

1
2
3
#define IN_ALL_EVENTS	(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \
IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF)

Demo

  1. add file / directory
  2. modify file
  3. using echo hello > file.txt
  4. rename file
  5. move file to other place
  6. IN_ONESHOT 使用觀察 IN_IGNORED 的狀況
  7. 若監視檔案移動到別的地方了,wd 中的 pathname 會不會更動?

19.5 佇列限制與 /proc 檔案

在 linux kernel configuration 中要啟用 inotify,必須先開啟 menuconfig

1
2
3
4
5
6
7
Symbol: INOTIFY_USER [=y]
Type : boolean Prompt:
Inotify support for userspace
Defined at fs/notify/inotify/Kconfig:1
Location:
-> File systems
Selects: ANON_INODES [=y] && FSNOTIFY [=y]

之後可以在 /proc/sys/fs/inotify/ 中設定 inotify 機制的操作組態,此設定要為高權限使用者

1
2
3
4
5
ls -l /proc/sys/fs/inotify/

-rw-r--r-- 1 root 0 Nov 21 14:58 max_queued_events
-rw-r--r-- 1 root 0 Nov 21 14:58 max_user_instances
-rw-r--r-- 1 root 0 Nov 21 14:58 max_user_watches

  • max_queued_events: default 16384
    • 當呼叫 inotify_init() 時,這個值是設定新的 inotify instance 數目上限,若超過的話會產生 IN_Q_OVERFLOW 事件,wd return -1
  • max_user_instances: default 128
    • 真實使用者可以建立的 inotify instance 數目
  • max_user_watches: default 8192
    • 真實使用者可以建立的監看項目數量限制
  • 以上都可以進行設定

19.6 舊版本的系統監視檔案事件:dnotify

從 Linux kernel v2.4 之後支援,之後被 inotify 所取代,因為有許多限制:

  • dnotify 是透過 signal 傳送事件通知,而 inotify 沒有使用訊號
  • dnotify 監視的單位是一個 目錄 ,而 inotify 則可以用來監視檔案或目錄
  • 為了監視目錄,dnotify 需要應用程式開啟那個目錄的 fd ,使用 file descriptor 會有兩個問題
    • 由於忙碌,所以包含那個目錄的檔案系統都不可以被卸載
    • 每個目錄都需要 fd ,所以會消耗大量的檔案描述符,但 inotify 沒有使用,所以可以避免這個問題
  • dnotify 事件資訊沒有 inotify 來得精準