当前位置: 首页 > java >正文

inotify事件驱动机制

前言

刚开始接触这个inotify机制时,是我在写我的云备份项目时,其中有一个文件压缩备份模块,当时需要对一个目录进行监控,压缩的逻辑是:如果目录中的文件它的最后一次修改时间超过我自定义的阈值时,也就是所说的冷文件,就对其进行压缩。

第一次实现时,我采用简单暴力的轮询方式:定期遍历备份目录文件,调用stat获取每个文件的访问时间,如果超过自定义阈值,就压缩并移动到指定目录。这种做法冗余地遍历了目录与所有文件,效率很低。

后来,我优化为基于inotify的事件驱动方案:用inotify监控目录变化,只有在实际有文件发生访问、修改、新建或删除时才更新内存中的哈希表(映射文件名到访问时间)。后台压缩线程只需要遍历哈希表做冷文件检测和压缩。

对比来看,原始版需要频繁轮询目录和每个文件;inotify版则只在真正有变动时才感知和更新缓存,大幅减少了无用遍历和系统调用。

  • V1
    1. 遍历目录,获取文件列表
    2. 依次stat获取访问时间并比较
    3. 超时则压缩
  • V2
    1. inotify事件触发时更新哈希表
    2. 压缩线程定时遍历哈希表处理冷文件

通过 inotify 实现事件驱动监控,不仅提升效率,也让代码更优雅。掌握 inotify,对于今后涉及文件、目录监控的项目十分有价值!

inotify简介

inotify 是 Linux 内核从 2.6.13 版本后引入的一个文件系统事件监控机制。它允许用户空间的程序监控文件或目录的各种变化(如内容修改、新建、删除等),实现事件驱动的高效监控。

inotify 运行于内核空间,内核为每个监控点注册感兴趣的事件(如文件被修改、读、删等)。当这些事件发生时,内核会生成一个事件结构体,并放进 inotify 文件描述符关联的事件队列中。

用户程序通过 inotify 的系统调用接口(如 inotify_initinotify_add_watch)来创建监控实例和添加监控对象(文件/目录)。程序通常通过 readselectpollepoll 等同步/异步 I/O 方式监听 inotify 文件描述符。只有当被监视对象上发生感兴趣的事件时,read 调用才会返回,传递事件信息。

程序收到事件后,根据事件类型和发生对象等信息,完成后续处理(如重载配置、触发备份、自动同步等功能)。

image-20250508104635427

inotify接口

inotify_init / inotify_init1

创建一个 inotify 实例,返回一个新的文件描述符,用于后续事件监听。

原型:

int inotify_init(void);
int inotify_init1(int flags);

参数:

  • inotify_init 无参数。
  • inotify_init1flags 参数,一般可为 0 或使用以下选项:
    • IN_NONBLOCK :返回的 fd 是非阻塞的,read 若无事件会立刻返回 -1 并设置 errno 为 EAGAIN。
    • IN_CLOEXEC :fork 后自动关闭 fd。

返回值

  • 成功,返回新的文件描述符。
  • 失败,返回 -1,设置 errno。

示例:

int inotify_fd = inotify_init();
if (inotify_fd < 0) {perror("inotify_init");exit(1);
}

inotify_add_watch

为一个 inotify 实例添加一个监视点(watch),用于监听指定文件或目录的事件。

原型:

int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

参数:

  • fd:由 inotify_init 返回的文件描述符。
  • pathname:你要监听的文件或目录的路径。
  • mask:需要监听的事件类型,是一组位掩码。常见的 mask 常量有(可组合使用):
    • IN_ACCESS :文件被访问(读取)
    • IN_MODIFY :文件内容被修改
    • IN_ATTRIB :文件属性被修改
    • IN_CLOSE_WRITE :可写文件被关闭
    • IN_CLOSE_NOWRITE :只读文件被关闭
    • IN_OPEN :文件被打开
    • IN_MOVED_FROM :被移出目录
    • IN_MOVED_TO :被移入目录
    • IN_CREATE :创建新文件或目录
    • IN_DELETE :文件或目录被删除
    • IN_DELETE_SELF :被监控对象自身被删除
    • IN_ALL_EVENTS :所有事件

IN_MOVED_TO 和 IN_CREATE 以及IN_MOVED_FROM 和 IN_DELETE 的区别?

  • IN_CREATE
    当目录中新建了一个文件或子目录时,触发(比如 touch a.txt)。
  • IN_MOVED_TO
    当一个文件或子目录被移动到监控的目录时,触发(比如 mv /tmp/a.txt ./ 或者在同一目录下重命名文件也是一种“移动到”)。

区别:
IN_CREATE 是“新建”,指创建文件本身;
IN_MOVED_TO 是“移入”,指把现有的文件从其他地方搬进来,文件本身其实早就已存在。

  • IN_DELETE
    当目录中的文件或子目录被删除(比如 rm a.txt),触发。
  • IN_MOVED_FROM
    当目录中的文件或子目录被移出到别处时,触发(比如 mv ./a.txt /tmp/)。

区别:
IN_DELETE 是“真删除”,文件内容没了;
IN_MOVED_FROM 是“移出”,文件内容还在,只不过从这个目录转移到别处了(并没有被物理删除)。

示例:

假设当前目录是 /test,监控它:

  • touch b.txt 触发 IN_CREATE
  • mv /tmp/c.txt ./ 触发 IN_MOVED_TO
  • rm b.txt 触发 IN_DELETE
  • mv c.txt /tmp/ 触发 IN_MOVED_FROM

总结

  • “CREATE/DELETE” 表示在该目录中直接创建/删除文件
  • “MOVED_TO/MOVED_FROM” 表示有文件搬进/搬出该目录

返回值:

  • 成功,返回一个 watch 描述符(小整数)。
  • 失败,返回 -1。

示例:

int wd = inotify_add_watch(inotify_fd, "/tmp", IN_CREATE | IN_DELETE | IN_MODIFY);
if (wd < 0) {perror("inotify_add_watch");exit(1);
}

inotify_rm_watch

移除之前注册的监视点,停止监听指定文件或目录。

原型:

int inotify_rm_watch(int fd, int wd);

参数:

  • fd:inotify_init 返回的 fd。
  • wd:inotify_add_watch 返回的监视点描述符。

返回值:

  • 成功,返回 0。
  • 失败,返回 -1。

示例:

inotify_rm_watch(inotify_fd, wd);

读取inotify事件 — read

调用 read 从 inotify fd 读取事件,每次读取获得的内容是一个或多个 struct inotify_event 结构。(在阻塞模式下)

struct inotify_event 结构体
struct inotify_event {int      wd;       // watch 描述符uint32_t mask;     // 事件掩码uint32_t cookie;   // 联动事件编号(如rename)uint32_t len;      // name 数组长度char     name[];   // 文件名(可选,len>0时有效)
};
  • int wd
    监控项(watch descriptor)的描述符。每次你通过 inotify_add_watch 添加一个监控时,系统分配一个唯一的 wd,用来标识这个监控对象。通过它可以知道是哪一个监控点报告了当前事件。
  • uint32_t mask
    事件掩码。用二进制位表示发生了哪些事件,比如 IN_ACCESS(被访问)、IN_MODIFY(被修改)、IN_CREATE(创建新文件)、IN_DELETE(删除文件)等。可以通过与操作判断发生了什么类型的事件。
  • uint32_t cookie
    联动事件编号,主要用于标识那些需要成对出现的事件,比如重命名(rename)文件时,move-from 和 move-to 事件的 cookie 是相同的,可以通过这个字段将相关的两个事件关联起来。
  • uint32_t len
    name 字段的长度。如果对应的事件与某个具体的文件相关,则 name 表示文件名,这时 len 为非零。如果为零则 name 字段不存在。
  • char name
    可选字段——文件名,只在 len > 0 时有效。该字段不一定以 ‘\0’ 结尾,长度由 len 指定。用于指出触发事件的具体文件或子目录名。

简单的说就是:inotify_event 结构体告诉你“哪个监控点(wd)”、“发生了什么事件(mask)”、“有无联动事件(cookie)”、“事件关联的文件名(name)”。

示例:

char buffer[4096];
ssize_t length = read(inotify_fd, buffer, sizeof(buffer));
if (length < 0) {perror("read");// 处理错误
}
// 解析 buffer 为一组 struct inotify_event
for (char *ptr = buffer; ptr < buffer + length;) {struct inotify_event *event = (struct inotify_event *) ptr;// 检查 event->mask, event->name 等ptr += sizeof(struct inotify_event) + event->len;
}

注意:

  • 每次 read 可能返回多个事件,要用循环解析。
  • 名称字段仅在返回被监视目录中文件的事件时存在;它标识了被监视目录中的文件名。该文件名以空字符终止,并且可能包含额外的空字符 (‘\0’) 以便后续读取对齐到合适的地址边界。
  • len 字段计算 name 中的所有字节,包括空字符;因此,inotify_event 结构的大小为 sizeof(struct inotify_event) + size。
  • 当传递给 read 的缓冲区太小而无法返回下一个事件的信息时,行为取决于内核版本:在 Linux 2.6.21 之前,read返回 0;自 Linux 2.6.21 起,read 以错误 EINVAL 失败。

小结

inotify 一般流程如下:

  1. 调用 inotify_init/inotify_init1 创建 fd;
  2. 调用 inotify_add_watch 添加你关心的目录或文件和事件;
  3. 不断用 read 从 fd 读取事件,处理文件变更通知;
  4. 不再需要时用 inotify_rm_watch 移除监听点,用 close 关闭 fd。

文件冷热分离

bool RunModule()
{running = true;std::thread listener([this](){EventListener();});listener.detach();while(running){ProcessHotFiles();std::this_thread::sleep_for(std::chrono::seconds(_hot_time));}return true;
}void EventListener()
{constexpr size_t BUF_LEN = 4096;char buffer[BUF_LEN];while (running) {ssize_t len = read(inotify_fd, buffer, BUF_LEN);if (len < 0) {perror("read");break;}// 处理事件std::lock_guard<std::mutex> lock(cache_mutex);for (char *ptr = buffer; ptr < buffer + len;) {struct inotify_event *event = reinterpret_cast<struct inotify_event*>(ptr);if (event->len > 0) {std::string filepath = _back_dir + event->name;if (event->mask & (IN_ACCESS | IN_MODIFY | IN_CREATE)) // 文件新建、被访问或修改,更新时间{last_access_map[filepath] = time(nullptr);} else if (event->mask & IN_DELETE) // 文件删除,移除记录{last_access_map.erase(filepath);}}ptr += sizeof(struct inotify_event) + event->len;}}
}void ProcessHotFiles()
{// 创建本地副本,避免长时间持有锁std::unordered_map<std::string, time_t> local_copy;{std::lock_guard<std::mutex> lock(cache_mutex);local_copy = last_access_map;}// 检查并压缩冷文件time_t now = time(nullptr);for (const auto &[filepath, last_access] : local_copy) {if (now - last_access > _hot_time) {CompressFile(filepath);{std::lock_guard<std::mutex> lock(cache_mutex);last_access_map.erase(filepath);}}}
}

代码原理

代码中创建了一个 inotify 监听线程(listener),一直 read inotify_fd。只要某个文件发生了访问、修改、新建或删除(被配置为监控的事件),就会在 EventListener 中抓到事件。监听到访问/修改/新建事件,代码就通过 last_access_map[filepath] = time(nullptr);记录该文件“最近一次活跃”的时间戳(以文件路径为 key)。监听到删除事件,直接删掉它的活跃时间记录(因为文件都没了)。

主线程每隔 _hot_time 秒(假设为 60s)调用 ProcessHotFiles(即热文件巡检)。

ProcessHotFiles 拿到本地副本 last_access_map 的数据,遍历每个文件:

  • 如果离现在的时间差大于 _hot_time
  • 说明文件很久没动过:是“冷文件”
  • 此时执行 CompressFile,把文件压缩、归档、删除,并更新持久化数据。

“冷热分离”原理

活跃(热)文件:刚被访问/修改/新建过,时间戳是最近,不会被归档。

冷文件:长时间没动,逐渐积攒后,被系统发现并压缩、归档、腾空间。

每当热文件“被访问/修改/新建”,就又刷新了“上次活跃时间”,从而一直保持活跃状态,不会被当作冷文件处理。

小结

  • 热冷界限由 _hot_time 控制(比如 10 分钟没动就被当冷文件处理)。
  • inotify 实现高效活跃性监控,只在有事件时才更新时间,不需频繁扫描全盘。
  • 冷热算法简单高效,很适合归档、备份、自动清理等用途。

这个方案用 inotify 实时感知文件活跃状态,并定期检查,把长时间未活跃的文件自动归档压缩,实现了纯事件驱动的“文件冷热分离”机制。
活跃(热)文件:刚被访问/修改/新建过,时间戳是最近,不会被归档。

冷文件:长时间没动,逐渐积攒后,被系统发现并压缩、归档、腾空间。

每当热文件“被访问/修改/新建”,就又刷新了“上次活跃时间”,从而一直保持活跃状态,不会被当作冷文件处理。

小结

  • 热冷界限由 _hot_time 控制(比如 10 分钟没动就被当冷文件处理)。
  • inotify 实现高效活跃性监控,只在有事件时才更新时间,不需频繁扫描全盘。
  • 冷热算法简单高效,很适合归档、备份、自动清理等用途。

这个方案用 inotify 实时感知文件活跃状态,并定期检查,把长时间未活跃的文件自动归档压缩,实现了纯事件驱动的“文件冷热分离”机制。

http://www.xdnf.cn/news/4709.html

相关文章:

  • 【5G通信】bwp和redcap 随手记 2
  • LabVIEW超声波液位计检定
  • 长事务:数据库中的“隐形炸弹“——金仓数据库运维避坑指南
  • CSS:字体和文本样式
  • Ubuntu上安装MySQL 8并配置Navicat远程连接
  • docker操作镜像-以mysql为例
  • 数据结构和算法
  • AI技术视角:美联储信号与黄金动态的量化研究——基于多模态数据分析框架
  • 滚珠导轨:电子制造领域精密运动的核心支撑
  • Spark缓存--persist方法
  • C++使用PoDoFo库处理PDF文件
  • 计算机网络
  • 【Java ee初阶】初始网络
  • 无实体对话式社交机器人 拟人化印象形成机制:基于多模态交互与文化适配的拓展研究
  • Sui Basecamp 2025 全栈出击
  • 前端-什么是结构语言、样式语言、脚本语言?
  • 【金仓数据库征文】金仓数据库 KES 助力企业数据库迁移的实践路径
  • 学习黑客什么是 ARP
  • Kafka消息队列之 【消费者分组】 详解
  • Windows系统下使用Kafka和Zookeeper,Python运行kafka(二)
  • 量子密码的轻量级通信协议笔记
  • viewDesign里的table内嵌套select动态添加表格行绑定内容丢失
  • DeFi开发系统软件开发:技术架构与生态重构
  • MariaDB 与 MySQL 的关系:从同源到分道扬镳
  • 单体架构实现延时任务
  • WPF实时调试的一种实现方法
  • 聊一聊接口的压力测试如何进行的?
  • 多商户进销存一体化管理,Java+Vue,含源码与文档,高效统筹库存、销售与采购,适配多元商业场景
  • 2.4 点云数据存储格式——轻量文本型存储格式
  • 在一台服务器上通过 Nginx 配置实现不同子域名访问静态文件和后端服务