高级IO------epoll

一,epoll的三个系统调用

函数1

int epoll_create(int size);

//创建一个句柄,也要在文件描述符表上占一个位置
//size 可以忽略,存在的原因是有很多程序已经用了size,如果去掉就不能用了。




函数2 

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

作用:epoll事件注册函数

       1,int  epfd :表示要对那个epoll对象进行操作,传入上面创建的句柄

       2, int op     :表示对某个文件描述符进行什么操作,增删改 

                      EPOLL_CTL_ADD:注册新的fd到epfd中。

                      EPOLL_CTL_MOD:修改已经注册的fd的监听事件。

                      EPOLL_CTL_DEL:从epfd中删除一个fd。

      3, int  fd    :操作那个文件描述符

       4,struct epoll_event * event:

                             

struct epoll_event
{
    uint32_t events;  //检测的事件
    epoll_data_t data;//用户自定义空间

}__EPOLL_PACKED;


typedef union epoll_data   
{
    void * ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
 
}epoll_data_t;

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

                             EPOLLIN : 表⽰对应的⽂件描述符可以读 (包括对端SOCKET正常关闭);

                             EPOLLOUT : 表⽰对应的⽂件描述符可以写;

                            EPOLLPRI : 表⽰对应的⽂件描述符有紧急的数据可读 (这⾥应该表⽰有带外数据到来);

                            EPOLLERR : 表⽰对应的⽂件描述符发⽣错误;

                            EPOLLHUP : 表⽰对应的⽂件描述符被挂断;

                           EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于⽔平触发(Level Triggered) 来说的.

                           EPOLLONESHOT:只监听⼀次事件, 当监听完这次事件之后, 还需要继续监听这个socket。

函数3 

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

函数作用:收集在epoll监控的事件中已经发送的事件

                 参数events是分配好的epoll_event结构体数组首元素的指针,每个结构体维护了一个文件描述符。

                 epoll将发生的事件赋值到events数组中,(数组的空间需要我们自己创建)

                 maxevents 将告诉内核,这个events有多大,就是有几个结构体。但不能大于创建句柄时的参数size

                 参数timeout 是超时时间,(0会立即返回,-1会永久阻塞)

函数返回值:成功返回对应Io上就绪的文件描述符的个数。返回0 表示超时,返回小于0表示失败

二,epoll的工作原理

1,当使用epoll_create方法创建一个epoll对象时,内核会创建一个epollevent结构体。

struct eventpoll{
 ....
 /*红⿊树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
 struct rb_root rbr;
 /*链表头结点,双链表中则存放着将要通过epoll_wait返回给⽤户的满⾜条件的事件*/
 struct list_head rdlist;
 ....
}; 

 2,每个epoll对象都会有一个对应的epollevent结构体,将通过epoll_ctl方法加入的新的事件加入到结构体的红黑树成员中。

 3,这些事件被挂载在红黑树中,如果有重复添加的事件,就可以通过红黑树高效的识别出来,(红黑树的插入效率是log n )。

 4,所有添加进epoll中的事件,都会与设备(网卡)驱动程序建立回调关系,也就是说当响应发生时,会自动调用这个回调方法。

 5,这个回调⽅法在内核中叫eppollcallback,它会将发⽣的事件添加到rdlist双链表中。

 6,当调⽤epoll_wait检查是否有事件发⽣时,只需要检查eventpoll对象中的rdlist双链表中是否有 epitem元素即可。

 7,如果rdlist不为空,则把发⽣的事件复制到⽤户态,同时将事件数量返回给⽤户. 这个操作的时间复 杂度是O(1)。

三,epoll优点

1,文件描述符的数目无上限:基于红黑树来管理所有需要监控的文件描述符。(对应 4 )

2,基于事件就绪通知方式:一旦被监听的文件描述符就绪,内核会采用类似于callback的回调机制,

      迅速激活这个文件描述符,将就绪的文件描述符加入就绪队列,调用epoll_wait函数获取就绪文件描述符时,

      直接拿出内核中就绪队列的元素即可。 这样随着文件描述符的增加,也不会影响判定就绪的性能。

      其实就是说,select和poll获取就绪文件描述符时,都要遍历查找文件描述符表,开销很大,但是epoll

      已将就绪描述符加入到就绪队列中,只需要从队列中拿出来就行,不用遍历。(对应select 3 )

4,从接口使用角度上讲,不必每次都重新设置要监控的文件描述符,使接口使用方便,

      也能够避免频繁的用户态和内核态之间,大量文件描述符的拷贝开销。

      其实就是说,select和poll每次都要讲文件描述符表拷贝到内核中,因为内核中并没有存放文件描述符的空间,

      所以这两个函数将文件描述符的 增删改 和 等待 没有区分,因为每次循环都需要 增删改 和等待。

      但是在创建一个epoll对象时,在内核中就已经为其开辟了一块空间(就是红黑树),用来保存文件描述符表,

      所以要进行 增删改 就直接调用  epoll_ctl  函数就行了,在每次的循环中就只需要等待就行了。

      这就是为什么,epoll要将 epoll_ctl 和 epoll_wait 函数分开来使用的原因了。(对应select 1, 2)

四,epoll的两种模式

工作模式1:LT水平触发(select ,poll ,epoll默认都是LT)

                    第一次 epoll-wait 返回读就绪, 如果一次没有将数据读完,那么下次还会触发这个文件描述符的读就绪。

工作模式2:ET边缘触发

                    要求一次将读就绪的数据读完,也就是第一次没有将数据读完时,下次不会触发这个文件描述符的读就绪。

对比ET和LT:

                    使用ET能减少epoll的触发次数,但是要求程序员必须在一次响应中就将数据处理完,这无疑增加了代码的复杂度。

                    但是ET模式,也使得程序员可以操作的空间更加灵活了。

理解ET模式和文件描述符的非阻塞模式结合:

                   

猜你喜欢

转载自blog.csdn.net/wm12345645/article/details/81838034
今日推荐