epoll

概述

     epoll 是 linux 内核为处理大批量文件描述符而对 poll 进行的改进版本,是 linux 下多路复用 IO 接口 select/poll 的增强版本,显著提高了程序在大量并发连接中只有少量活跃的情况下的CPU利用率

在获取事件时,它无需遍历整个被侦听描述符集,只要遍历被内核 IO 事件异步唤醒而加入 ready 队列的描述符集合就行了

epoll 除了提供 select/poll 所提供的 IO 事件的电平触发,还提供了边沿触发,,这样做可以使得用户空间程序有可能缓存 IO 状态,减少 epoll_wait 或 epoll_pwait 的调用,提高程序效率

 

实现原理

 

liunx内部存储需要监控的epoll_event对象,如果有事件触发,返回对应事件

 

当某个进程调用 epoll_create 函数创建 epoll 专用的文件描述符epfd(连接用户空间和内核空间通道,就像socket 一样,用户看到的是epfd),

Linux 内核会创建为每个(epfd)创建 eventpoll 结构体变量:

struct eventpoll

{
  struct rb_root rbr; // 红黑树根节点,存储epoll 中所有事件

  struct list_head rdllist; // 双向链表,保存需要epoll_wait 返回的事件

}
 

这个结构体会在内核空间中分配独立的内存,用于存储使用 epoll_ctl 函数向 epoll 对象中添加进来的事件,

每一个事件都会挂到(存储)红黑树 rbr 上,这样重复添加的时间就可以通过红黑树结构快速识别并避免加入,保证了 epoll_ctl 函数的效率

所有添加到 epoll 中的时间都会与设备驱动程序建立回调关系,

一旦某个事件发生(满足条件是四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满),

则设备驱动程序会调用相应的回调函数,这个回调函数就是 ep_poll_callback,它会把相应事件放到 rdllist 这个双向链表中

这个双向链表的元素是 epitem 结构体类型的:

当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构:

struct epitem {

    struct rb_node  rbn;        //用于主结构管理的红黑树

    struct list_head  rdllink;  //事件就绪队列

    struct epitem  *next;       //用于主结构体中的链表

 struct epoll_filefd  ffd;   //这个结构体对应的被监听的文件描述符信息

 int  nwait;                 //poll操作中事件的个数

    struct list_head  pwqlist;  //双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table

    struct eventpoll  *ep;      //该项属于哪个主结构体(多个epitm从属于一个eventpoll)

    struct list_head  fllink;   //双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,用来保存所有监视这个文件的epoll节点

    struct epoll_event  event;  //注册的感兴趣的事件,也就是用户空间的epoll_event

}
 
如下图:
 

08215950-f001a31a5e4d4d6f8ab191f57f6fe948.png

事件触发模式

  EPOLL事件有两种模型 Level Triggered (LT)Edge Triggered (ET):

LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。

ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。

函数原型

int epoll_create(int size);

创建一个 epoll 专用的文件描述符,调用成功返回描述符,否则返回 -1

需要注意的是,该描述符使用完毕后同样需要 close 操作

 

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
作用:  需要监听的socket句柄和事件添加到内核

调用成功返回0,否则返回 -1

参数说明

  • epfd -- epoll_create 返回的 epoll 专用的文件描述符
  • op -- epoll_ctl 动作参数取值
    fd -- 需要监听的fd 
  • event -- 监听事件类型
struct epoll_event 的定义如下 :

    typedef union epoll_data {
        void *ptr;
        int fd;
        __uint32_t u32;
        __uint64_t u64;
    } epoll_data_t;


    struct epoll_event {
        __uint32_t events;      /* Epoll events 触发的条件 */
        epoll_data_t data;      /* User data variable */
    };
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

When successful, epoll_wait(2) returns the number of file descriptors ready for the requested I/O, or zero if no file descriptor became ready during the requested timeoutmilliseconds. When an error occurs, epoll_wait(2) returns -1 and errno is set appropriately.

返回需要处理的事件数目,如返回 0 表示超时,调用失败返回 –1

 

参数说明

  • epfd -- epoll 专用文件描述符
  • events -- 事件触发的事件集合,
  • maxevents -- 每次能处理的最大事件数,不能大于 epoll_create 的 size 参数
  • timeout -- 超时时间,以毫秒为单位,0 表示立即返回,-1 表示永远阻塞
 

 

 
 

优势

支持同时打开大量的文件描述符

select 函数对一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是1024,这对于一个服务器来说显然是太少了,虽然修改这个宏之后重新编译系统可以解决这个问题,但是随着 FD_SETSIZE 值的上升,select 函数的性能会显著下降

传统 Apache 服务器对此的解决方案是使用多进程的方式来打开大于 FD_SETSIEZE 的文件描述符,但是开辟进程的效率和资源都有一定的消耗,同时进程间数据同步也远没有线程间数据同步来的高效

epoll 能够打开的 FD 与系统能够持有的 FD 数目是一致的,只受限于系统的内存

IO效率不随 FD 数目增加而线性下降

传统的 select、poll 具有一个致命弱点,每当有数据可读或可写,都需要对整个描述符集合进行扫描,这样如果文件描述符集合很大,而同时又有大量空闲连接,则效率下降会非常明显

使用mmap加速内核与用户空间的消息传递

epoll是通过内核与用户空间mmap同一块内存实现的,这样就可以避免从内核空间通知用户空间的时候不必要的拷贝了

内核微调

内核的 TCP/IP 协议栈使用内存池管理 sk_buff 结构,通过在运行时改变 /proc/sys/net/core/hot_list_length 的值,即可动态调整整个内存池的大小,如 listen 函数所指示的3次握手数据包队列长度也可以根据平台内存动态调整

 

代码演示

  http://git.oschina.net/wang_cyi/wcycode/blob/master/liunx/socket/epoll/epoll.cpp?dir=0&filepath=liunx%2Fsocket%2Fepoll%  2Fepoll.cpp&oid=65c7ea07088acbdb02e432b10ac4453c3a335076&sha=06f1a2beaa838b498a1ff7450729766b6e4df6e8

参考文档:

1 http://blog.youkuaiyun.com/justlinux2010/article/details/8510507

2 http://www.cnblogs.com/apprentice89/p/3234677.html

3 http://techlog.cn/article/list/10182604#e

转载于:https://my.oschina.net/woyaoxue/blog/521303

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值