linux系统编程 学习笔记 第四章 高级文件I/O(部分)

函数poll和select必须遍历被监视的文件描述符列表,当这个列表很大时,每次调用时的遍历时间成为瓶颈。epoll函数就是为了优化poll和select函数。

创建一个epoll实例:

#include <sys/epoll.h> 
int epoll_create(int size)

成功时,创建了一个epoll实例,返回值为与该实例关联的文件描述符。这个文件描述符与真正的文件没有关系,仅为了后续调用使用epoll而创建。

size参数告诉内核需要监听的文件描述符数,但不是最大值,传递一个适当的近似值可带来性能的提升,但不需要给出确切数字。

函数出错时,返回-1,并将errno设为以下值:
1.EINVAL:size不是正数。
2.ENFILE:系统达到打开文件数的上限。
3.ENOMEN:没有足够内存完成此次操作。

一个标准调用如下:

int epfd;
epfd = epoll_create(100);
if (epfd < 0) {
    perror("epoll_create");
}

函数epoll_create返回的文件描述符需要调用close关闭。

向epoll上下文中加入或删除文件描述符:

#include <sys/epoll.h> 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

其中epoll_event结构体定义在头文件sys/epoll.h头文件中:

struct epoll_event { 
    __u32 events;    /* events */ 
    union { 
        void *ptr; 
        int fd; 
        __u32 u32; 
        __u64 u64; 
    } data; 
}; 

epoll_ctl调用成功将关联由参数epfd指定的epoll实例和由参数fd指定的文件描述符,参数op指定对fd要进行的操作,event参数描述epoll更具体的行为。

参数op的有效值:
1.EPOLL_CTL_ADD:将参数fd添加到参数epfd指定的epoll实例监听集中,监听在参数event中定义的事件。
2.EPOLL_CTL_DEL:把参数fd从参数epfd的监听集中删除。
3.EPOLL_CTL_MOD:使用参数event改变已有fd上的监听行为。

参数event结构体中的events成员列出了在给定文件描述符上的监听事件,多个事件可用位或运算同时指定,以下为有效值:
1.EPOLLERR:文件出错。即使没有设置,此事件也被监听。向已经关闭的socket写时,会发生EPOLLERR。
2.EPOLLET:在监听文件上开启边沿触发。默认为水平触发。
3.EPOLLHUP:文件被挂起。即使没有设置,此事件也被监听。新建一个socket fd后直接加入epoll中,会上报EPOLLHUP。
4.EPOLLIN:文件可读。
5.EPOLLONESHOT:产生一次事件并被处理后,文件就不再被监听,之后必须用EPOLL_CTL_MOD指定新事件,以便重新监听文件。
6.EPOLLOUT:文件可写。
7.EPOLLPRI:高优先级的带外数据可读。

event参数结构体的data成员由用户使用,确定监听事件后,data成员被返回给用户,通常将event.data.fd设为fd,这样就知道哪个文件描述符触发了事件。

epoll_ctl函数成功时返回0,失败时返回-1,并将errno置为:
1.EBADF:参数epfd不是一个有效epoll实例,或参数fd不是一个有效文件描述符。
2.EEXIST:参数op为EPOLL_CTL_ADD时,但参数fd已与参数epfd关联。
3.EINVAL:参数epfd不是一个epoll实例、参数epfd和参数fd相同、参数op无效。
4.ENOENT:参数op是EPOLL_CTL_MOD,或EPOLL_CTL_DEL,但参数fd没有与参数epfd相关联。
5.ENOMEM:内存不足。
6.EPERM:参数fd不支持epoll。

向参数epfd实例中加入一个参数fd指定的监听文件:

struct epoll_event event;
int ret;
event.data.fd = fd;    // return the fd to us later
event.events = EPOLLIN | EPOLLOUT;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if (ret) {
    perror("epoll_ctl");
}

修改参数epfd实例中一个参数fd上的监听事件:

struct epoll_event event;
int ret;
event.data.fd = fd;  
event.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
if (ret) {
    perror("epoll_ctl");
}

从epfd中移除一个监听的fd:

struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);    // 2.9.6之后的内核中,此处的event参数可为NULL,这样写是为了兼容性,该指针不会被访问
if (ret) {
    perror("epoll_ctl");
}

等待给定epoll实例关联的文件描述符上的事件:

#include <sys/epoll.h> 
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 

参数timeout为等待的ms数。成功返回时,参数events指向包含多个epoll_event结构体的内存,最多可有maxevents个。返回值是事件数,出错返回-1,并将errno置为以下值:
1.EBADF:参数epfd是无效描述符。
2.EFAULT:进程对参数events指向的内存无写权限。
3.EINTR:系统调用在完成前被信号中断。
4.EINVAL:参数epfd不是有效epoll实例,或参数maxevents小于0。

timeout参数的取值:
0:即使没有事件发生,也立即返回。
-1:永久等待。

完整的使用epoll_wait函数的例子:

#define MAX_EVENTS 64
struct epoll_event *events;
int nr_events, i, epfd;
events = malloc(sizeof(struct epoll_event) * MAX_EVENTS);
if (!events) {
    perror("malloc");
    return 1;
}
nr_events = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (nr_events < 0) {
    perror("epoll_wait");
    free(events);
    return 1;
}
for (i = 0; i < nr_events; ++i) {
    printf("event=%ld on fd=%d\n", events[i].events, events[i].data.fd);
}
free(events);

如果epoll_ctl函数的参数event的成员events设置为EPOLLET,则fd上的监听称为边沿触发,默认为水平触发。这两者的区别以生产者消费者通过管道通信时情景举例:
1.生产者向管道写入1kb数据。
2.消费者在管道上调用epoll_wait,等待pipe出现数据,从而可读。

对于水平触发的监听,步骤2里对epoll_wait的调用将立即返回,以表明pipe可读;对于边沿触发的监听,epoll_wait调用会在1发生后才返回,即使调用epoll_wait时管道已经可读,调用仍然会等待,直到有数据写入,之后才返回。

水平触发也是poll和select函数的行为。

posted @   epiphanyy  阅读(5)  评论(0编辑  收藏  举报  
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示