zoukankan      html  css  js  c++  java
  • 我理解的epoll(一)——实现分析

    epoll项目中用了几次,但是对于其原理只是一知半解。我希望通过几篇blog能加深对她的理解。

    我认为epoll是同步IO,因为他在调用epoll_wait时,内核在有I/O就绪前是阻塞的,虽然可以将timeout设置为0,此时就是非阻塞的了。但这不是变成忙轮询了么?

    select和epoll的比较

    select的缺点:

    1、支持一个进程打开大数目的socket描述符(FD):epoll支持的数目是进程打开文件的数目,具体数目可以cat /proc/sys/fs/file-max察看,差不多10玩非。而select只支持1024。虽然后来可以自己修改,但是引入了第二个缺点。

    2、IO效率不随FD数目增加而线性下降:虽然监听的FD很多,但是很多时候只有少数处于活跃,但是每次调用select还是不得不轮询所有的预监听的FD集合,导致效率成线性下降。因为select只告知用户准备好的fd的数目,没有告知具体是哪些FD准备好了。epoll不存在这样的问题,epoll_wait得到的是准备好的fd的集合。每一次扫描该集合都是有效的。

    3、每次调用select都需要重新设置fdset,并重新传到内核,而epoll只需要调用epoll_ctrl对已经传到内核的fdset进行add,del或者modify操作。

    epoll的简单实现

    知道这些缺点并没什么卵用,必须弄清楚其内部实现,才能对以上差异了解的更加透彻。这些差异中,最重要的是epoll性能不会随着FD的增加呈线程下降。这是怎么做到的呢。这要从epoll_create说起。

    1 int epfd = epoll_create(size);    //size代表一次监听的最大FD数目,因为早起版本是用hashtable实现的,现在是rbtree,所以size已经没有意义了。

    疑问:epoll_create为何要返回一个fd?后面在回答。

    epoll_create的时候创建了一个 struct eventpoll 结构体(内核自己创建的),每次创建epoll_create时,返回一个文件描述符epfd,内核就是通过这个数据结构来管理epoll的,或者说这个fd和这个struct evenpoll绑定了。

     1 struct eventpoll {
     2 
     3     spin_lock_t       lock;        //对本数据结构的访问
     4 
     5     struct mutex      mtx;         //防止使用时被删除
     6 
     7     wait_queue_head_t     wq;      //sys_epoll_wait() 使用的等待队列
     8 
     9     wait_queue_head_t   poll_wait;       //file->poll()使用的等待队列
    10 
    11     struct list_head    rdllist;        //事件满足条件的链表
    12 
    13     struct rb_root      rbr;            //用于管理所有fd的红黑树(树根)
    14 
    15     struct epitem      *ovflist;       //将事件到达的fd进行链接起来发送至用户空间
    16 
    17 }

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

     1 struct epitem {
     2 
     3     struct rb_node  rbn;        //用于主结构管理的红黑树
     4 
     5     struct list_head  rdllink;  //事件就绪队列
     6 
     7     struct epitem  *next;       //用于主结构体中的链表
     8 
     9     struct epoll_filefd  ffd;   //这个结构体对应的被监听的文件描述符信息
    10 
    11     int  nwait;                 //poll操作中事件的个数
    12 
    13     struct list_head  pwqlist;  //双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table
    14 
    15     struct eventpoll  *ep;      //该项属于哪个主结构体(多个epitm从属于一个eventpoll)
    16 
    17     struct list_head  fllink;   //双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,用来保存所有监视这个文件的epoll节点
    18 
    19     struct epoll_event  event;  //注册的感兴趣的事件,也就是用户空间的epoll_event
    20 
    21 }

    struct evenpoll结构中最重要的就是 struct rb_root rbr 和 struct list_head rdllist 。struct rb_node rbn是红黑树的根结点,epoll_ctrl向内核注册的要监听的fd都在这棵树上操作,比如add,del,modify。同时epoll_create还创建了一个事件满足条件的链表struct list_head rdllist,存放着就绪事件。

    这样说来,内核中维护了一棵红黑树,大致的结构如下:

    epol_wait得到的就绪事件都是从struct list_head rdllist中拷贝出来的。这是怎么做到的呢?epoll_ctl的时候还向内核中断处理程序注册一个回调函数ep_poll_callback,告诉内核,如果这个fd的中断到了,就把它放到准备就绪list链表里(rdllist)。所以,ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中,当用户调用epoll_wait()时,内核会将就绪队列中的事件报告给用户。

    介绍完epoll的大体流程,说一下epoll_ctl的函数原型:

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

    epfd:epoll_create创建的文件句柄。

    op:有三个值。EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD。 看字面意思就知道了怎么用了,其中由于struct eventpoll内部是红黑树,所以反复对同一个fd进行EPOLL_CTL_ADD没有效果。

    fd:要监听的文件描述符。

    event:是一个和fd关联的结构体,内部结构如下:

     1            typedef union epoll_data {
     2                void        *ptr;
     3                int          fd;
     4                uint32_t     u32;
     5                uint64_t     u64;
     6            } epoll_data_t;
     7 
     8            struct epoll_event {
     9                uint32_t     events;      /* Epoll events */
    10                epoll_data_t data;        /* User data variable */
    11            };

    一般使用方式为:event.data.fd = fd,还要指定事件类型和触发方式:event.events = EPOLLIN | EPOLLET。

    关于触发LT和ET触发方式,下文再说。epoll支持的事件类型,已经有很多资料了,就不细说了。

    下面简要分析一下epoll的工作过程:

    (1)epoll_wait调用ep_poll监听事件就绪队列rdlist,为空挂起,不为空被唤醒。

    (2)fd状态改变,导致fd对应的ep_poll_callback被调用,将fd关联的epitem结构放到rdlist上。

    (3)ep_events_transfer将rdllist上epitem的拷贝到txlist中,并将rdlist清空。

    (4)ep_send_events(很关键)将扫描txlist上的每个epitem,并调用其关联fd对用的poll方法,取得fd关联的新的events,将fd和封装到struct epoll_event内,发送到用户态。通过epoll_wait返回。之后如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist,否则(ET模式)不在加入rdlist。

     ET事件发生仅通知一次的原因是只被添加到rdlist中一次,而LT可以有多次添加的机会。

    有两种情况下ep_poll_callback会添加rdlist:

    情况1:fd状态发生改变,从不可读变成可读,或者从不可写变成可写。

    情况2:fd可读,或可写。

    LT模式下,情况1、2都可以触发ep_poll_callback添加rddlist。

    ET模式下,只有情况1能触发ep_poll_callback添加rddlist。

  • 相关阅读:
    【XSS技巧拓展】————23、多反射型XSS
    【XSS技巧拓展】————22、Source-Breaking Injections
    【XSS技巧拓展】————21、Location Based Payloads – Part II
    【XSS技巧拓展】————21、Location Based Payloads – Part I
    【XSS技巧拓展】————20、Agnostic Event Handlers
    Webshell如何bypass安全狗,D盾
    javaweb的面试题
    java面试题基础(二)
    java面试题基础(一)
    Mysql数据库简单使用
  • 原文地址:https://www.cnblogs.com/howo/p/8598235.html
Copyright © 2011-2022 走看看