I/O多路复用之epoll

1、select、poll的些许缺点

先回忆下select和poll的接口

int select(int nfdsfd_set *readfdsfd_set *writefdsfd_set *exceptfds, struct timeval *timeout);

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

这两个多路复用实现的特点是:

  • 每次调用select和poll都要把用户关心的事件集合(select为readfds,writefds,exceptfds集合,poll为fds结构体数组)从用户空间到内核空间。
  • 如果某一时间段内,只有少部分事件是活跃的(用户关心的事件集合只有少部分事件会发生),会浪费cpu在对无效事件轮询上,使得效率较低,比如,用户关心1024个tcp socket的读事件,当是,每次调用select或poll时只有1个tcp链接是活跃的,那么对其他1023个事件的轮询是没有必要的。

select支持的文件描述符数量较小,一般只有1024,poll虽然没有这个限制,但基于上面两个原因,poll和select存在同样一个缺点,就是包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而且不论这些文件描述符是否就绪,每次都会轮询所有描述符的状态,使得他们的开销随着文件描述符数量的增加而线性增大。epoll针对这几个缺点进行了改进,不再像select和poll那样,每次调用select和poll都把描述符集合拷贝到内核空间,而是一次注册永久使用;另一方面,epoll也不会对每个描述符都轮询时间是否发生,而是只针对事件已经发生的文件描述符进行资源抢占(因为同一个描述符资源(如可读或可写)可能阻塞了多个进程,调用epoll的进程需要与这些进程抢占该相应资源)。下面记录一下自己对epoll的学习和理解。

2、epoll的几个接口

上面说到每次调用select和poll都把描述符集合拷贝到内核空间,这是因为select和poll注册事件和监听事件是绑定在一起的,为甚这么说呢,我们看select和poll的编程模式就明白了:

while(true){
  select(maxfd+1,readfds,writefds,execpfds,timeout)/poll(pollfd,nfds,timeout);
}

I/O多路复用之select中说到了select的实现,调用select时就会进行一次用户空间到内核空间的拷贝。epoll的改进其实就是把注册事件和监听事件分开了,epoll使用了一个特殊的文件来管理用户关心的事件集合,这个文件存在于内核之中,由特殊的数据结构和一组操作构成,这样的话,用户就可以提前告知内核自己关心的事件,然后再进行监听,因此,就只需要一次用户空间到内核空间的拷贝了。其中管理事件集合的文件通过epoll_create创建,注册用户行为通过epoll_ctl实现,监听通过epoll_wait实现。那么编程模型大概是这个样子:

epoll_fd=epoll_create(size);
epoll_ctl(epoll_fd,operation,fd,event);
while(true){
  epoll_wait(epoll_fd,events,max_events,timeout);
}

2.1、epoll_create接口

#include <sys/epoll.h>

int epoll_create(int size);

epoll_create创建epoll文件,其返回epoll的句柄,size用来告诉内核监听文件描述符的最大数目,这个参数不同于select()中的第一个参数(给出最大监听的fd+1的值)。需要注意的是,当创建好epoll句柄后,它会占用一个fd值,在linux下如果查看/proc/进程id/fd/,能够看到这个fd,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。(摘自epoll精髓

epoll_create会在内核初始化完成epoll所需的数据结构,其中一个关键的结构就是rdlist,表示就绪的文件描述符链表,epoll_wait函数就是直接检查该链表,从而抢占准备好的事件;另一个关键的结构是一颗红黑树,这棵树专门用于管理用户关心的文件描述符集合。

注:关于epoll文件的核心数据结构以及epoll_create的源码请参考这两份资料

linux 内核poll/select/epoll实现剖析

epoll源码实现分析[整理]

2.2、epoll_ctl接口

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll_ctl用于用户告知内核自己关心哪个描述符(fd)的什么事件(event),

  • epfd,使用epoll_create函数创建的epoll句柄,epfd文件描述符对应的结构中,有一颗红黑树,专门用于管理用户关心的事件集合。
  • op,用于指定用户行为,op参数有三种取值:fd,用户关心的文件描述符
    • EPOLL_CTL_ADD,注册新的fd到epfd中;
    • EPOLL_CTL_MOD,修改已注册fd的事件;
    • EPOLL_CTL_DEL,从epfd中删除一个fd;
  • event,用户关心的事件(读,写)

参数event的结构如下:

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable,内核会修改该属性 */
};

events可以是以下几个宏的集合:

  • EPOLLIN ,表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT,表示对应的文件描述符可以写;
  • EPOLLPRI,表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR,表示对应的文件描述符发生错误;
  • EPOLLHUP,表示对应的文件描述符被挂起;
  • EPOLLET,将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT,只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

2.2.1、EPOLL_CTL_ADD

重点说一下这个取值,当op=EPOLL_CTL_ADD时,epoll_ctl主要做了四件事:

  • 把当前文件描述符及其对应的事件(fd,epoll_event)加入红黑树,便于内核管理
  • 注册设备驱动poll的回调函数ep_ptable_queue_proc,当调用f_op->poll()时,最终会调用该回调函数ep_ptable_queue_proc()
  • ep_ptable_queue_proc回调函数中,注册回调函数ep_poll_callback,ep_poll_callback表示当描述符fd上相应的事件发生时该如何告知进程。
  • ep_ptable_queue_proc回调函数中,检测是文件描述符fd对应的设备的epoll_event事件是否发生,如果发生则把fd及其epoll_event加入上面提到的就绪队列rdlist中

注:关于epoll_ctl、ep_ptable_queue_proc、ep_poll_callback的原理及源码请参考这两份资料

linux 内核poll/select/epoll实现剖析

epoll源码实现分析[整理]

2.3、epoll_wait接口

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

  • epfd,使用epoll_create函数创建的epoll句柄,epfd文件描述符对应的结构中,有一颗红黑树,专门用于管理用户关心的事件集合。
  • events,传出参数,表示发生的事件
  • maxevents,传入参数,表示events数组的最大容量,其值不能超过epoll_create函数的参数size
  • timeout,0,不阻塞;整数,阻塞timeout时间;负数,无限阻塞

epoll_wait函数的原理就是去检查上面提到的rdlist链表中每个结点,rdlist的每一个结点能够索引到监听的文件描述符,就可以调用该文件描述符对应设备的poll驱动函数f_op->poll,用以检查该设备是否可用。这里有个问题需要思考一下,既然rdlist就表示就绪的事件,也就是设备对应的资源可用了,为什么还要进行检查?这是因为设备的某个资源可能被多个进程等待,当设备资源准备好后,设备会唤醒阻塞在这个资源上的所有进程,当前调用epoll_wait的进程未必能抢占这个资源,所以需要再调用检查一次资源是否可用,以防止被其他进程抢占而导致再次不可用,检查的方法就是调用fd设备的驱动f_op->poll。

这也是为什么epoll效率可能比较高的原因,epoll每次只检查已经就绪的设备,不像select、poll,不管有没有就绪,都去检查。

注:关于epoll_wait的原理及源码请参考这两份资料

linux 内核poll/select/epoll实现剖析

epoll源码实现分析[整理]

2、epoll的两种触发模式ET&LT

二者的差异在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。下面两幅图清晰反映了二者区别,这两幅图摘自Epoll在LT和ET模式下的读写方式

      

参考资料:

linux 内核poll/select/epoll实现剖析

epoll源码实现分析[整理]

Epoll在LT和ET模式下的读写方式

时间: 2024-10-07 05:30:23

I/O多路复用之epoll的相关文章

用C写一个web服务器(二) I/O多路复用之epoll

.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .container::before,.container::after { content: " "; display: table } .container::after { clear: both } .container::before,.container::after { content:

Linux网络编程——多路复用之epoll

目录 Linux网络编程--多路复用之epoll 基础API 实例一.epoll实现在线聊天 实例二.epoll实现在客户端断开后服务端能一直运行,客户端可以多次重连 Linux网络编程--多路复用之epoll ? epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,

epoll高并发多路复用,基于epoll的高性能服务器

并发测试工具ab使用 linux命令安装这个工具:apt-get install apache2 windows中装好apache之后就会再带一个工具 windows命令使用方法 ab -n 200 -c 5 http://www.baidu.com/ 1000就是测试的数量 -c 10 就是开启的线程数 测试的地址 反回了一些测试信息,如 使用时间,每次要多久等信息. linux也是一样用的. epoll多路复用IO 高并发 epoll多路复用是专门用来处理高并发的,在linux多路复用中有多

IO多路复用之epoll

1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次. 2.epoll接口 epoll操作过程需要三个接口,分别如下: #include <sys/epoll.h> int epoll_create(int size); int epoll_ctl(

unix网络编程——I/O多路复用之epoll

1. 基本概念 当程序进行IO时,如果数据尚未准备好,那么IO将处于阻塞状态.当某个进程有多个打开的文件,比如socket,那么其后的所有准备好读写的文件将受到阻塞的影响而不能操作.不借助线程,单一进程无法在同一时间服务多个文件描述符.非阻挡式IO可以作为一个解决方案,但是效率并不高.首先进程需要不断发IO请求,其次,如果程序可以休眠,让出CPU将提高效率.多任务式IO是在其中任何一个文件描述符就绪时收到通知,此时IO将不会受到阻挡,其余时间处于休眠状态,将CPU资源让给别的进程. 为了实现I/

IO多路复用之epoll总结

1.基本知识 epoll是在2.6内核中提出的(mac没有),是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy 只需一次. ==>内核事件表?? 2.epoll接口 epoll操作过程需要三个接口,分别如下:llinux 系统 $man  epoll 查看是否支持epoll #include <sys

Linux IO多路复用之epoll网络编程及源码(转)

原文: 前言 本章节是用基本的Linux基本函数加上epoll调用编写一个完整的服务器和客户端例子,可在Linux上运行,客户端和服务端的功能如下: 客户端从标准输入读入一行,发送到服务端 服务端从网络读取一行,然后输出到客户端 客户端收到服务端的响应,输出这一行到标准输出 服务端代码 代码如下: #include <unistd.h> #include <sys/types.h> /* basic system data types */ #include <sys/soc

IO多路复用之epoll(二)

前一篇介绍了epoll的LT模式,LT模式注意epollout事件在数据全部写成功后需要取消关注, 或者更改为EPOLLIN. 而这次epoll的ET模式,要注意的是在读和写的过程中要在循环中写完或者读完所有数据, 确保不要丢掉一些数据. 因为epoll ET模式只在两种边缘更改的时候触发,对于读事件只在内核缓冲区由空变为 非空通知一次用户,对于写事件,内核缓冲区只在由满变为非满的情况通知用户一次. 下面是代码 int main(){ int eventsize = 20; struct epo

I/O多路复用之epoll(转)

4.epoll 在linux的网络编程中,很长的一段时间都在使用select来做事件触发.然而select逐渐暴露出了一些缺陷,使得linux不得不在新的内核中寻找出替代方案,那就是epoll.其实,epoll与select原理类似,只不过,epoll作出了一些重大改进,即: a.当它们所监听的集合中有状态发生改变时,select需要循环检查整个集合,才能确定那个文件描述符状态发生改变,进而进行操作:而epoll在添加文件描述符到集合时,已经绑定了该文件描述符的对应函数,因此,当该文件描述符状态