• 欢迎浏览“String me = Creater\忠实的资深Linux玩家;”,请文明浏览,理性发言,有侵犯你的权益请邮件我(creater@vip.qq.com).
  • 把任何的失败都当作一次尝试,不要自卑;把所有的成功都想成是一种幸运,不要自傲。
  •    5年前 (2013-05-31)  Linux编程问题 |   5 条评论  19 
    文章评分 0 次,平均分 0.0

    1. 功能介绍
    epoll与select/poll不同的一点是,它是由一组系统调用组成。

    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

    epoll相关系统调用是在Linux 2.5.44开始引入的。该系统调用针对传统的select/poll系统调用的不足,设计上作了很大的改动。select/poll的缺点在于:epollnenene
    1.每次调用时要重复地从用户态读入参数。
    2.每次调用时要重复地扫描文件描述符。
    3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
    在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll显得不够高效。 epoll的设计思路,是把
    select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个wait。此外,内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们是一对多的关系。
    由于在执行epoll_create和epoll_ctrl时,已经把用户态的信息保存到内核态了,所以之后即使反复地调用epoll_wait,也不会重复地拷贝参数,扫描文件描述符,反复地把当前进程放入/放出等待队列。这样就避免了以上的三个缺点。
    接下去看看它们的实现:
    2. 关键结构体

    /* Wrapper struct used by poll queueing */
    struct ep_pqueue {
    poll_table pt;
    struct epitem *epi;
    };

    这个结构体类似于select/poll中的struct poll_wqueues。由于epoll需要在内核态保存大量信息,所以光光一个回调函数指针已经不能满足要求,所以在这里引入了一个新的结构体struct epitem。

    /*
    * Each file descriptor added to the eventpoll interface will
    * have an entry of this type linked to the hash.
    */
    struct epitem {
    /* RB-Tree node used to link this structure to the eventpoll rb-tree */
    struct rb_node rbn;
    红黑树,用来保存eventpoll
    /* List header used to link this structure to the eventpoll ready list */
    struct list_head rdllink;
    双向链表,用来保存已经完成的eventpoll
    /* The file descriptor information this item refers to */
    struct epoll_filefd ffd;
    这个结构体对应的被监听的文件描述符信息
    /* Number of active wait queue attached to poll operations */
    int nwait;
    poll操作中事件的个数
    /* List containing poll wait queues */
    struct list_head pwqlist;
    双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table
    /* The "container" of this item */
    struct eventpoll *ep;
    指向eventpoll,多个epitem对应一个eventpoll
    /* The structure that describe the interested events and the source fd */
    struct epoll_event event;
    记录发生的事件和对应的fd
    /*
    * Used to keep track of the usage count of the structure. This avoids
    * that the structure will desappear from underneath our processing.
    */
    atomic_t usecnt;
    引用计数
    /* List header used to link this item to the "struct file" items list */
    struct list_head fllink;
    双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,
    用来保存所有监视这个文件的epoll节点
    /* List header used to link the item to the transfer list */
    struct list_head txlink;
    双向链表,用来保存传输队列
    /*
    * This is used during the collection/transfer of events to userspace
    * to pin items empty events set.
    */
    unsigned int revents;
    文件描述符的状态,在收集和传输时用来锁住空的事件集合
    };
    该结构体用来保存与epoll节点关联的多个文件描述符,保存的方式是使用红黑树实现的hash表.
    至于为什么要保存,下文有详细解释。它与被监听的文件描述符一一对应.
    struct eventpoll {
    /* Protect the this structure access */
    rwlock_t lock;
    读写锁
    /*
    * This semaphore is used to ensure that files are not removed
    * while epoll is using them. This is read-held during the event
    * collection loop and it is write-held during the file cleanup
    * path, the epoll file exit code and the ctl operations.
    */
    struct rw_semaphore sem;
    读写信号量
    /* Wait queue used by sys_epoll_wait() */
    wait_queue_head_t wq;
    /* Wait queue used by file->poll() */
    wait_queue_head_t poll_wait;
    /* List of ready file descriptors */
    struct list_head rdllist;
    已经完成的操作事件的队列。
    /* RB-Tree root used to store monitored fd structs */
    struct rb_root rbr;
    保存epoll监视的文件描述符
    };

    这个结构体保存了epoll文件描述符的扩展信息,它被保存在file结构体的private_data
    中。它与epoll文件节点一一对应。通常一个epoll文件节点对应多个被监视的文件描述符。
    所以一个eventpoll结构体会对应多个epitem结构体。
    那么,epoll中的等待事件放在哪里呢?见下面

    /* Wait structure used by the poll hooks */
    struct eppoll_entry {
    /* List header used to link this structure to the "struct epitem" */
    struct list_head llink;
    /* The "base" pointer is set to the container "struct epitem" */
    void *base;
    /*
    * Wait queue item that will be linked to the target file wait
    * queue head.
    */
    wait_queue_t wait;
    /* The wait queue head that linked the "wait" wait queue item */
    wait_queue_head_t *whead;
    };

    与select/poll的struct poll_table_entry相比,epoll的表示等待队列节点的结构体只是稍有不同,与struct poll_table_entry比较一下。

    struct poll_table_entry {
    struct file * filp;
    wait_queue_t wait;
    wait_queue_head_t * wait_address;
    };

    由于epitem对应一个被监视的文件,所以通过base可以方便地得到被监视的文件信息。
    又因为一个文件可能有多个事件发生,所以用llink链接这些事件。
    3. epoll_create的实现
    epoll_create()的功能是创建一个eventpollfs文件系统的inode节点。具体由ep_getfd()完成。ep_getfd()先调用ep_eventpoll_inode()创建一个inode节点,然后调用d_alloc()为inode分配一个dentry。最后把file,dentry,inode三者关联起来。在执行了ep_getfd()之后,它又调用了ep_file_init(),分配了eventpoll结构体,并把eventpoll的指针赋给file结构体,这样eventpoll就与file结构体关联起来了。需要注意的是epoll_create()的参数size实际上只是起参考作用,只要它不小于等于0,就并不限制这个epoll inode关联的文件描述符数量。
    4. epoll_ctl的实现
    epoll_ctl的功能是实现一系列操作,如把文件与eventpollfs文件系统的inode节点关联起来。这里要介绍一下eventpoll结构体,它保存在file->f_private中,记录了eventpollfs文件系统的inode节点的重要信息,其中成员rbr保存了该epoll文件节点监视的所有文件描述符。组织的方式是一棵红黑树,这种结构体在查找节点时非常
    高效。
    首先它调用ep_find()从eventpoll中的红黑树获得epitem结构体。然后根据op参数的不同而选择不同的操作。如果op为EPOLL_CTL_ADD,那么正常情况下epitem是不可能在eventpoll的红黑树中找到的,所以调用ep_insert创建一个epitem结构体并插入到对应的红黑树中。ep_insert()首先分配一个epitem对象,对它初始化后,把它放入对应的红黑树。此外,这个函数还要作一个操作,就是把当前进程放入对应文件操作的等待队列。这一步是由下面的代码完成的。
    init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
    。。。
    revents = tfile->f_op->poll(tfile, &epq.pt);
    函数先调用init_poll_funcptr注册了一个回调函数 ep_ptable_queue_proc,这个函数会在调用f_op->poll时被执行。该函数分配一个epoll等待队列结点eppoll_entry:一方面把它挂到文件操作的等待队列中,另一方面把它挂到epitem的队列中。此外,它还注册了一个等待队列的回调函数ep_poll_callback。当文件操作完成,唤醒当前进程之前,
    会调用ep_poll_callback(),把eventpoll放到epitem的完成队列中,并唤醒等待进程。如果在执行f_op->poll以后,发现被监视的文件操作已经完成了,那么把它放在完成
    队列中了,并立即把等待操作的那些进程唤醒。
    5. epoll_wait的实现
    epoll_wait的工作是等待文件操作完成并返回。
    它的主体是ep_poll(),该函数在for循环中检查epitem中有没有已经完成的事件
    ,有的话就把结果返回。没有的话调用schedule_timeout()进入休眠,直到进程被再
    度唤醒或者超时。
    6. 性能分析
    epoll机制是针对select/poll的缺陷设计的。通过新引入的eventpollfs文件系统
    ,epoll把参数拷贝到内核态,在每次轮询时不会重复拷贝。通过把操作拆分为epol
    l_create,epoll_ctl,epoll_wait,避免了重复地遍历要监视的文件描述符。此外,
    由于调用epoll的进程被唤醒后,只要直接从epitem的完成队列中找出完成的事件,
    找出完成事件的复杂度由O(N)降到了O(1)。
    但是epoll的性能提高是有前提的,那就是监视的文件描述符非常多,而且每次完
    成操作的文件非常少。所以,epoll能否显著提高效率,取决于实际的应用场景。这
    方面需要进一步测试。

     

    除特别注明外,本站所有文章均为String me = "Creater\忠实的资深Linux玩家";原创,转载请注明出处来自http://unix8.net/home.php/1312.html

    关于

    发表评论

    暂无评论

    切换注册

    登录

    忘记密码 ?

    切换登录

    注册

    扫一扫二维码分享