epoll 是一个 Linux 系统调用,用于监控多个文件描述符,查看是否可以在其中任何一个文件描述符上进行 I/O 操作。它常用于网络编程,用于构建可扩展的高效服务器。
epoll API 的核心概念是 epoll 实例,它是一种内核数据结构,从用户空间的角度来看,可以将其视为两个列表的容器:
①. The interest list(兴趣列表): 进程已注册要监控的文件描述符集;
②. The ready list(就绪列表):"就绪 "可进行 I/O 的文件描述符集合。ready list是“兴趣列表”中文件描述符的子集(或者更准确地说,是一组引用)。内核会根据这些文件描述符上的 I/O 活动动态填充“就绪列表”。
以下系统调用可用于创建和管理 epoll 实例:
1. epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);
epoll_create() 创建一个新的 epoll 实例。自 Linux 2.6.8 起,size 参数将被忽略,但必须大于 0;
epoll_create() 返回一个指向新 epoll 实例的文件描述符。该文件描述符将用于随后对 epoll 接口的所有调用。不再需要时,应使用 close() 关闭 epoll_create() 返回的文件描述符。当引用 epoll 实例的所有文件描述符都被关闭后,内核将销毁该实例,并释放相关资源以供重用。
epoll_create1() 如果 flags 为 0,则除了放弃过时的 size 参数外,epoll_create1() 与 epoll_create() 相同。可以在 flags 中加入以下值,以获得不同的行为:EPOLL_CLOEXEC;
EPOLL_CLOEXEC:设置新文件描述符的执行时关闭 (FD_CLOEXEC) 标志。
2. epoll_ctl
通过 epoll_ctl()注册特定文件描述符,将其添加到 epoll 实例的interest list中。
#include <sys/epoll.h> #include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *_Nullable event);
用于添加、修改或删除文件描述符 epfd 所映射的epoll实例的“兴趣列表”。它要求对目标文件描述符 fd 执行操作 op。
op参数的有效值为:
①. EPOLL_CTL_ADD:
EPOLL_CTL_ADD 在 epoll 文件描述符 epfd 的“兴趣列表”中添加一个条目。该条目包括文件描述符 fd、相应的打开文件描述的引用,以及 event 中指定的设置。
②EPOLL_CTL_MOD:
EPOLL_CTL_MOD 将“兴趣列表”中与 fd 相关的设置更改为事件中指定的新设置。
③EPOLL_CTL_DEL:
EPOLL_CTL_DEL 从“兴趣列表”中删除目标文件描述符 fd。事件参数将被忽略,可以为 NULL;
event参数描述了与文件描述符 fd 相链接的对象
#include <sys/epoll.h>
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
};
typedef union epoll_data epoll_data_t;
epoll_event 结构的数据成员指定内核应保存的数据,然后在该文件描述符就绪时(通过 epoll_wait())返回。
epoll_event 结构的events成员是一个位掩码,由 epoll_wait() 返回的零个或多个事件类型和输入标志组成,输入标志会影响其行为,但不会返回。常用的事件类型有: EPOLLIN、EPOLLOUT;
函数返回值:成功时,epoll_ctl() 返回 0;发生错误时,epoll_ctl() 返回-1,并设置 errno 表示错误。
3. epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout, const sigset_t *_Nullable sigmask);
int epoll_pwait2(int epfd, struct epoll_event *events,
int maxevents, const struct timespec *_Nullable timeout,
const sigset_t *_Nullable sigmask);
等待 I/O 事件,如果当前没有可用事件,则阻塞调用线程。(可视为从 epoll 实例的“就绪列表”中获取node)。
epoll_wait() 系统调用会等待文件描述符 epfd 所指向的 epoll实例上的事件。事件指向的缓冲区用于从“就绪列表”中返回关于“兴趣列表”中具有某些可用事件的文件描述符的信息。epoll_wait() 最多会返回 maxevents,maxevents 参数必须大于零。
参数timeout指定了 epoll_wait() 将阻塞的毫秒数。时间是根据 CLOCK_MONOTONIC 时钟计算的。
调用 epoll_wait() 会阻塞,直到出现以下任一情况:
①. 文件描述符传递事件;
②. 调用被信号处理器中断;
③. 超时结束;
需要注意的是,超时间隔将按系统时钟粒度四舍五入,而内核调度延迟意味着阻塞间隔可能会超时一小段时间。指定的超时时间为 -1 会导致 epoll_wait() 无限期阻塞,而指定的超时时间等于零则会导致 epoll_wait() 立即返回,即使没有可用的事件。
返回值:成功时,epoll_wait() 返回已为请求的 I/O 做好准备的文件描述符的数量,如果在请求的超时毫秒内没有文件描述符做好准备,则返回 0。如果失败,epoll_wait() 返回 -1 并设置 errno 表示错误。
4. C++示例程序
#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>
int main() {
// Create an epoll instance
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
std::cerr << "Failed to create epoll instance\n";
return 1;
}
// Create a pipe for demonstration purposes
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
std::cerr << "Failed to create pipe\n";
return 1;
}
// Add the read end of the pipe to the epoll instance
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = pipe_fds[0]; // Add the read end of the pipe
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipe_fds[0], &event) == -1) {
std::cerr << "Failed to add file descriptor to epoll\n";
return 1;
}
// Demonstrate waiting for events
while (true) {
constexpr int max_events = 10;
struct epoll_event events[max_events];
// Wait for events
int num_events = epoll_wait(epoll_fd, events, max_events, -1);
if (num_events == -1) {
std::cerr << "epoll_wait error\n";
return 1;
}
// Handle events
for (int i = 0; i < num_events; ++i) {
if (events[i].events & EPOLLIN) {
// Event is ready for reading
std::cout << "Data is available to read from the pipe\n";
// Read data from the pipe
char buffer[100];
ssize_t bytes_read = read(pipe_fds[0], buffer, sizeof(buffer));
if (bytes_read == -1) {
std::cerr << "Read error\n";
return 1;
}
// Print the read data
std::cout << "Read " << bytes_read << " bytes: " << buffer << "\n";
}
}
}
// Close resources
close(pipe_fds[0]);
close(pipe_fds[1]);
close(epoll_fd);
return 0;
}
该演示创建了一个 epoll 实例,将管道的读取端添加到该实例中,然后进入一个事件循环,使用 epoll_wait 等待事件发生。.当可以从管道中读取数据时,它会读取并打印数据。这是一个简化的示例;在实际应用中,您需要处理更复杂的情况,并更谨慎地管理资源。