好的,我们来深入学习 inotify_rm_watch
系统调用,从 Linux 编程小白的角度出发。
1. 函数介绍 見出しへのリンク
在 Linux 系统中,inotify
是一个强大的文件系统事件监控机制。它允许程序(比如你写的 C 程序)向内核“订阅”对一个或多个文件/目录的特定事件(如文件被创建、修改、删除、移动等)的通知。
使用 inotify
的基本流程是:
- 创建一个 inotify 实例:调用
inotify_init()
或inotify_init1()
,它会返回一个文件描述符 (fd),代表这个 inotify 实例。 - 添加监视:调用
inotify_add_watch()
,告诉内核你想要监视哪个文件/目录 (pathname
) 以及关心哪些事件 (mask
)。这个函数会返回一个监视描述符 (watch descriptor, wd),它是一个整数,唯一标识这次监视。 - 读取事件:当被监视的事件发生时,这些事件会被存储在与 inotify 实例 fd 相关联的内部缓冲区中。你可以像读取普通文件一样,使用
read()
函数从这个 inotify fd 中读取事件。读取到的是struct inotify_event
结构体。 - 移除监视:如果你不再需要监视某个特定的文件/目录,就需要调用
inotify_rm_watch()
来移除这个监视。
inotify_rm_watch
的作用就是:告诉内核,“好了,我不再需要监视这个 wd 代表的文件/目录了,请停止为它生成事件,并释放相关的资源。”
简单来说,inotify_rm_watch
就像是你对内核说:“取消订阅那个文件/目录的通知,我不看了。”
2. 函数原型 見出しへのリンク
#include <sys/inotify.h> // 包含 inotify_rm_watch 函数声明
int inotify_rm_watch(int fd, int wd);
3. 功能 見出しへのリンク
从 inotify 实例 fd
中移除由监视描述符 wd
标识的监视。一旦调用成功,内核将停止为该监视生成事件。
4. 参数 見出しへのリンク
fd
:int
类型。- 一个由
inotify_init()
或inotify_init1()
返回的有效的 inotify 实例文件描述符。
wd
:int
类型。- 一个由
inotify_add_watch()
返回的监视描述符 (watch descriptor)。它唯一标识了 inotify 实例中的一个监视项。
5. 返回值 見出しへのリンク
- 成功: 返回 0。
- 失败: 返回 -1,并设置全局变量
errno
来指示具体的错误原因。
6. 错误码 (errno
)
見出しへのリンク
EBADF
:fd
不是一个有效的文件描述符,或者它不是一个 inotify 实例的文件描述符。EINVAL
:wd
不是fd
所引用的 inotify 实例中的有效监视描述符。
7. 相似函数或关联函数 見出しへのリンク
inotify_init
/inotify_init1
: 创建一个新的 inotify 实例,并返回一个文件描述符。inotify_add_watch
: 向 inotify 实例添加一个监视项,返回一个监视描述符。read
: 用于从 inotify 实例文件描述符中读取发生的事件 (struct inotify_event
)。close
: 关闭 inotify 实例文件描述符。调用close()
会自动移除该实例上的所有监视。
8. 示例代码 見出しへのリンク
下面的示例演示了如何使用 inotify
系列函数,包括 inotify_rm_watch
来添加和移除监视。
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h> // inotify_* 系列函数
#include <limits.h> // 包含 NAME_MAX
#include <string.h>
#include <errno.h>
#include <poll.h> // 包含 poll
#include <sys/stat.h> // 包含 mkdir
#include <fcntl.h> // 包含 open, O_* flags
// 用于 read 的缓冲区大小
// 建议至少能容纳一个 struct inotify_event 加上文件名
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + NAME_MAX + 1))
// 辅助函数:打印 inotify 事件
void print_inotify_event(struct inotify_event *event) {
if (event->mask & IN_OPEN) printf("IN_OPEN: ");
if (event->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE: ");
if (event->mask & IN_CLOSE_WRITE) printf("IN_CLOSE_WRITE: ");
if (event->mask & IN_ACCESS) printf("IN_ACCESS: ");
if (event->mask & IN_MODIFY) printf("IN_MODIFY: ");
if (event->mask & IN_CREATE) printf("IN_CREATE: ");
if (event->mask & IN_DELETE) printf("IN_DELETE: ");
if (event->mask & IN_MOVED_FROM) printf("IN_MOVED_FROM: ");
if (event->mask & IN_MOVED_TO) printf("IN_MOVED_TO: ");
if (event->mask & IN_IGNORED) printf("IN_IGNORED (Watch was removed): ");
if (event->len > 0) {
printf("Name: %s", event->name);
}
printf("\n");
}
int main() {
int inotify_fd, wd1, wd2;
char buffer[BUF_LEN];
ssize_t len;
char *ptr;
struct inotify_event *event;
struct pollfd pfd;
const char *watch_dir1 = "/tmp/inotify_test_dir1";
const char *watch_dir2 = "/tmp/inotify_test_dir2";
printf("--- Demonstrating inotify_rm_watch ---\n");
// 1. 创建测试目录
if (mkdir(watch_dir1, 0755) == -1 && errno != EEXIST) {
perror("mkdir watch_dir1");
} else {
printf("Created/Confirmed directory: %s\n", watch_dir1);
}
if (mkdir(watch_dir2, 0755) == -1 && errno != EEXIST) {
perror("mkdir watch_dir2");
} else {
printf("Created/Confirmed directory: %s\n", watch_dir2);
}
// 2. 初始化 inotify 实例
inotify_fd = inotify_init1(IN_NONBLOCK); // 使用非阻塞模式
if (inotify_fd == -1) {
perror("inotify_init1");
rmdir(watch_dir1);
rmdir(watch_dir2);
exit(EXIT_FAILURE);
}
printf("Initialized inotify instance. fd = %d\n", inotify_fd);
// 3. 添加监视
// 监视目录1的 文件创建、删除、移动
wd1 = inotify_add_watch(inotify_fd, watch_dir1, IN_CREATE | IN_DELETE | IN_MOVE);
if (wd1 == -1) {
perror("inotify_add_watch dir1");
close(inotify_fd);
rmdir(watch_dir1);
rmdir(watch_dir2);
exit(EXIT_FAILURE);
}
printf("Added watch on '%s'. wd = %d\n", watch_dir1, wd1);
// 监视目录2的 文件访问、修改
wd2 = inotify_add_watch(inotify_fd, watch_dir2, IN_ACCESS | IN_MODIFY);
if (wd2 == -1) {
perror("inotify_add_watch dir2");
inotify_rm_watch(inotify_fd, wd1); // 移除已添加的监视
close(inotify_fd);
rmdir(watch_dir1);
rmdir(watch_dir2);
exit(EXIT_FAILURE);
}
printf("Added watch on '%s'. wd = %d\n", watch_dir2, wd2);
// 4. 设置 poll 来等待事件或超时
pfd.fd = inotify_fd;
pfd.events = POLLIN;
printf("\n--- Monitoring started ---\n");
printf("Perform actions in the watched directories:\n");
printf(" In %s: create/delete/move files\n", watch_dir1);
printf(" In %s: read/write files\n", watch_dir2);
printf("The program will run for 30 seconds or until you press Ctrl+C.\n");
printf("After 15 seconds, the watch on %s (wd=%d) will be removed.\n", watch_dir1, wd1);
printf("--------------------------\n");
time_t start_time = time(NULL);
int watch1_removed = 0;
while (1) {
time_t current_time = time(NULL);
if (!watch1_removed && (current_time - start_time) >= 15) {
// 5. 演示 inotify_rm_watch: 移除对目录1的监视
printf("\n--- Time to remove watch %d for '%s' ---\n", wd1, watch_dir1);
if (inotify_rm_watch(inotify_fd, wd1) == 0) {
printf("Successfully removed watch (wd=%d) for '%s'.\n", wd1, watch_dir1);
printf("Events for '%s' should stop appearing now.\n", watch_dir1);
} else {
perror("inotify_rm_watch");
}
watch1_removed = 1;
}
if ((current_time - start_time) >= 30) {
printf("\n--- 30 seconds elapsed, stopping monitor. ---\n");
break;
}
// 6. 使用 poll 等待事件,设置超时以便定期检查时间
int poll_result = poll(&pfd, 1, 1000); // 1秒超时
if (poll_result == -1) {
if (errno == EINTR) {
continue; // 被信号中断,继续循环
} else {
perror("poll");
break;
}
} else if (poll_result == 0) {
// 超时,继续循环检查时间
continue;
}
// 7. 有事件可读
len = read(inotify_fd, buffer, BUF_LEN);
if (len == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 非阻塞模式下没有数据可读 (不太可能,因为 poll 说了有)
continue;
} else {
perror("read");
break;
}
}
if (len == 0) {
// 这通常不应该发生
fprintf(stderr, "read() returned 0 on inotify fd.\n");
break;
}
// 8. 处理所有读取到的事件
for (ptr = buffer; ptr < buffer + len; ) {
event = (struct inotify_event *) ptr;
printf("Event received: ");
print_inotify_event(event);
// 移动到下一个事件
ptr += sizeof(struct inotify_event) + event->len;
}
}
// 9. 清理资源
// 注意:即使没有显式调用 inotify_rm_watch,close 也会移除所有监视
// 但显式调用是一个好习惯
printf("\n--- Cleaning up ---\n");
if (!watch1_removed) {
inotify_rm_watch(inotify_fd, wd1);
printf("Removed watch %d\n", wd1);
}
inotify_rm_watch(inotify_fd, wd2);
printf("Removed watch %d\n", wd2);
close(inotify_fd);
printf("Closed inotify instance (fd=%d)\n", inotify_fd);
// 清理测试目录 (需要先删除目录内文件)
// 为简单起见,这里直接尝试删除目录
rmdir(watch_dir1);
rmdir(watch_dir2);
printf("Removed test directories.\n");
printf("\n--- Summary ---\n");
printf("1. inotify_rm_watch(fd, wd) removes a specific watch from an inotify instance.\n");
printf("2. After removal, no more events will be generated for that watch.\n");
printf("3. It's good practice to remove watches when done to free kernel resources.\n");
printf("4. close(inotify_fd) automatically removes ALL watches for that instance.\n");
return 0;
}
9. 编译和运行 見出しへのリンク
# 假设代码保存在 inotify_rm_watch_example.c 中
gcc -o inotify_rm_watch_example inotify_rm_watch_example.c
# 在一个终端运行程序
./inotify_rm_watch_example
# 在另一个终端或文件管理器中执行以下操作来触发事件
# (在程序开始后的前15秒内)
touch /tmp/inotify_test_dir1/test_file.txt
rm /tmp/inotify_test_dir1/test_file.txt
touch /tmp/inotify_test_dir2/another_file.txt
echo "data" > /tmp/inotify_test_dir2/another_file.txt
cat /tmp/inotify_test_dir2/another_file.txt
# 等待15秒后,再对 /tmp/inotify_test_dir1 执行操作
# 应该不会看到 IN_CREATE/DELETE 等事件了
touch /tmp/inotify_test_dir1/should_not_see_this.txt
# 程序会在30秒后自动停止
10. 预期输出 (片段) 見出しへのリンク
--- Demonstrating inotify_rm_watch ---
Created/Confirmed directory: /tmp/inotify_test_dir1
Created/Confirmed directory: /tmp/inotify_test_dir2
Initialized inotify instance. fd = 3
Added watch on '/tmp/inotify_test_dir1'. wd = 1
Added watch on '/tmp/inotify_test_dir2'. wd = 2
--- Monitoring started ---
Perform actions in the watched directories:
In /tmp/inotify_test_dir1: create/delete/move files
In /tmp/inotify_test_dir2: read/write files
The program will run for 30 seconds or until you press Ctrl+C.
After 15 seconds, the watch on /tmp/inotify_test_dir1 (wd=1) will be removed.
--------------------------
Event received: IN_CREATE: Name: test_file.txt
Event received: IN_DELETE: Name: test_file.txt
Event received: IN_CREATE: Name: another_file.txt
Event received: IN_MODIFY: Name: another_file.txt
Event received: IN_ACCESS: Name: another_file.txt
--- Time to remove watch 1 for '/tmp/inotify_test_dir1' ---
Successfully removed watch (wd=1) for '/tmp/inotify_test_dir1'.
Events for '/tmp/inotify_test_dir1' should stop appearing now.
Event received: IN_MODIFY: Name: another_file.txt
Event received: IN_ACCESS: Name: another_file.txt
--- 30 seconds elapsed, stopping monitor. ---
--- Cleaning up ---
Removed watch 2
Closed inotify instance (fd=3)
Removed test directories.
--- Summary ---
1. inotify_rm_watch(fd, wd) removes a specific watch from an inotify instance.
2. After removal, no more events will be generated for that watch.
3. It's good practice to remove watches when done to free kernel resources.
4. close(inotify_fd) automatically removes ALL watches for that instance.
11. 总结 見出しへのリンク
inotify_rm_watch
是 inotify
文件系统监控机制中的一个重要组成部分。
- 核心作用:移除一个特定的监视项,停止接收其事件。
- 参数:需要提供 inotify 实例的文件描述符 (
fd
) 和要移除的监视描述符 (wd
)。 - 重要性:
- 资源管理:及时移除不需要的监视可以释放内核资源。
- 逻辑清晰:明确地管理监视的生命周期使程序逻辑更清晰。
- 与
close
的关系:调用close(inotify_fd)
会自动移除该实例上的所有监视,但在某些情况下(如长时间运行的服务只移除部分监视),显式调用inotify_rm_watch
是必要的。 - 事件
IN_IGNORED
:当一个监视被成功移除后(无论是通过inotify_rm_watch
还是close
),内核可能会为该监视生成一个IN_IGNORED
事件,通知应用程序该监视已失效。
掌握 inotify_rm_watch
有助于你编写更健壮、资源友好的文件系统监控程序。