一.什么是I/O多路复用
二.select,poll和epoll
1.关于select,poll和epoll
2.select介绍
3.poll介绍
4.epoll介绍
5.select,poll和epoll简单总结
二.epoll源码实现分析
一.什么是I/O复用
1. I/O multiplexing(I/O多路复用) 这里面的 multiplexing 指的是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。以下图来说明:
2. I/O复用使得程序能同时监听多个文件描述符,这可以提高程序的性能。
3. 什么情况下会用到I/O复用技术?
①客户端程序要同时处理多个socket。
②客户端程序要同时处理用户输入和网络连接。
③TCP服务器要同时处理舰艇socket和连接socket。此场景I/O复用使用率最高。
④服务器要同时处理TCP和UDP请求。
⑤服务器要同时监听多个端口,或处理多种服务。
4.I/O复用本身是阻塞的,当多个文件描述符同时就绪时,如果不使用多进程或多线程等编程手段,程序只能按照顺序依次处理其中的每一个文件描述符。
5.I/O复用的五种模型
①阻塞I/O
最常见的I/O模型。默认情况下,socket是阻塞的。阻塞是指调用结果返回之前,当前线程会被挂起(线程进入睡眠状态) 函数只有在得到结果之后,才会返回,才能继续执行。I/O 完成后, 系统直接通知进程, 则进程被唤醒 。
②非阻塞I/O
进程发起I/O调用,I/O自己知道需过一段时间完成,就立即通知进程进行别的操作,则为非阻塞I/O。当I/O操作不能完成时,内核不会让进程睡眠,而是返回错误。
③I/O多路复用-》同步
使用select/poll/epoll系统调用,当描述符准备就绪时,系统调用返回,并执行I/O操作
④异步I/O
通知内核开始执行I/O操作,全部完成后发送通知.
⑤信号驱动I/O
进程设置了信号处理程序后立即返回,当描述符就绪时,可以执行I/O操作时,内核发送SIGIO信号通知进程。
二.select,poll和epoll
1.关于select,poll和epoll
select,poll和epoll都是I/O多路复用的机制。I/O多路复用机制,可以监视多个描述符,一旦某个就绪,能够通知程序进行相应操作。
2.select介绍
该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经过一段指定时间后才被唤醒。进程需要调用的时候,把请求发送给select ,可以发起多个,但是最多只能支持1024个,因为底层实现是一个数组,他的时间复杂度为O(n),也就是说,我要去一个个看哪个描述符准备好了,因为每次调用select,都要把在内核遍历传进来的所有的描述符。
3.poll介绍
poll与select的实现相似,只是描述fd集合的方式不同,底层实现是链表,对于请求没有限制,但是多余1024个性能会下降,他的时间复杂度也为O(n)。
4.epoll介绍
✦epoll是Linux特有的I/O复用函数,epoll使用一组函数(sys_epoll_creat,sys_epoll_ctl,sys_epoll_wait)来完成任务,他把用户关心的文件描述符放在内核事件表(红黑树)中,这样的话,就相当于是准备好的描述符都放在一个地方(rdlist就绪链表),我只需要去拿走就好了。他的时间复杂度为O(1)。但是epoll需要一个额外的文件描述符,来唯一标识内核中的事件表。
✦epoll是对select的优化,主要体现在两个方面:
①在sys_epoll_ctl函数中,每次注册新的事件到epoll句中时,会把所有的fd拷贝进内核,而不是在sys_epoll_wait时重复拷贝,epoll保证每个fd在整个过程中只会被拷贝一次,这样解决了select和poll在每次调用时都会把所有fd拷贝一次,减少开销。
②epoll不像select和epoll每次都把current加入fd对应的设备等待队列中,只在sys_epoll_ctl函数时挂一遍,并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数回吧就需的fd加入一个就绪的fd加入一个就绪链表,sys_epoll_wait在这个链表中查看有没有就绪的fd(利用timeout实现睡一会,查一会的功能,也就是轮询注册回调函数)
✦epoll对文件描述符处理的两种模式(LT和ET):
①LT:电平触发
默认工作模式,这种模式下epoll相当于一个效率较高的poll。当sys_epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不用立即处理此事件,下一次调用sys_epoll_wait时,sys_epoll_wait还会再次向应用程序通告此事件。
②ET:边沿触发
党项epoll内核事件表中注册一个文件描述符上的EPOLITE事件时(将addfd函数的第三个参数设置为true),epoll将用ET模式来操作该文件描述符,ET模式时epoll的高效工作模式。当sys_epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理此事件。这样降低了同一个epoll事件被重复触发的次数。
5.select,poll和epoll简单总结
用通俗的话来讲,假如有三个老师分别是select,poll,epoll),每次当老师要去收全班同学的作业时,也就是当老师被调用时,select和poll老师就会一个一个的检查在这个班的所有的同学的作业并拿走作业,而epoll老师设置了一个讲台,说谁写完了就放在讲台上,那当epoll老师工作的时候,只需要在讲台上拿走完成的作业,而不用全部遍历。
二.epoll源码实现分析
1.sys_epoll_creat-----》创建结构体,关联fd和文件,返回的是文件描述符
✦执行epoll_create时,创建了红黑树和rdlist链表。
✦关于size参数,size参数其实是没有任何意义的,因为之前的fd使用hash表保存,size表示hash表的大小,现在使用红黑树,为了兼容以前的版本,将size保留了下来,size的值为0.
✦执行步骤:
第一步 得到fd,inode,file,此三者有关系,可以关联起来
第二步 创建ep结构体
①file的指针(private->data)指向结构体*ep,红黑树存储的就是内核事件表,*ep中有rdlist就绪队列和红黑树,添加进程描述符时,只需要向红黑树添加一个节点
②fd与file关联
✦红黑树
在红黑树的节点上挂着我们感兴趣的select事件,当向epoll添加socket事件时,系统将我们传递的信息封装成结构体,然后挂到红黑树的节点上。
✦rdlist就绪链表
这个双向链表中存放的是就需的事件,调用sys_epoll_wait的时候将这些事件返回给用户。
✦图示结构体关系
2.sys_epoll_ctl------》向内核事件添加描述符
✦执行sys_epoll_ctl时,如果增加fd(socket),则检查在红黑树中是否存在,存在立即返回,不存在则添加到红黑树上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪list链表中插入数据。
✦执行步骤
✧通过文件表指向结构体
✧判断epfd和op是否相同,相同则退出,因为不能把自身加入红黑树
✧epfd找到的file,file指向struct eventpoll,查看目标描述符是否已经添加到红黑树(内核事件表),如果epi==NULL,则之前没有添加过,默认添加事件
对红黑树进行增删改查,例如插入,即向红黑树添加描述符和事件,红黑树指向一个epitem,会创建一个与fd对应的epitem结构,并初始化相关成员,并制定调用poll_wait时的回调函数用于数据就绪时唤醒进程
①设置回调函数
②添加到红黑树✧ep_poll_callback–》回调函数,当有数据则添加,,,注册的回调函数,当数据就绪时,ep_poll_callback被调用,将就绪描述符添加到rdlist就绪队列
3.sys_epoll_wait–》找到就绪的描述符
✦执行sys_epoll_wait时立刻返回准备就绪链表里的数据即可。
✦执行步骤
✧通过fd找到file
✧如果有描述符要拷到用户空间,则把需要的数目返回,如果没有,则goto继续等待rdlist有没有描述符,只有回调函数才能把描述符加入rdlist
✦没有事件就不应该把他放入rdlist中,但有时候却在rdlist中
因为我们要具备反复检查数据的能力,没读完事件时,描述符会被拷贝出去,可以继续读,返还给rdlist
✦rdlist有数据的两种情况(LT):
调回调函数,添加数据
用户没读完事件时,描述符会被拷贝出去,可以继续读,返还给rdlist