epoll 是 Linux 内核提供的一种高效的 I/O 多路复用机制,相比于传统的 select 和 poll,具有更高的性能和灵活性。epoll 的核心接口主要包括以下几个:
- epoll_create/epoll_create1
- epoll_ctl
- epoll_wait
下面是对这些接口的详细解释:
1. epoll_create / epoll_create1
epoll_create
int epoll_create(int size);
epoll_create 创建一个新的 epoll 实例,并返回一个文件描述符用于后续操作。参数 size 并不影响 epoll 实例的性能,只是为了向后兼容旧的内核版本。
epoll_create1
int epoll_create1(int flags);
epoll_create1 是 epoll_create 的改进版,允许通过 flags 参数设置额外的选项,比如 EPOLL_CLOEXEC,在执行 exec 系列函数时自动关闭该文件描述符。
示例:
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
2. epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl 函数用于控制 epoll 实例中的文件描述符。它的参数说明如下:
epfd:由epoll_create或epoll_create1返回的 epoll 实例的文件描述符。op:要执行的操作,主要有以下几种:EPOLL_CTL_ADD:将文件描述符fd添加到 epoll 实例中。EPOLL_CTL_MOD:修改已经在 epoll 实例中的文件描述符fd的监听事件。EPOLL_CTL_DEL:从 epoll 实例中删除文件描述符fd。
fd:要操作的目标文件描述符。event:指向epoll_event结构体的指针,用于指定事件类型和关联的数据。
示例:
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = listen_sock;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &event) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
3. epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_wait 用于等待事件的发生。参数说明如下:
epfd:epoll 实例的文件描述符。events:指向epoll_event结构体数组的指针,用于返回发生的事件。maxevents:events数组的大小,即一次最多处理的事件数。timeout:等待事件发生的超时时间,以毫秒为单位。如果设置为 -1,则表示无限等待。
示例:
struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int n = 0; n < nfds; ++n) {
if (events[n].events & EPOLLIN) {
handle_incoming_data(events[n].data.fd);
}
}
epoll_event 结构体
在上面的接口中,我们多次提到 epoll_event 结构体,它定义了 epoll 事件的类型和关联的数据。
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
其中,events 是一个位掩码,可以是以下值的组合:
EPOLLIN:表示对应的文件描述符可以读。EPOLLOUT:表示对应的文件描述符可以写。EPOLLRDHUP:表示对端关闭连接或半关闭连接。EPOLLPRI:表示对应的文件描述符有紧急数据可读。EPOLLERR:表示对应的文件描述符发生错误。EPOLLHUP:表示对应的文件描述符被挂起。EPOLLET:表示将文件描述符设置为边缘触发模式。EPOLLONESHOT:表示一次事件后自动将文件描述符从 epoll 实例中移除。
data 是一个联合体,可以存储用户自定义的数据,如文件描述符、指针等。
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
epoll 的优点
- 高效性:与 select 和 poll 不同,epoll 在监听大量文件描述符时不会随文件描述符数量增加而线性增长,因此更适合大规模并发。
- 灵活性:epoll 支持边缘触发和水平触发两种模式,可以根据需求选择合适的模式。
- 用户态与内核态交互减少:epoll 通过事件通知机制,减少了用户态与内核态的交互次数,从而提升了性能。
小结
epoll 提供了一种高效的 I/O 多路复用机制,特别适用于处理大规模并发连接。通过 epoll_create 创建 epoll 实例,使用 epoll_ctl 添加、修改或删除文件描述符,然后通过 epoll_wait 等待事件发生,可以构建高性能的网络服务器或其他 I/O 密集型应用。了解和使用 epoll 的这些接口,能够显著提升系统的 I/O 处理能力和整体性能。