Linux系统编程:监视文件系统变化(inotify)
inotify简介
你是否有过这样的需求,监控某个文件的修改情况,一旦某个文件的内容被修改你的进程就能观察到,对准对情况进行文件备份之类的操作。在Linux 环境下,内核提供了inotify API 用于监控文件系统的情况,可以用于监视文件的创建,修改,删除等事件。
inotify用于当内核发生文件系统相关的某种事件后,用于通知用户空间,方便用户做出具体的操作。inotify 可以监控单个的文件,也可以监控整个目录,当一个目录被监控时,inotify会返回关于该目录本身及其内部文件的事件。
inotify接口
inotify 接口的使用十分的简单,调用时一般需要引用头文件 <sys/inotify.h> ,其常用的接口就三种,下面我们来介绍一下各个接口的使用。
inotify_init()
接口inotify_init 的函数签名如下:
int inotify_init();
这个接口用于创建一个inotify 实例,并返回一个文件描述符指向这个实例的事件队列。inotify API还提供一个类似的借口用于创建inotify,即inotify_init1() ,可以格外接受一个参数用于提供格外的机制,这里不展开。
inotify_add_watch
函数签名如下:
int inotify_add_watch(int fd, const char* pathname, uint32_t mask);
这个接口用于为一个已经初始化后的inotify 实例创建一个新的监视项,或者修改一个已经存在的监视项。接口的参数的含义如下:
- fd: inotify 实例对应的文件描述符,即inotify_init() 返回的结果。
- pathname:监视的文件或者目录的路径
- mask:用来指定监视的事件类型
其中可以设置的事件如下:
IN_ACCESS 文件被访问
IN_ATTRIB 文件元数据改变
IN_CLOSE_WRITE 关闭为了写入而打开的文件
IN_CLOSE_NOWRITE 关闭只读方式打开的文件
IN_CREATE 在监听目录内创建了文件/目录
IN_DELETE 在监听目录内删除文件/目录
IN_DELETE_SELF 监听目录/文件本身被删除。
IN_MODIFY 文件被修改
IN_MOVE_SELF 受监控目录/文件本身被移动
IN_MOVED 文件被移
IN_OPEN 文件被打开
IN_ALL_EVENTS 以上所有输出事件的统称
成功的inotify_add_watch 调用会为inotify 实例和 文件系统对象创建一个唯一的 监控描述符。如果该文件系统没有被该 inotify 实例监视过,那么该监控描述符就是新分配的,否则会返回已经存在的监控描述符。
inotify_rm_watch()
函数原型如下:
int inotify_rm_watch(int fd, int wd);
参数的含义如下:
- fd: inotify 实例的文件描述符。
- wd: 一个监控项的监控描述符,即inotify_add_watch 的返回值。
该接口用于从一个inotify 实例中移除一个已经存在的监控项。
读取事件
inotify 并没有提供格外的借口来提供对变动事件的获取,而是通过通用的read 来读取,当没有触发任何事件时,默认情况下调用read 会阻塞线程。read 的函数签名如下:
int read(int fd, void* event, size_t len);
参数的介绍如下:
- fd: inotify 实例的文件描述符
- event: 用于存放事件变动的缓冲区
- len: 表示缓冲区的大小
event参数用于存放变动事件,一般强制转换为struct inotify_event 结构体数组来使用,这里转换为数组是因为read一次调用时,可能同时得到多个事件的响应,如果你觉得不能理解,可以查看后面的示例之后再来体会。
数据结构
struct inotify_event 结构体的定义如下:
struct inotify_event {
int wd; // 被监控文件或目录的描述符(由inotify_add_watch)
uint32_t mask; // 变动的事件
uint32_t cookie; // 比较少使用,可以忽略
uint32_t len; // name的长度
char name[]; // 用于存放发生变动的文件或目录名称
};
示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <sys/inotify.h>
#define MAX_EVENTS 10
#define EVENT_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1)
#define BUFFER_SIZE (1024 * (EVENT_SIZE))
void print_event(int wd, struct inotify_event *event) {
if(event->mask & IN_CREATE)
{
printf("create file: %s\n",event->name);
}
else if(event->mask & IN_MODIFY)
{
printf("modify file: %s\n",event->name);
}
else {
printf("delete file: %s\n",event->name);
}
}
int main() {
int fd, i;
unsigned long events;
char buffer[BUFFER_SIZE];
struct inotify_event *event;
// 创建 inotify 实例
fd = inotify_init();
if (fd < 0) {
perror("ERROR: inotify_init failed");
return EXIT_FAILURE;
}
// 添加需要监控的路径
int wd = inotify_add_watch(fd, "./", IN_CREATE | IN_DELETE | IN_MODIFY);
if (wd < 0) {
perror("ERROR: inotify_add_watch failed");
close(fd);
return EXIT_FAILURE;
}
// 读取事件
for (;;) {
events = read(fd, buffer, BUFFER_SIZE);
if (events < 0) {
perror("ERROR: read failed");
break;
}
// 遍历触发的事件列表
for (i = 0; i < events; ) {
printf("%d: ", i);
event = (struct inotify_event *) &buffer[i];
print_event(wd, event);
i += EVENT_SIZE + event->len;
}
}
// 清理
inotify_rm_watch(fd, wd);
close(fd);
return 0;
}
注意事项
当你希望使用 inotify 来监视某一个文件的创建和删除时,因为inotify 无法监视一个不存在的文件,你应该使用监视文件所在目录的方式来监听。
inofity 的句柄文件描述符和普通的文件描述符一样,你可以使用epoll 来监听inotify 句柄的读事件来高效的响应文件的变动事件,这里不做演示。