博客專欄

EEPW首頁(yè) > 博客 > 監(jiān)聽(tīng)容器中的文件系統(tǒng)事件

監(jiān)聽(tīng)容器中的文件系統(tǒng)事件

發(fā)布人:電子禪石 時(shí)間:2024-09-18 來(lái)源:工程師 發(fā)布文章
基本概念

Linux 文件系統(tǒng)事件監(jiān)聽(tīng):應(yīng)用層的進(jìn)程操作目錄或文件時(shí),會(huì)觸發(fā) system call,此時(shí),內(nèi)核中的 notification 子系統(tǒng)把該進(jìn)程對(duì)文件的操作事件上報(bào)給應(yīng)用層的監(jiān)聽(tīng)進(jìn)程(稱為 listerner)。

dnotify:2001 年的 kernel 2.4 版本引入,只能監(jiān)控 directory,采用的是 signal 機(jī)制來(lái)向 listener 發(fā)送通知,可以傳遞的信息很有限。

inotify:2005 年在 kernel 2.6.13 中亮相,除了可以監(jiān)控目錄,還可以監(jiān)聽(tīng)普通文件,inotify 擯棄了 signal 機(jī)制,通過(guò) event queue 向 listener 上傳事件信息。

fanotify:kernel 2.6.36 引入,fanotify 的出現(xiàn)解決了已有實(shí)現(xiàn)只能 notify 的問(wèn)題,允許 listener 介入并改變文件事件的行為,實(shí)現(xiàn)從“監(jiān)聽(tīng)”到“監(jiān)控”的跨越。

本文主要介紹如何通過(guò) inotify 和 fanotify 監(jiān)聽(tīng)容器中的文件系統(tǒng)事件。

Inotify基本介紹

inotify(inode[1] notify)是 Linux 內(nèi)核中的一個(gè)子系統(tǒng),由 John McCutchan[2] 創(chuàng)建,用于監(jiān)視文件系統(tǒng)事件。它可以在文件或目錄發(fā)生變化時(shí)通知應(yīng)用程序,例如,監(jiān)聽(tīng)文件的創(chuàng)建、修改或刪除事件。inotify 可以用于自動(dòng)更新文件系統(tǒng)視圖、重新加載配置文件,記錄文件改變歷史等場(chǎng)景。

Inotify 的工作流程如下:

  1. 用戶通過(guò)系統(tǒng)調(diào)用(如:write、read)操作文件;

  2. 內(nèi)核將文件系統(tǒng)事件保存到 fsnotify_group 的事件隊(duì)列中;

  3. 喚醒等待 inotify 的進(jìn)程(listener);

  4. 進(jìn)程通過(guò) fd 從內(nèi)核隊(duì)列讀取 inotify 事件。

圖片

其中,inotify_event_info 的定義如下:

c

mask 標(biāo)記具體的文件操作事件。

API 介紹

Inotify 可以用來(lái)監(jiān)聽(tīng)單個(gè)文件,也可以用來(lái)監(jiān)聽(tīng)目錄。當(dāng)監(jiān)聽(tīng)的是目錄時(shí),inotify 除了生成目錄的事件,還會(huì)生成目錄中文件的事件。

注意:當(dāng)使用 inotify 監(jiān)聽(tīng)目錄時(shí),并不會(huì)遞歸監(jiān)聽(tīng)子目錄中的文件,如果需要得到這些事件,需要手動(dòng)指定監(jiān)聽(tīng)這些文件。對(duì)于很大的目錄樹(shù),這個(gè)過(guò)程將花費(fèi)大量時(shí)間。

參考:inotify.7[3]

  • inotify_init(void)

初始化 inotify 實(shí)例,返回文件描述符,用于內(nèi)核向用戶態(tài)程序傳輸監(jiān)聽(tīng)到的 inotify 事件。函數(shù)聲明為:

c

內(nèi)核同時(shí)提供了int inotify_init1(int flags),flags 的可選值如下:

c

可以通過(guò) OR 指定多個(gè)flag,當(dāng)flags=0等價(jià)于int inotify_init(void)。

  • inotify_add_watch

添加需要監(jiān)聽(tīng)的目錄或文件(watch list),可以添加新的路徑,也可以是已經(jīng)添加過(guò)的路徑。fd 是inotify_init返回的文件描述符,mask 指定需要監(jiān)聽(tīng)的事件類型,通過(guò) OR 指定多個(gè)事件。返回值是當(dāng)前路徑的wd(watch descriptor),可用于移除對(duì)該路徑的監(jiān)聽(tīng)。

函數(shù)聲明為:

c

Inotify 支持監(jiān)聽(tīng)的事件包括:

c
  • inotify_rm_watch

移除被監(jiān)聽(tīng)的路徑。fd 是inotify_init返回的文件描述符,wd 是inotify_add_watch返回的監(jiān)聽(tīng)文件描述符。

函數(shù)聲明為:

c
實(shí)例

以下是基于 Rust 語(yǔ)言實(shí)現(xiàn)的實(shí)例:

rust
use nix::{    poll::{poll, PollFd, PollFlags},    sys::inotify::{AddWatchFlags, InitFlags, Inotify, InotifyEvent},};use signal_hook::{consts::SIGTERM, low_level::pipe};use std::os::unix::net::UnixStream;use std::{env, io, os::fd::AsRawFd, path::PathBuf};fn main() -> io::Result<()> {    let args: Vec<String> = env::args().collect();    if args.len() < 2 {        eprintln!("Usage: {} <path>", args[0]);        std::process::exit(1);    }    let path = PathBuf::from(&args[1]);    // 初始化 inotify,得到 fd    let inotify_fd = Inotify::init(InitFlags::empty())?;    // 添加被監(jiān)聽(tīng)的目錄或文件,指定需要監(jiān)聽(tīng)的事件    let wd = inotify_fd.add_watch(        &path,        AddWatchFlags::IN_ACCESS | AddWatchFlags::IN_OPEN | AddWatchFlags::IN_CREATE,    )?;    let (read, write) = UnixStream::pair()?;    // 注冊(cè)用于處理信號(hào)的 pipe    if let Err(e) = pipe::register(SIGTERM, write) {        println!("failed to set SIGTERM signal handler {e:?}");    }    let mut fds = [        PollFd::new(inotify_fd.as_raw_fd(), PollFlags::POLLIN),        PollFd::new(read.as_raw_fd(), PollFlags::POLLIN),    ];    loop {        match poll(&mut fds, -1) {            Ok(polled_num) => {                if polled_num <= 0 {                    eprintln!("polled_num <= 0!");                    break;                }                if let Some(flag) = fds[0].revents() {                    if flag.contains(PollFlags::POLLIN) {                        // 得到 inotify 事件,進(jìn)行處理                        let events = inotify_fd.read_events()?;                        for event in events {                            handle_event(event)?;                        }                    }                }                if let Some(flag) = fds[1].revents() {                    if flag.contains(PollFlags::POLLIN) {                        println!("received SIGTERM signal");                        break;                    }                }            }            Err(e) => {                if e == nix::Error::EINTR {                    continue;                }                eprintln!("Poll error {:?}", e);                break;            }        }    }    inotify_fd.rm_watch(wd)?;    Ok(())}fn handle_event(event: InotifyEvent) -> io::Result<()> {    let file_name = match event.name {        Some(name) => name,        None => return Ok(()),    };    let event_mask = event.mask;    let kind = if event_mask.contains(AddWatchFlags::IN_ISDIR) {        "directory"    } else {        "file"    };    println!(        "{} {} was {:?}.",        kind,        file_name.to_string_lossy(),        event_mask    );    Ok(())}

編譯&測(cè)試:

shell
cargo build./target/debug/inotify test

圖片

可以看到,inotify 不會(huì)遞歸監(jiān)聽(tīng)二級(jí)目錄下的文件dir1/file2.txt。

經(jīng)測(cè)試,Inotify 可以直接監(jiān)聽(tīng)容器 rootfs 下的目錄:

shell
nerdctl run --rm -it golang./target/debug/inotify /run/containerd/io.containerd.runtime.v2.task/default/CONTAINERD_ID/rootfs

圖片

Fanotify基本介紹

Inotify 能夠監(jiān)聽(tīng)目錄和文件的事件,但這種 notifiation 機(jī)制也存在局限:inotify 只能通知用戶態(tài)進(jìn)程觸發(fā)了哪些文件系統(tǒng)事件,而無(wú)法進(jìn)行干預(yù),典型的應(yīng)用場(chǎng)景是殺毒軟件。

Fanotify[4] 的出現(xiàn)就是為了解決這個(gè)問(wèn)題,同時(shí)允許遞歸監(jiān)聽(tīng)目錄下的子目錄和文件。

Fanotify 的工作流程如下:

  1. 用戶通過(guò)系統(tǒng)調(diào)用(如:write、read)操作文件;

  2. 內(nèi)核將文件系統(tǒng)事件發(fā)送到 fsnotify_group 的事件隊(duì)列中;

  3. 喚醒等待 fanotify 事件的進(jìn)程(listener);

  4. 進(jìn)程通過(guò) fd 從內(nèi)核隊(duì)列讀取 fanotify 事件;

  5. 如果是 FAN_OPEN_PERM 和 FAN_ACCESS_PERM 監(jiān)聽(tīng)類型,進(jìn)程需要通過(guò) write 把許可信息(允許 or 拒絕)寫(xiě)回內(nèi)核;

  6. 內(nèi)核根據(jù)許可信息決定是否繼續(xù)完成該文件系統(tǒng)事件。

監(jiān)監(jiān)聽(tīng)容器中的文件系統(tǒng)事件 - abin在路上 - 博客園 (cnblogs.com)

*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。



關(guān)鍵詞: fanotify

技術(shù)專區(qū)

關(guān)閉