1.2.2 kqueue
简介
kqueue是BSD中使用的内核事件通知机制。提供了一种用于一个或者多个进程同步的简单而高效的方法。
其中一个kqueue代表一个描述符,这个描述符会阻塞等待一个特定类型或者种类的事件发生,用户态的进程可以等待这个描述符。
kqueue和对应的kevent构成了内核异步I/O的基础,kevent代表事件的数据结构。其结构如下:
struct kevent {
uintptr_t ident; //与事件相关的描述符,比如文件描述符或目录描述符
int16_t filter;//表示触发事件的过滤器类型,通常为EVFILT_VNODE,用于文件系统事件。
uint16_t flags;//标志位,指示发生的事件类型和状态,比如文件的创建、删除、写入等
uint32_t fflags;//更详细的事件标志,提供了更多关于发生事件类型的信息,比如文件权限、文件大小等
intptr_t data;//可能包含一些额外的数据,具体含义取决于事件类型和标志。
void *udata;//用户数据,通常用于与特定事件关联的自定义数据。
};
如何使用kqueue?
我们可以通过以下步骤使用kqueue
- 创建kqueue对象:使用系统函数kqueue()创建一个'kqueue'对象。这个对象是用来存储事件和对事件进行操作的
- 注册事件: 使用EV_SET宏定义设置指定的事件,即初始化kevent结构体。在调用kevent或者kevent64注册事件
- 监听事件: 在循环中使用kevent()系统调用监听事件,kevent将会阻塞程序,直到有事件发生或超时。
- 处理事件:一旦事件被检测到,可以根据事件类型执行相应的操作
以下是一个简单的示例,展示了如何在 macOS 中使用 kqueue 监控文件系统事件(如文件的读取、写入、删除):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
int main() {
int kq, i;
struct kevent events;
struct timespec timeout = { 1, 0 }; // 设置超时时间为1秒
// 创建 kqueue
kq = kqueue();
if (kq == -1) {
perror("kqueue");
exit(EXIT_FAILURE);
}
// 打开文件
const char *file_path = "/path/to/your/file"; // 修改为你要监控的文件路径
int file_fd = open(file_path, O_RDONLY);
if (file_fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 注册事件
EV_SET(&events, file_fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
NOTE_WRITE | NOTE_DELETE, 0, NULL);
if (kevent(kq, &events, 1, NULL, 0, NULL) == -1) {
perror("kevent");
exit(EXIT_FAILURE);
}
// 监听事件
while (1) {
i = kevent(kq, NULL, 0, &events, 1, &timeout);
if (i == -1) {
perror("kevent");
exit(EXIT_FAILURE);
} else if (i > 0) {
if (events.flags & EV_ERROR) {
fprintf(stderr, "Error in event\n");
continue;
}
if (events.fflags & NOTE_WRITE) {
printf("File was written\n");
}
if (events.fflags & NOTE_DELETE) {
printf("File was deleted\n");
}
}
}
close(kq);
return 0;
}
在设置指定事件的时候,可以设置一些事件过滤器,如果满足过滤器的话则返回,否则阻塞。系统支持一些“预定义”的过滤器,如下表所示:
事件过滤器常量 | 值 | 用途 | |
---|---|---|---|
EVFILT_READ | -1 | 如果监视文件,那么当文件指针没有在EOF时返回。如果监视套接字、管道或者FIFO,那么当有数据可读时就返回 | |
EVFILT_WRITE | -2 | 如果监视文件,那么当文件可以写入时返回 如果监控套接字、管道或FIFO,那么当数据可以写入时返回,在事件数据中返回的缓冲区的可用空间 | |
EVFILT_AIO | -3 | 监视一个或多个异步 I/O 请求的状态变化。异步 I/O 操作允许应用程序发出读取或写入请求,并在后台进行,当完成时会发送通知,而不会阻塞主线程。 | |
EVFILT_VNODE | -4 | 文件(vnode)相关系统调用的过滤器,例如rename,delete,unlink,link | |
EVFILT_PROC | -5 | 监视一个指定PID表示的进程调用execute、exit、fork、wait等,但是这个flag过期了 | |
EVFILT_SIGNAL | -6 | 监视发给一个进程的特性信号,即使这个信号被进程忽略(kill -9 SIGKILL监控不了) | |
EVFILT_TIMER | -7 | 最高能达到纳秒精确度周期定时器 | |
EVFILT_MACHPORT | -8 | 监视一个Mach port或一个port组,如果监视的port接收到一条消息,则返回 | |
EVFILT_FS | -9 | 监视指定路径下的文件系统事件。它允许你捕获文件系统级别的变化,而不需要轮询文件系统状态。常见的事件:NOTE_WRITE,NOTE_DELETE,NOTE_RENAME,NOTE_WRITE | NOTE_ATTRIB |
EVFILT_VM | -12 | 虚拟内存相关的通知,用于处理内存压力 | |
EVFILT_EXCEPT | -15 | 监视文件描述符上发生的异常情况,如带外数据的到达(NOTE_OOB)、套接字错误(NOTE_SOCKERR)等。 |
kqueue的优点
-
高效性:kqueue 允许在单个系统调用中管理大量的事件,处理大规模事件时效率高,避免了传统轮询机制的性能瓶颈。
-
多样化事件支持: 支持多种事件类型的监控,可以监视文件描述符、套接字、信号、定时器等多种事件,提供了灵活的事件监控机制。
-
可扩展性: 对于大量事件和高并发场景表现出良好的可扩展性,能够轻松处理大量的事件。
-
跨平台支持: 虽然最初是在 BSD 系统中引入,但类似的事件通知机制也在其他类 Unix 系统中实现,因此具有跨平台的优势。