zoukankan      html  css  js  c++  java
  • epoll实现

    在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。在大数据、高并发、集群等一些名词唱得火热之年代,select和poll的用武之地越来越有限,风头已经被epoll占尽。

    epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分:

    1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

    2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

    3)调用epoll_wait收集发生的事件的连接

    如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。

    下面来看看Linux内核具体的epoll机制实现思路。

    当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:

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

    每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

    而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

    在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

    struct epitem{
        struct rb_node  rbn;//红黑树节点
        struct list_head    rdllink;//双向链表节点
        struct epoll_filefd  ffd;  //事件句柄信息
        struct eventpoll *ep;    //指向其所属的eventpoll对象
        struct epoll_event event; //期待发生的事件类型
    }

    当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

    从上面的讲解可知:通过红黑树双链表数据结构,并结合回调机制,造就了epoll的高效。

    OK,讲解完了Epoll的机理,我们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。

    第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。

    第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

    第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

    具体流程

    使用起来很清晰,首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。

    epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。

    epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。

     epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。

    几乎所有的epoll程序都使用下面的框架:

     for( ; ; )
        {
            nfds = epoll_wait(epfd,events,20,500);
            for(i=0;i<nfds;++i)
            {
                if(events[i].data.fd==listenfd) //有新的连接
                {
                    connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
                    ev.data.fd=connfd;
                    ev.events=EPOLLIN|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
                }
     
                else if( events[i].events&EPOLLIN ) //接收到数据,读socket
                {
                    n = read(sockfd, line, MAXLINE)) < 0    //
                    ev.data.ptr = md;     //md为自定义类型,添加数据
                    ev.events=EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
                }
                else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
                {
                    struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据
                    sockfd = md->fd;
                    send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据
                    ev.data.fd=sockfd;
                    ev.events=EPOLLIN|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
                }
                else
                {
                    //其他的处理
                }
            }
        }

    参考:

    高并发网络编程之epoll详解

    论epoll的实现

    linux下epoll如何实现高效处理百万句柄的

    剖析 epoll ET/LT 触发方式的性能差异误解(定性分析)

  • 相关阅读:
    JVM笔记-temp
    Spark笔记-treeReduce、reduce、reduceByKey
    Java笔记-快速失败and安全失败
    Netty笔记--ByteBuf释放
    Spark笔记--使用Maven编译Spark源码(windows)
    MySQL笔记--查询语句实践
    Kafka笔记--指定消息的partition规则
    Spark Executor Driver资源调度小结【转】
    Spark学习笔记--Graphx
    HBase笔记--自定义filter
  • 原文地址:https://www.cnblogs.com/losophy/p/9396243.html
Copyright © 2011-2022 走看看