Linux epoll

IO过程以及IO多路复用

操作系统上的IO就是用户空间和内核空间数据的交互,因此IO操作通常包含以下步骤

  1. 等待网络数据到达网卡(读就绪)
  2. 读取到内核缓冲区
  3. 从内核缓冲区复制数据到用户空间
  4. 从用户空间读数据

所谓的多路复用其实复用的只是线程

系统调用selectpollepoll
事件集合传入感兴趣的可读、可写以及异常事件,内核通过对这些参数修改来反馈其中的就绪事件,这使得用户每次调用select都要重置这3个参数统一处理所有事件类型。通过pollfd.events传入所有事件,内核通过pollfd.revents反馈其中就绪事件内核通过一个事件表接管用户所有事件,无需反复传入
索引就绪文件符时间复杂度O(n)O(n)O(1)
最大支持文件描述符数有最大值限制没有最大值限制没有最大值限制
工作模式LTLTET
内核实现与工作效率轮询方式检测就绪事件O(n)轮询方式检测就绪事件O(n)回调方式检测就绪事件O(1)

工作模式

在上表中出现的LT、ET两种模式,LT指的是Level Trigger,ET指的是Edge Trigger

  • LT: 只要fd一直保持就绪,就会一直提醒用户程序操作
  • ET: 仅会提示一次,直到下次数据流入前都不会提示

select

select的思想是预先传递一个socket列表,若列表中的socket都没有数据就挂起进程,直到有一个socket收到数据,唤醒进程

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

具体流程如下

  1. 对于进程A同时监听sock1、sock2、sock3,那么调用A后就会将A加入这3个socket的等待队列中
  2. 当任何一个socket收到数据后,就会将进程A从所有的等待队列中移除,加入到工作队列中

也就是说当进程A被唤醒后,至少知道有一个socket接收了数据。程序只需要遍历一遍socket列表,就可以得到就绪的socket

但每次调用select都要将进程加入所有要监听的socket等待队列,每次唤醒都要从所有队列移除,而且要知道哪个socket收到了数据还要再遍历一次,这效率就很低。

epoll

epoll在select基础上改良引入了两项措施

  • 将维护等待队列和堵塞进程功能分离
  • 采用就绪列表储存进程就绪socket

大概流程是epoll维护了储存需要监听socket的红黑树,以及就绪socket链表。当有socket数据过来时,加入该socket到就绪socket链表,然后进程遍历对应的就绪socket链表

linux epoll相关方法

// 内核生成一个epoll实例数据结构,并返回一个文件描述符。这个特定的描述符是epoll实例的句柄,接下来的两个接口以它为中心(即epfd形式参数)。
// size 表示最大监视的文件描述符数量。后续版本弃用
int epoll_create(int size);

// 添加正在监听的描述符,或者将其从红黑树中移除,或者修改要监听的文件描述符的最大值,尽管在Linux的后续版本中已经弃用了(另外,不要为大小传递0,否则会报告无效参数错误)。
// op参数描述了操作的类型。

// EPOLL_CTL_ADD:向需要监控的兴趣列表添加一个描述符
// EPOLL_CTL_DEL:从兴趣列表删除一个描述符
// EPOLL_CTL_MOD: 从兴趣列表修改一个描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
typedef union epoll_data {
  void *ptr; // 指向自定义数据
  int fd; // 注册的文件描述符
  uint32_t u32;
  uint64_t u64;
}epoll_data_t;


struct epoll_event {
  // 描述epoll事件。常见epoll事件如下
  // EPOLLIN: 描述符处于可读状态
  // EPOLLOUT: 描述符处于可写状态
  // EPOLLET:设置epoll事件通知方式为ET
	// Epollonshot:第一次通知,之后不再监控
	// EPOLLHUP:本地描述符生成一个挂起事件,即默认的监控事件
	// EPOLLRDHUP:相反的描述符生成一个待决事件
	// EPOLLPRI:由带外数据触发
	// EPOLLERR:当描述符生成错误时触发,默认的检测事件
  uint32_t event; 
  epoll_data_t data 
}


// 阻塞等待注册事件的发生,返回事件数量,并将激活事件写入events数组(events大小与maxevents一致)
// timeout -1: 意味着调用将一直堵塞,直到文件描述符进入就绪状态,或者在返回之前捕获到信号
// 0 用于非堵塞状态检测描述符是否处于就绪状态
// >0 代表调用将持续最多超时事件,如果在此期间就绪或者捕获信号,则返回,否则直接超时
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

Ref

  1. https://www.sobyte.net/post/2022-04/epoll-efficiently/
  2. https://stackoverflow.com/questions/9162712/what-is-the-purpose-of-epolls-edge-triggered-option
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值