inotify 函数详解 链接到标题
1. 函数介绍 链接到标题
inotify
是 Linux 系统中用于文件系统事件监控的机制。可以把 inotify 想象成"文件系统的监控摄像头"——它可以实时监控文件和目录的变化,当发生特定事件(如文件创建、修改、删除等)时,立即通知应用程序。
在传统的文件监控方法中,程序需要不断地轮询检查文件状态,这既浪费系统资源又不够及时。而 inotify 采用事件驱动的方式,只有当文件系统发生变化时才通知应用程序,效率更高,响应更及时。
2. 函数原型 链接到标题
inotify 相关的函数包括:
#include <sys/inotify.h>
int inotify_init(void); // 初始化 inotify 实例
int inotify_init1(int flags); // 带标志的初始化
int inotify_add_watch(int fd, const char *pathname, uint32_t mask); // 添加监控
int inotify_rm_watch(int fd, int wd); // 移除监控
3. 功能 链接到标题
inotify 机制提供了一种高效的方式来监控文件系统事件:
- 监控文件和目录的变化
- 实时获取文件系统事件通知
- 支持多种事件类型
- 可以同时监控多个文件和目录
4. 相关函数详解 链接到标题
inotify_init() 链接到标题
创建一个新的 inotify 实例,返回文件描述符。
inotify_init1(int flags) 链接到标题
创建新的 inotify 实例,支持额外标志:
IN_NONBLOCK
: 非阻塞模式IN_CLOEXEC
: 执行时关闭标志
inotify_add_watch(int fd, const char *pathname, uint32_t mask) 链接到标题
为指定路径添加监控,返回监控描述符。
inotify_rm_watch(int fd, int wd) 链接到标题
移除指定的监控。
5. 事件类型(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_MOVE_SELF | 监控对象本身被移动 |
6. 返回值 链接到标题
- inotify_init/inotify_init1: 成功返回文件描述符,失败返回 -1
- inotify_add_watch: 成功返回监控描述符,失败返回 -1
- inotify_rm_watch: 成功返回 0,失败返回 -1
7. 相关结构体 链接到标题
struct inotify_event {
int wd; /* 监控描述符 */
uint32_t mask; /* 事件掩码 */
uint32_t cookie; /* 用于关联事件的 cookie */
uint32_t len; /* name 字段长度 */
char name[]; /* 事件相关的文件名 */
};
8. 示例代码 链接到标题
示例1:基础用法 - 监控单个目录 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
// 事件类型转换为字符串
const char* event_to_string(uint32_t mask) {
if (mask & IN_ACCESS) return "访问";
if (mask & IN_MODIFY) return "修改";
if (mask & IN_ATTRIB) return "属性改变";
if (mask & IN_CLOSE_WRITE) return "可写文件关闭";
if (mask & IN_CLOSE_NOWRITE) return "只读文件关闭";
if (mask & IN_OPEN) return "打开";
if (mask & IN_MOVED_FROM) return "移出";
if (mask & IN_MOVED_TO) return "移入";
if (mask & IN_CREATE) return "创建";
if (mask & IN_DELETE) return "删除";
if (mask & IN_DELETE_SELF) return "自身被删除";
if (mask & IN_MOVE_SELF) return "自身被移动";
return "未知事件";
}
int main(int argc, char *argv[]) {
int fd, wd;
char buffer[BUF_LEN];
ssize_t len;
char *ptr;
if (argc != 2) {
printf("用法: %s <监控目录>\n", argv[0]);
exit(1);
}
printf("=== inotify 基础监控示例 ===\n");
printf("监控目录: %s\n", argv[1]);
printf("按 Ctrl+C 退出\n\n");
// 初始化 inotify
fd = inotify_init();
if (fd == -1) {
perror("inotify_init");
exit(1);
}
// 添加监控
wd = inotify_add_watch(fd, argv[1],
IN_CREATE | IN_DELETE | IN_MODIFY |
IN_MOVED_FROM | IN_MOVED_TO);
if (wd == -1) {
perror("inotify_add_watch");
close(fd);
exit(1);
}
printf("开始监控,等待事件...\n");
// 事件循环
while (1) {
len = read(fd, buffer, BUF_LEN);
if (len == -1) {
if (errno == EINTR) continue; // 被信号中断
perror("read");
break;
}
// 处理事件
for (ptr = buffer; ptr < buffer + len; ) {
struct inotify_event *event = (struct inotify_event *)ptr;
printf("事件: [%s] ", event_to_string(event->mask));
if (event->len) {
printf("文件: %s", event->name);
}
printf("\n");
ptr += EVENT_SIZE + event->len;
}
}
// 清理资源
inotify_rm_watch(fd, wd);
close(fd);
return 0;
}
示例2:监控多个目录和事件类型 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#define MAX_WATCHES 100
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
// 监控信息结构体
struct watch_info {
int wd;
char path[PATH_MAX];
time_t start_time;
};
// 全局监控信息
struct watch_info watches[MAX_WATCHES];
int watch_count = 0;
// 添加监控目录
int add_watch(int fd, const char *path) {
if (watch_count >= MAX_WATCHES) {
fprintf(stderr, "监控数量已达上限\n");
return -1;
}
int wd = inotify_add_watch(fd, path,
IN_ALL_EVENTS); // 监控所有事件
if (wd == -1) {
perror("inotify_add_watch");
return -1;
}
// 记录监控信息
watches[watch_count].wd = wd;
strncpy(watches[watch_count].path, path, PATH_MAX - 1);
watches[watch_count].path[PATH_MAX - 1] = '\0';
watches[watch_count].start_time = time(NULL);
watch_count++;
printf("添加监控: %s (wd: %d)\n", path, wd);
return wd;
}
// 根据监控描述符获取路径
const char* get_watch_path(int wd) {
for (int i = 0; i < watch_count; i++) {
if (watches[i].wd == wd) {
return watches[i].path;
}
}
return "未知路径";
}
// 显示监控统计信息
void show_watch_stats() {
printf("\n=== 监控统计信息 ===\n");
printf("当前监控目录数量: %d\n", watch_count);
printf("监控列表:\n");
for (int i = 0; i < watch_count; i++) {
printf(" %d. %s (wd: %d)\n", i + 1,
watches[i].path, watches[i].wd);
}
printf("\n");
}
// 详细事件处理
void handle_event(struct inotify_event *event) {
time_t now = time(NULL);
char time_str[32];
strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime(&now));
printf("[%s] ", time_str);
printf("目录: %s ", get_watch_path(event->wd));
if (event->mask & IN_ISDIR) {
printf("[目录] ");
} else {
printf("[文件] ");
}
// 事件类型处理
if (event->mask & IN_CREATE) {
printf("创建: %s\n", event->name);
} else if (event->mask & IN_DELETE) {
printf("删除: %s\n", event->name);
} else if (event->mask & IN_MODIFY) {
printf("修改: %s\n", event->name);
} else if (event->mask & IN_MOVED_FROM) {
printf("移出: %s (cookie: %u)\n", event->name, event->cookie);
} else if (event->mask & IN_MOVED_TO) {
printf("移入: %s (cookie: %u)\n", event->name, event->cookie);
} else if (event->mask & IN_ACCESS) {
printf("访问: %s\n", event->name);
} else if (event->mask & IN_OPEN) {
printf("打开: %s\n", event->name);
} else if (event->mask & IN_CLOSE_WRITE) {
printf("写关闭: %s\n", event->name);
} else if (event->mask & IN_CLOSE_NOWRITE) {
printf("读关闭: %s\n", event->name);
} else if (event->mask & IN_ATTRIB) {
printf("属性改变: %s\n", event->name);
} else if (event->mask & IN_DELETE_SELF) {
printf("自身被删除\n");
} else if (event->mask & IN_MOVE_SELF) {
printf("自身被移动\n");
} else {
printf("未知事件 (mask: 0x%x)\n", event->mask);
}
}
int main(int argc, char *argv[]) {
int fd;
char buffer[BUF_LEN];
ssize_t len;
char *ptr;
if (argc < 2) {
printf("用法: %s <监控目录1> [监控目录2] ...\n", argv[0]);
exit(1);
}
printf("=== inotify 多目录监控示例 ===\n");
// 初始化 inotify
fd = inotify_init1(IN_NONBLOCK); // 非阻塞模式
if (fd == -1) {
perror("inotify_init1");
exit(1);
}
// 添加所有指定的监控目录
for (int i = 1; i < argc; i++) {
if (add_watch(fd, argv[i]) == -1) {
fprintf(stderr, "添加监控失败: %s\n", argv[i]);
}
}
if (watch_count == 0) {
fprintf(stderr, "没有成功添加任何监控\n");
close(fd);
exit(1);
}
show_watch_stats();
printf("开始监控,按 Ctrl+C 退出\n\n");
// 事件循环
while (1) {
len = read(fd, buffer, BUF_LEN);
if (len == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 非阻塞模式下没有数据
usleep(100000); // 100ms
continue;
} else if (errno == EINTR) {
// 被信号中断
continue;
} else {
perror("read");
break;
}
}
if (len == 0) {
usleep(100000); // 100ms
continue;
}
// 处理事件
for (ptr = buffer; ptr < buffer + len; ) {
struct inotify_event *event = (struct inotify_event *)ptr;
handle_event(event);
ptr += EVENT_SIZE + event->len;
}
}
// 清理资源
for (int i = 0; i < watch_count; i++) {
inotify_rm_watch(fd, watches[i].wd);
}
close(fd);
return 0;
}
示例3:完整的文件系统监控工具 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <getopt.h>
#include <signal.h>
#include <fcntl.h>
#define MAX_WATCHES 1000
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
// 程序状态
volatile sig_atomic_t running = 1;
// 事件统计
struct event_stats {
long total_events;
long create_events;
long delete_events;
long modify_events;
long access_events;
long move_events;
} stats = {0};
// 监控选项
struct monitor_options {
int recursive; // 递归监控子目录
int verbose; // 详细输出
int quiet; // 静默模式
int log_to_file; // 记录到文件
char *log_filename; // 日志文件名
FILE *log_file; // 日志文件指针
};
// 监控信息
struct watch_entry {
int wd;
char *path;
int is_dir;
};
struct watch_entry watches[MAX_WATCHES];
int watch_count = 0;
struct monitor_options options = {0};
// 信号处理函数
void signal_handler(int sig) {
running = 0;
printf("\n收到信号 %d,准备退出...\n", sig);
}
// 初始化信号处理
void setup_signal_handlers() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL); // Ctrl+C
sigaction(SIGTERM, &sa, NULL); // 终止信号
}
// 递归添加目录监控
int add_watch_recursive(int fd, const char *path) {
struct stat st;
if (stat(path, &st) == -1) {
perror("stat");
return -1;
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "不是目录: %s\n", path);
return -1;
}
// 添加当前目录监控
int wd = inotify_add_watch(fd, path, IN_ALL_EVENTS);
if (wd == -1) {
perror("inotify_add_watch");
return -1;
}
if (watch_count < MAX_WATCHES) {
watches[watch_count].wd = wd;
watches[watch_count].path = strdup(path);
watches[watch_count].is_dir = 1;
watch_count++;
if (!options.quiet) {
printf("监控目录: %s (wd: %d)\n", path, wd);
}
}
// 如果启用递归监控,需要扫描子目录
// 这里简化处理,实际应用中需要使用 opendir/readdir
return wd;
}
// 记录事件到日志
void log_event(const char *message) {
time_t now = time(NULL);
char time_str[32];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&now));
if (options.log_to_file && options.log_file) {
fprintf(options.log_file, "[%s] %s\n", time_str, message);
fflush(options.log_file);
}
if (!options.quiet) {
printf("[%s] %s\n", time_str, message);
}
}
// 处理事件
void process_event(struct inotify_event *event) {
char message[1024];
char time_str[32];
time_t now = time(NULL);
strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime(&now));
stats.total_events++;
// 构造事件消息
if (event->mask & IN_CREATE) {
snprintf(message, sizeof(message),
"[%s] 创建: %s/%s", time_str,
"目录", event->name);
stats.create_events++;
} else if (event->mask & IN_DELETE) {
snprintf(message, sizeof(message),
"[%s] 删除: %s/%s", time_str,
(event->mask & IN_ISDIR) ? "目录" : "文件", event->name);
stats.delete_events++;
} else if (event->mask & IN_MODIFY) {
snprintf(message, sizeof(message),
"[%s] 修改: %s/%s", time_str,
(event->mask & IN_ISDIR) ? "目录" : "文件", event->name);
stats.modify_events++;
} else if (event->mask & IN_ACCESS) {
snprintf(message, sizeof(message),
"[%s] 访问: %s/%s", time_str,
(event->mask & IN_ISDIR) ? "目录" : "文件", event->name);
stats.access_events++;
} else if (event->mask & IN_MOVED_FROM || event->mask & IN_MOVED_TO) {
snprintf(message, sizeof(message),
"[%s] 移动: %s/%s (cookie: %u)", time_str,
(event->mask & IN_ISDIR) ? "目录" : "文件", event->name, event->cookie);
stats.move_events++;
} else {
snprintf(message, sizeof(message),
"[%s] 其他事件: mask=0x%x", time_str, event->mask);
}
log_event(message);
}
// 显示统计信息
void show_statistics() {
printf("\n=== 监控统计信息 ===\n");
printf("运行时间: %s", ctime(&time(NULL)));
printf("总事件数: %ld\n", stats.total_events);
printf("创建事件: %ld\n", stats.create_events);
printf("删除事件: %ld\n", stats.delete_events);
printf("修改事件: %ld\n", stats.modify_events);
printf("访问事件: %ld\n", stats.access_events);
printf("移动事件: %ld\n", stats.move_events);
printf("监控目录数: %d\n", watch_count);
}
// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s [选项] <监控目录>...\n", program_name);
printf("\n选项:\n");
printf(" -r, --recursive 递归监控子目录\n");
printf(" -v, --verbose 详细输出\n");
printf(" -q, --quiet 静默模式\n");
printf(" -l, --log=FILE 记录到指定日志文件\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n说明:\n");
printf(" 监控指定目录的文件系统事件\n");
printf(" 按 Ctrl+C 退出程序\n");
}
int main(int argc, char *argv[]) {
int fd;
char buffer[BUF_LEN];
ssize_t len;
char *ptr;
// 解析命令行参数
static struct option long_options[] = {
{"recursive", no_argument, 0, 'r'},
{"verbose", no_argument, 0, 'v'},
{"quiet", no_argument, 0, 'q'},
{"log", required_argument, 0, 'l'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int c;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "rvql:h", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'r':
options.recursive = 1;
break;
case 'v':
options.verbose = 1;
break;
case 'q':
options.quiet = 1;
break;
case 'l':
options.log_to_file = 1;
options.log_filename = optarg;
break;
case 'h':
show_help(argv[0]);
exit(0);
case '?':
exit(1);
}
}
if (optind >= argc) {
fprintf(stderr, "错误: 请指定至少一个监控目录\n");
show_help(argv[0]);
exit(1);
}
// 设置信号处理
setup_signal_handlers();
// 打开日志文件
if (options.log_to_file) {
options.log_file = fopen(options.log_filename, "a");
if (!options.log_file) {
perror("打开日志文件失败");
exit(1);
}
setbuf(options.log_file, NULL); // 无缓冲
}
// 初始化 inotify
fd = inotify_init1(IN_NONBLOCK);
if (fd == -1) {
perror("inotify_init1");
if (options.log_file) fclose(options.log_file);
exit(1);
}
// 添加监控目录
for (int i = optind; i < argc; i++) {
if (options.recursive) {
add_watch_recursive(fd, argv[i]);
} else {
int wd = inotify_add_watch(fd, argv[i], IN_ALL_EVENTS);
if (wd != -1 && watch_count < MAX_WATCHES) {
watches[watch_count].wd = wd;
watches[watch_count].path = strdup(argv[i]);
watches[watch_count].is_dir = 1;
watch_count++;
if (!options.quiet) {
printf("监控目录: %s (wd: %d)\n", argv[i], wd);
}
}
}
}
if (watch_count == 0) {
fprintf(stderr, "没有成功添加任何监控\n");
close(fd);
if (options.log_file) fclose(options.log_file);
exit(1);
}
if (!options.quiet) {
printf("=== 文件系统监控工具启动 ===\n");
printf("监控目录数量: %d\n", watch_count);
printf("递归监控: %s\n", options.recursive ? "是" : "否");
printf("详细输出: %s\n", options.verbose ? "是" : "否");
if (options.log_to_file) {
printf("日志文件: %s\n", options.log_filename);
}
printf("按 Ctrl+C 退出\n\n");
}
// 事件循环
while (running) {
len = read(fd, buffer, BUF_LEN);
if (len == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
usleep(100000); // 100ms
continue;
} else if (errno == EINTR) {
continue;
} else {
if (!options.quiet) {
perror("read");
}
break;
}
}
if (len > 0) {
for (ptr = buffer; ptr < buffer + len; ) {
struct inotify_event *event = (struct inotify_event *)ptr;
process_event(event);
ptr += EVENT_SIZE + event->len;
}
} else {
usleep(100000); // 100ms
}
}
// 清理资源
if (!options.quiet) {
printf("正在清理资源...\n");
}
for (int i = 0; i < watch_count; i++) {
inotify_rm_watch(fd, watches[i].wd);
free(watches[i].path);
}
close(fd);
if (options.log_file) {
fclose(options.log_file);
}
show_statistics();
printf("监控工具已退出\n");
return 0;
}
编译和运行说明 链接到标题
# 编译示例程序
gcc -o inotify_example1 example1.c
gcc -o inotify_example2 example2.c
gcc -o inotify_example3 example3.c
# 运行示例
./inotify_example1 /tmp
./inotify_example2 /tmp /home /etc
./inotify_example3 --help
./inotify_example3 -r -l monitor.log /tmp
系统要求检查 链接到标题
# 检查内核版本(需要 2.6.13+)
uname -r
# 检查 inotify 支持
grep inotify /boot/config-$(uname -r)
# 查看 inotify 限制
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances
重要注意事项 链接到标题
- 内核版本: 需要 Linux 2.6.13+ 内核支持
- 系统限制: 受
/proc/sys/fs/inotify/
参数限制 - 资源管理: 及时清理监控描述符
- 性能考虑: 避免监控过多文件/目录
- 事件丢失: 高负载下可能丢失事件
实际应用场景 链接到标题
- 文件同步: 实时同步文件变化
- 日志监控: 监控日志文件变化
- 备份系统: 监控文件变化触发备份
- 安全审计: 监控关键文件的访问和修改
- 开发工具: 自动重新编译/重启服务
- 系统监控: 监控系统配置文件变化
最佳实践 链接到标题
// 合理设置 inotify 限制
echo 1048576 > /proc/sys/fs/inotify/max_user_watches
// 使用 inotify_init1 带标志
int fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
// 及时处理事件避免缓冲区溢出
// 合理选择监控事件类型避免不必要的通知
// 正确处理信号和错误情况
这些示例展示了 inotify 函数的各种使用方法,从基础的文件监控到完整的监控工具,帮助你全面掌握 Linux 系统中的文件系统事件监控机制。