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

重要注意事项 見出しへのリンク

  1. 内核版本: 需要 Linux 2.6.13+ 内核支持
  2. 系统限制: 受 /proc/sys/fs/inotify/ 参数限制
  3. 资源管理: 及时清理监控描述符
  4. 性能考虑: 避免监控过多文件/目录
  5. 事件丢失: 高负载下可能丢失事件

实际应用场景 見出しへのリンク

  1. 文件同步: 实时同步文件变化
  2. 日志监控: 监控日志文件变化
  3. 备份系统: 监控文件变化触发备份
  4. 安全审计: 监控关键文件的访问和修改
  5. 开发工具: 自动重新编译/重启服务
  6. 系统监控: 监控系统配置文件变化

最佳实践 見出しへのリンク

// 合理设置 inotify 限制
echo 1048576 > /proc/sys/fs/inotify/max_user_watches

// 使用 inotify_init1 带标志
int fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);

// 及时处理事件避免缓冲区溢出
// 合理选择监控事件类型避免不必要的通知
// 正确处理信号和错误情况

这些示例展示了 inotify 函数的各种使用方法,从基础的文件监控到完整的监控工具,帮助你全面掌握 Linux 系统中的文件系统事件监控机制。