linux文件IO:epoll

poll和select的改进版,在一个程序需要处理数百个文件描述符时很有用

2.6内核引入epoll机制,解决了poll和select的性能问题,并加入了一些新特性
poll和select每次调用都需要所有被监听的文件描述符,内核需要遍历所有的文件描述符,当数量变大时,性能消耗巨大

epoll将监听注册从实际监听中分离,从而解决了该问题
一个系统调用初始化一个epoll上下文,另一个从上下文中加入和删除文件描述符,第三个执行真正的事件等待

创建一个epoll实例

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

创建一个epoll实例,返回与该实例相关的文件描述符
size指定需要监听的文件描述符数量,传递一个近似值会带来性能提升

出错时返回-1,并设置errno
EINVAL size不是正数
ENFILE 系统达到打开文件数上限
ENOMEN 没有足够内存完成操作

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

文件描述符需要调用close关闭

控制epoll

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

向指定的epoll上下文加入或删除文件描述符

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

参数op指定要对fd进行的操作
参数event指定更具体的行为

op取值如下

op字段 说明
EPOLL_CTL_ADD 添加fd到epfd指定的epoll实例中
EPOLL_CTL_MOD 使用event修改在已有fd上的监听行为
EPOLL_CTL_DEL 从epfd指定的epoll实例中删除fd

epoll_event结构体指定了在文件描述符上的监听事件
epoll_event.events字段取值如下,可用位或运算符同时设置

events字段 说明
EPOLLERR 文件出错,即使没有设置,该事件也是会被监听
EPOLLET 在监听文件上开启边沿触发
EPOLLHUP 文件被挂起,即使没有设置,该事件也是会被监听
EPOLLIN 文件未阻塞,可读
EPOLLONESHOT 在一次事件产生并被处理后,文件不再被监听
EPOLLOUT 文件未阻塞,可写
EPOLLPRI 高优先级的带外数据可读

epoll_event.data字段由用户使用,确认事件后会返回给用户
通常将epoll_event.data.fd设置为指定的fd,从而可以知道触发事件的文件描述符

返回值,成功返回0,失败返回-1,并设置errno为下列值

errno值 说明
EBADF epfd不是有效的epoll实例,或fd不是有效的文件描述符
EEXIST op为EPOLL_CTL_ADD,但fd已与epfd关联
EINVAL epfd不是一个epoll实例,epfd与fd相同,或op无效
ENOENT op为EPOLL_CTL_MOD和EPOLL_CTL_DEL,但fd没有与epfd关联
ENOMEN 没有足够内存完成进程请求
EPERM fd不支持epoll

在epfd实例中加入一个fd指定的监听文件

struct epoll_event evt;
evt.data.fd= fd;
evt.events= EPOLLIN | EPOLLOUT;
int ret= epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt);
if(ret){
  perror("epoll ctl");
}

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

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

op为EPOLL_CTL_DEL时,因为没有设置事件,event参数可以为NULL

等待epoll事件

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

等待epfd实例中的fd上的事件,超时timeout毫秒
成功返回,events指向包含epoll_event结构体的内存,最多可以有maxevents个事件
返回值为事件数,出错返回-1,并设置errno为如下值

errno值 说明
EBADF epfd为无效文件描述符
EFAULT 进程对events指向的内存无写权限
EINTR 系统调用在完成前被信号中断
EINVAL epfd不是有效的epoll实例,和maxevents小于0

若timeout为0,即使没有事件发生,也会立即返回,此时调用返回0
若timeout为-1,将一直等待到有事件发生

一个完整的epoll_wait示例

#define MAX_EVENTS 64
int epfd;

struct epoll_event* events;
events= malloc(sizeof(struct epoll_event)* MAX_EVENTS);
if(!events){
  perror("malloc")
  return 0;
}

int nr_events= epoll_wait(epfd, events, MAX_EVENTS, -1);
if(nr_events<0){
  perror("epoll_wait");
  free(events);
  return 0;
}

for(int 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上的监听为边沿触发,相反则为水平触发

如下生产者和消费者在通过unix管道通信的情况
1.生产者向管道写入1kb数据
2.消费者在管道上调用epoll_wait,等待管道出现数据,从而可读

对于水平触发的监听,步骤2对epoll_wait的调用会立即返回
对于边沿触发的监听,调用会等到步骤1发生后才会返回,即使管道已有数据可读,调用也会等到有数据写入才会返回

水平触发是默认行为,是大多数开发者期望的,也是poll和select的行为
边沿触发需要一个不同的方式来写程序,通常利用非阻塞IO,并需要仔细检查EAGAIN

posted @   sgqmax  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示