在Linux/Unix系统中,对于1次IO读取操作,数据并不会由磁盘/Socket直接拷贝到应用程序的缓存区(用户空间)。

数据的流转顺序为:

磁盘/Socket–>内核空间–>用户空间。

所以,数据读取可以看成2个过程:

  • Waiting for the data to be ready(等待数据到达内核缓冲区)
  • Copying the data from the kernel to the process(从内核缓冲区拷贝数据到应用程序缓冲区)

基础概念

同步和异步

同步和异步关注的是消息通信机制

同步指的是发出1个调用时,在没有得到结果之前,该调用就不返回,即调用者主动等待返回结果。

而异步恰恰相反,发出调用后,会立即返回,调用者无需等待。任务完成后,被调用者将结果通过状态、通知或回调来告知给调用者。

通知调用者的3种方式:

  • 状态,监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率比较低;
  • 通知,被调用者将任务执行完成后,发出通知告知调用者(一般通过消息队列),无需消耗太多性能;
  • 回调,被调用者将任务执行完成后,调用调用者提供的回调函数告知结果。

阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

系统IO模型

阻塞式 I/O 模型(blocking I/O)


在阻塞式 I/O 模型中,应用程序在从调用 recvfrom 开始到它返回有数据报准备好这段时间是阻塞的,recvfrom 返回成功后,应用进程开始处理数据报。

  • 优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用 CPU 资源。
  • 缺点:每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大,这种模型在实际生产中很少使用。

非阻塞式 I/O 模型(non-blocking I/O)


在非阻塞式 I/O 模型中,应用程序把一个套接口设置为非阻塞,就是告诉内核,当所请求的 I/O 操作无法完成时,不要将进程睡眠。而是返回一个错误,应用程序基于 I/O 操作函数将不断的轮询数据是否已经准备好,如果没有准备好,继续轮询,直到数据准备好为止。

  • 优点:不会阻塞在内核的等待数据过程,每次发起的 I/O 请求可以立即返回,不用阻塞等待,实时性较好。
  • 缺点:轮询将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低,所以一般 Web 服务器不使用这种 I/O 模型。

I/O 复用模型(I/O multiplexing)


在 I/O 复用模型中,会用到 Select 或 Poll 函数或 Epoll 函数(Linux 2.6 以后的内核开始支持),这两个函数也会使进程阻塞,但是和阻塞 I/O 有所不同。这两个函数可以同时阻塞多个 I/O 操作,而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。

  • 优点:可以基于一个阻塞对象,同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程),这样可以大大节省系统资源。
  • 缺点:当连接数较少时效率相比多线程+阻塞 I/O 模型效率较低,可能延迟更大,因为单个连接处理需要 2 次系统调用,占用时间会有增加。

信号驱动式 I/O 模型(signal-driven I/O)


在信号驱动式 I/O 模型中,应用程序使用 Socket 进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。

  • 优点:线程并没有在等待数据时被阻塞,可以提高资源的利用率。
  • 缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知。

信号驱动 I/O 尽管对于处理 UDP 套接字来说有用,即这种信号通知意味着到达一个数据报,或者返回一个异步错误。

但是,对于 TCP 而言,信号驱动的 I/O 方式近乎无用,因为导致这种通知的条件为数众多,每一个来进行判别会消耗很大资源,与前几种方式相比优势尽失。

异步 I/O 模型(asynchronous I/O)


由 POSIX 规范定义,应用程序告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到应用程序的缓冲区)完成后通知应用程序。这种模型与信号驱动模型的主要区别在于:信号驱动 I/O 是由内核通知应用程序何时启动一个 I/O 操作,而异步 I/O 模型是由内核通知应用程序 I/O 操作何时完成。

  • 优点:异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠。
  • 缺点:要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O。而在 Linux 系统下,Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 IO 复用模型模式为主。

五种I/O模型总结


这五种 I/O 模型中,前四种属于同步 I/O,因为其中真正的 I/O 操作(recvfrom)将阻塞进程/线程,只有异步 I/O 模型才与 POSIX 定义的异步 I/O 相匹配。

IO多路复用的3种实现

Select

运行机制

select是通过Long型数组fd_set来实现的,fd_set种的每个元素均与1个文件描述符(Socket、文件句柄、设备句柄等)绑定,绑定过程由应用程序负责。当应用程序执行select()系统调用时,内核会根据IO状态来修改fd_set中的元素,由此来通知应用进程哪一个文件或Socket可读。

select的优势在于可以通过单个线程来管理多个文件或Socket请求,而在同步阻塞模型中,必须通过多线程的方式来实现该目的。

劣势

  • 每次调用select,均需要将fd_set由用户态拷贝到内核态,当fd_set元素较多时,拷贝开销较大;
  • 每次调用select,内核均需要遍历fd_set中的所有元素,当fd_set元素较多时,遍历开销较大;
  • 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且该阈值是基于宏来控制的,大小不可改变(32位系统限制为1024,64位系统限制为2048)。

Poll

运行机制

poll本质上和select没有区别,依然需要进行数据结构的复制,依然是基于轮询来实现,但区别就是,select使用的是fd数组,而poll则是维护了一个链表,所以从理论上,poll方法中,单个进程能监听的fd不再有数量限制。但是轮询,复制等select存在的问题,poll依然存在。

EPoll

运行机制

epoll就是对select和poll的改进了。它的核心思想是基于事件驱动来实现的,就是给每个fd注册一个回调函数,当fd对应的设备发生IO事件时,就会调用这个回调函数,将该fd放到一个链表中,然后由客户端从该链表中取出一个个fd,以此达到O(1)的时间复杂度。

epoll操作实际上对应着有三个函数:epoll_create,epoll_ctr,epoll_wait。

  • epoll_create

epoll_create相当于在内核中创建一个存放fd的数据结构。在select和poll方法中,内核都没有为fd准备存放其的数据结构,只是简单粗暴地把数组或者链表复制进来;而epoll则不一样,epoll_create会在内核建立一颗专门用来存放fd结点的红黑树,后续如果有新增的fd结点,都会注册到这个epoll红黑树上。

  • epoll_ctr

另一点不一样的是,select和poll会一次性将监听的所有fd都复制到内核中,而epoll不一样,当需要添加一个新的fd时,会调用epoll_ctr,给这个fd注册一个回调函数,然后将该fd结点注册到内核中的红黑树中。当该fd对应的设备活跃时,会调用该fd上的回调函数,将该结点存放在一个就绪链表中。这也解决了在内核空间和用户空间之间进行来回复制的问题。

  • epoll_wait

epoll_wait的做法也很简单,其实直接就是从就绪链表中取结点,这也解决了轮询的问题,时间复杂度变成O(1)。

水平触发和边缘触发

水平触发的意思就是说,只要条件满足,对应的事件就会一直被触发。所以如果条件满足了但未进行处理,那么就会一直被通知。

边缘触发的意思就是说,条件满足后,对应的事件只会被触发一次,无论是否被处理,都只会触发一次。

而对于select和poll来说,其触发都是水平触发。而epoll则有两种模式:·EPOLLLT和EPOLLET。

  • EPOLLLT(默认状态):也就是水平触发。在该模式下,只要这个fd还有数据可读,那么epoll_wait函数就会返回该fd
  • EPOLLET(高速模式):也就是边缘触发。在该模式下,当被监控的fd上有可读写事件发生时,epoll_wait会通知程序去读写,若本次读写没有读完所有数据,或者甚至没有进行处理,那么下一次调用epoll_wait时,也不会获取到该fd。这种效率比水平触发的要高,系统中不会充斥着大量程序不感兴趣的fd,不感兴趣直接忽视就行,下次不会再触发。

总结

  • select,poll是基于轮询实现的,将fd_set从用户空间复制到内核空间,然后让内核空间以poll机制来进行轮询,一旦有其中一个fd对应的设备活跃了,那么就把整个fd_set返回给客户端(复制到用户空间),再由客户端来轮询每个fd的,找出发生了IO事件的fd;
  • epoll是基于事件驱动实现的,加入一个新的fd,会调用epoll_ctr函数为该fd注册一个回调函数,然后将该fd结点注册到内核中的epoll红黑树中,当IO事件发生时,就会调用回调函数,将该fd结点放到就绪链表中,epoll_wait函数实际上就是从这个就绪链表中获取这些fd;
  • epoll分为EPOLLLT(水平触发,默认状态)和EPOLLET(边缘触发,效率高);
  • 并不是所有的情况中epoll都是最好的,比如当fd数量比较小的时候,epoll不见得就一定比select和poll好。

参考文献

  • https://elijahte.gitee.io/2021/01/07/Unix-IO模型与线程模型/
  • https://developer.aliyun.com/article/763247
  • https://cloud.tencent.com/developer/article/1005481
  • https://www.jianshu.com/p/397449cadc9a

聊一聊I/O那些事儿相关推荐

  1. [聊一聊系列]聊一聊移动调试那些事儿

    欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码): https://segmentfault.com/blog/frontenddriver 随着移动端的 ...

  2. 聊一聊回收科技那些事儿

    根据数据统计,每年在全球范围内产生约13亿吨的废弃物:到2025年,可能会增加到每年22亿吨:而预计到2050年,这一数量将增加70%.因此废物管理已成为各国密切关注的问题,民众与企业也越来越注重绿色 ...

  3. 聊一聊随机数安全那些事儿

    0x00 简介 和朋友聊到一个比较有意思的现象,在最近两年的校招面试中,大部分同学连一点基础的密码学知识都没有, 即便是有一些渗透功底的同学. 所以这里想和大家聊一些简单的密码学基础知识,不涉及算法实 ...

  4. 聊一聊网络营销那些事儿

    说起网络营销,相信很多朋友都不会陌生了,入门学网络营销推广渠道主要有哪些?初学者知识?2019年互联网的快速发展,让网络推广渠道也越来越丰富,但是对于刚做推广的新手来说,一定还是不知道如何进行,今天网 ...

  5. pmp每日三题(2022年2月18日)

    今日三题答案-BCC 今晚20:00,班班和大家一起直播聊一聊冲刺备考那些事儿,请大家尽量参加,会提前发直播链接出来,直播后也会有回放~ 1.对一个关键项目的要求是产品的持续可追溯性,质量团队建议在制 ...

  6. 影像篡改与识别(一):胶片时代

    现如今,影像篡改伪造已经越来越常见,一些恶意的行为所带来的安全问题也越来越严重,如何有效地鉴别影像真伪成为了一个迫切需要解决的问题.鉴于此,云鼎实验室近年来一直持续在该领域上投入,协助腾讯慧眼产品提升 ...

  7. 从就业看高考工科志愿填报——芯片行业篇

    [行业名称]芯片行业 [行业薪酬评级]五星 [行业前景]朝阳行业 [就业难度]容易 [对口专业]微电子科学与工程.集成电路设计与集成系统.其他电子信息大类相关专业 摘要:俗话说,女怕嫁错郎,人怕入错行 ...

  8. 聊一聊WEB前端安全那些事儿

    转载自:https://segmentfault.com/a/1190000006672214?utm_source=weekly&utm_medium=email&utm_campa ...

  9. [聊一聊系列]聊一聊移动web分辨率的那些事儿

    欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码): https://segmentfault.com/blog/frontenddriver 不同于PC时 ...

最新文章

  1. 深度学习 英文 训练阶段_半监督深度学习训练和实现小Tricks
  2. freeRtos学习笔(1)内核剪裁
  3. 获取另一个驱动的设备结构体_Linux 驱动开发 / 设备模型快速入门
  4. 2022考研 【理工专场讲座(新政策分析、专业分析、复习建议)】
  5. C语言:一种通用的程序设计语言
  6. db link hang的解决方法
  7. javascript 常用方法 解析URL,补充前导字符, 省市联动, 循环替换模板
  8. IOS开发 百度语音实现播报及IOS12.1后的播报功能问题与实现
  9. springboot跨域处理
  10. 计算机系统结构模拟试卷3,计算机系统结构全真模拟试卷.pdf
  11. pytorch 预训练模型
  12. GTK+ GUI库使用现状
  13. 联通查询套餐及名下联通卡
  14. 微信小程序登录-PHP后端
  15. AltiumDesigner如何绘制PCB封装
  16. 【云和恩墨】zData Light 敏捷助力威海市立医院核心系统性能提升
  17. JavaEE项目bug修复记——一场由特殊空字符(160号ASCII码)引发的血案
  18. 教你快速高效接入SDK——手游聚合SDK的总体思路和架构
  19. 什么是TPM? Windows 11 是否会重蹈 Windows Vista 覆辙?
  20. java逻辑题_Java蓝桥杯——逻辑推理练习题

热门文章

  1. c语言实现人民币转换成大写中文数字
  2. 百度违规屏蔽关键词判定标准查询工具
  3. 2021年金属非金属矿山安全检查(露天矿山)考试技巧及模拟考试题
  4. 清理蓝藻的机器人_水上清污机器人:自动巡航,漂浮物打捞率达99%以上丨寻访黑科技...
  5. 树莓派4B入手体验及配置
  6. 用C++实现的高性能WEB服务器
  7. 记一次 mac电脑 parallels desktop win10 虚拟机配置rsync文件同步功能(含mac 无法读写ntfs移动硬盘的解决方法)
  8. Jenkins 持续集成环境快捷部署
  9. 这就是波士顿动力第一款商用产品「机器狗」Spot
  10. C/C++数据结构(四) —— 栈