I/O模型与I/O复用

Unknown.jpg

IO模型

1. 阻塞式IO

等待数据时应用程序被阻塞,等待数据到来。直到数据被复制到用户态缓冲区才返回。只是应用程序被阻塞,不占用CPU时间,阻塞期间还可以执行其他进程。

2. 非阻塞式IO

程序执行系统调用后,如recvfrom, 内核返回一个状态码,应用继续执行,内核负责等待数据,程序需要不断执行系统调用来轮询内核IO是否完成。

3. IO复用

调用selectpoll等系统调用等待数据,并且可以等待多个套接字中的任何一个变为可读,这一过程会被阻塞。当某一套接字可读时,调用recvfrom将数据从内核复制到进程中。单进程处理多IO,省去进程创建和切换的开销。

4. 信号驱动

应用进程调用sigaction,内核立即返回,等待数据到达,等待阶段是非阻塞的。当内核发现数据到来时,向进程发一个SIGIO信号,进程收到后,调用recvfrom拷贝数据。

5. 异步IO

执行aio_read调用后立即返回,不等待。进程继续执行,不阻塞。内核完成所有操作后向进程发信号。与信号驱动不同在于,异步IO发送信号告诉进程IO已经完成,信号驱动发信号是说可以开始IO。

总结:14属于同步IO,即第二阶段(从内核拷贝到用户控件)应用程序会阻塞,其中24在第一阶段(等待数据到来)进程不会阻塞。异步IO有点像甩手掌柜,把事情交给内核打理。


IO复用:select,poll,epoll

select

select把事件分类,用三个数组来分别监听读,写和异常情况。调用时把数组全盘拷贝进内核,有事件到来时轮询数组看看哪个socket上发生。
它的缺点在于:

  1. 单个进程能监听的文件描述符存在最大限制,FD_SIZE通常是1024。由于select采用轮询方式扫描文件描述符,文件描述符越多,性能越差。
  2. 内核/用户态切换开销大。select需要切换大量的句柄到内核态。
  3. select返回整个数组,进程需要遍历整个数据才知道哪个socket上有事件。
  4. select使用水平触发,select如果没有处理已就绪的事件,后续select仍会将该文件操作符返回给进程。

poll

poll使用链表存储文件操作符,打破了select最大描述符的限制,但其他不足仍存在

epoll

epoll的实现:在内核中申请一个简易的文件系统(B+树)。
epoll调用分3步:

  1. epoll_create:创建一个epoll对象
  2. epoll_ctl:向对象中添加高并发socket
  3. epoll_wait: 收集有事件发生的socket,放到返回双链表中。
    因此,只需创建一个epoll对象,适时地向该对象添加或删除socket。

epoll对象数据结构
红黑树: 保存所有需要监控的句柄,高效去重
双链表:保存事件发生所在的句柄,待返回给进程
回调:epoll中事件与设备驱动建立回调关系,一旦事件发生回调函数就把事件添加到双链表中。

2种触发方式
水平触发:只要有fd就绪就向�内核发出通知,如果不处理,下次调用依然是就绪状态。
边缘触发:fd就绪,内核只发一次通知,如果不处理下次调用不再通知。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容