select。poll。epoll的区别

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

select。poll。I/O多路复用模型

select的缺点:
单个进程能够见识的文件描述符的数量存在最大限制,通常1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,
文件描述符数量越来越多,性能越来越差;
内核/用户控件内存拷贝问题,select需要复制带昂的句柄数据结构,产生巨大的开销,
select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现那些句柄发生了什么,
    select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行I/O,
那么之后再次select调用还是会讲这些文件描述符通知进程,
相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但是其他三个缺点依然存在,
    拿select模型为例,假设我们的服务器需要支持100万的并发连接,则在_FD_SETSIZE为1024情况下,我们至少需要开辟1k
个进程才能实现100万的并发连接,除了进程间上下文切换的时间消耗外,从内核/用户控件大量的无脑内存拷贝,数组轮询等,
是系统难以承受的,因此基于 select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务,
    epoll I/O多路复用模型实现的机制,
由于epoll的实现机制与select/poll机制完全不同,上面所说的select的缺点在epoll上不复存在,
    假设一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接,而每一时刻,通常只有几百上千个TCP连接时活跃的,
如何实现这样的高并发?
    在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统,让操作系统内核区查询这些套接字上是否有事件发生,
轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络时间,这一过程资源消耗较大,因此,select/poll
一般只能处理几千个的并发连接
    epoll的设计和实现select完全不同,epoll通过在linux内核中申请一个简易的文件系统
(文件系统一般情况用什么数据结构实现?B+树),把原先的select/poll调用分成三个部分
    1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
    2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字
    3)调用epoll_wait收集发生事件的连接
如此一来,要实现上面说的场景,只需要在进程启东时建立一个epoll对象,然后在需要的时候想这个epoll对象中添加或者删除连接
同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,
内核也不需要去遍历全部的连接。
    上面3个部分非常清晰,首先要调用epoll_create创建一个epoll对象,然后使用epoll_ctl可以操作上面建立的epoll对象,例如,
将刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socket句柄移除epoll,不在监控它等等。
    epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有时间发生时,就返回用户态的进程
    从上面调用方式就可以看到epoll比select/poll的优越之处,因为后者每次调用时都要传递你所要监控的所有的socket给socket/poll
系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百kb的内存到内核态,
非常低效,而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl
中拿到了要监控的句柄列表
    所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核
的数据结构里面塞入新的socket句柄
    在内核例,一切皆文件,所以epoll向内核注册额一个系统文件,用于存储上述被监控socket,当你调用epoll_create时,就会在这个
虚拟的epoll文件系统里创建一个file节点,当然这个file不是普通文件,它只服务与epoll,
    epoll在被内核初始化(操作系统启动),同时会开辟出epoll自己的内核告诉cache区,用于安置没一个我们想监控的socket,这些socket
里,以支持快速的查找,插入,删除。这个内核告诉cache区,就是建立连续的物理内存页,然后在纸上建立slab层,简单的说,就是物理上
分配好你想要的size的内存对象,每次使用时都是使用空闲的已经分配好的对象。
    epoll的高效就在于,当我们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然可以飞快的返回,并有效的将发生时间的句柄给我们用户
这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里加你一个file节点,在内核cache里建立了红黑树用于存储以后epoll_ctl
传来的socket外,还会在建立一个list链表,用于存储准备就绪的时间,当epoll_wait调用时,仅仅观察这个list链表里有,没有数据即可,
有数据就返回,没数据就sleep,等timeout时间到后即使链表没有数据也返回,所以epoll_wait非常高效。
    而且,通常情况下即使我们要监控百万计的句柄,大多一次或只返回很少量的准备就绪句柄而已,所以epoll_wait仅需要从内核态copy
少量的句柄到用户态而已,如何能不高效?
    那么,这个准备就绪list链表是怎么维护的呢,当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,
还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里,所以socket上有数据到了,
内核就把网卡上的数据copy到内核中后就来吧socket插入到准备就绪链表里了。
    如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决额大并发下的socket处理问题,指向epoll_create时,创建了
红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在就立即返回,不存在则添加到树干上,然后向内核
注册回调函数,用于当中断时间来临时向准备就绪的链表中插入数据,并执行epoll_wait时立刻返回准备就绪链表里的数据即可。
    最后看看epoll独有的两种模式LT和ET,无论是LT和ET模式,都适用于以上所说的流程,区别是,LT模式下,只要一个剧本上的时间一次
没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回,
    这件事怎么做到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪的list链表,这是我们调用epoll_wait,
会把准就绪的socket拷贝到用户态内存,然后清空准备就绪的list链表,最后epol_wait干了件事,就是检查这些socket,如果不是ET模式,
(就是LT模式的句柄),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪的链表里,epoll_wait,每次都会返回,
而ET模式的句柄,除了有新的中断到,即使socket上的时间没有处理完,也是不会次次从epoll_wait返回的。
    其中涉及到的数据结构:
    epoll用kmem_cache_create(slab分配器)分配内存用来存放structepitem 和structeppoll_entry.
    当向系统添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构,
        structrpitem{
            structrb_noderbn;//用于主结构管理的红黑树
            structlist_headrdlink;//时间就绪队列;
            structepitem *next;//用于主结构体重的链表
            structepoll_fiefdffd;//这个结构体对应的被监听的文件描述符信息
            intnwait;//poll 操作中事件的个数
            structlist_headpwqist;//双向链表,保存着被见识文件的等待队列,功能类似于select/poll中的poll_table;
            structeventpoll *ep; //该项属于哪个主结构体(多个epitm从属于一个eventpoll)
            structlist_headfllink;//双向链表,用来链表被坚持的文件描述符对应的struct file,因为file里有f_ep_link,用来保存所有监视这个文件
            的epoll节点,
            structepoll_eventevent; //注册的感兴趣的事件,也就是用户控件的epoll_event
        }
    而每个epoll fd(epfd)对应的主要数据结构为
        structeventpoll{
            spin_lock_tlock;//对本数据结构的访问
            structmutex mtx;//防止使用时被删除
            wait_queue_head_t wq;//sys_epoll_wait() 使用的等待队列
            wait_queue_head_tpoll_wait();//file—>poll() 使用的等待队列
            structlist_head rdllist;//事件满足条件的链表,双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件
            structrb_rootrbr;//用于管理所有fd的红黑树
            structepitem *ovfist;//将事件达到的fd进行连接起来发送至用户空间
        }
        
        structeventpoll在epoll_create时创建,
        这样来说,内核维护了一颗红黑树,大致的结构如下:附图4
        
        当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可,
        如果rdlist不为空,则把发生的事件复制到用户态,同时将时间数量返回给用户,
    
    

    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值