操作系统I/O模型及轮询技术演变

本文深入探讨了操作系统I/O模型的演进,包括同步阻塞I/O、同步非阻塞I/O、I/O多路复用(如select、poll、epoll)、信号驱动I/O和异步I/O模型。并通过白话风格的故事,形象地解释了各种模型的特点和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本来是想要介绍Node.js的轮询技术,但是在这之前先了解下操作系统I/O模式的演进,有助于之后的一些理解,操作系统对于I/O的处理通常是有两步

  • 应用程序先发起系统调用待数据就绪
  • 将数据从内核缓冲区拷贝到应用缓冲区

Github技术博客: Node.js技术栈

快速导航

面试指南

  • I/O复用轮询技术select和epoll的区别?,参考:#

同步阻塞IO

从应用程序开始系统调用->数据就绪,进行拷贝->拷贝结束,这之间应用程序都处于等待状态,不能做其它事情,直到将数据拷贝到用户空间或出错才返回,我们称之为阻塞I/O模式。

同步非阻塞IO

相比于同步阻塞I/O模式,同步非阻塞I/O在每次调用之后,如果数据没有就绪就会立即返回,之后重复调用来检查I/O操作是否就绪,这对CPU资源是一个极其浪费的操作,直到数据就绪将数据从内核拷贝到用户空间,返回成功指示给到应用程序。

Read:就是一种实现,通过重复轮询I/O来判断。

IO多路复用

链接(Socket)并发大的情况,上面两种就不适合了,前面一个处理不完,后面就只能干等,这里就用到了I/O多路复用,下图所示相比较前两种,分为了两步,先进行select数据就绪后,在调用recvfrom进行真正的I/O读写操作。它的高级之处还在于能够一个线程同时处理多个Socket。

I/O多路服用有多种实现模式:selectpollepollkqueue

  • select

通过轮询检查在文件描述符上设置的标识位来进行判断,Select的轮询相当于在数据库中查找一条记录没有建立索引,对所有的Socket进行全部遍历,这对CPU是浪费的。另外Select还有一个限制,对于单个进程所能打开的文件描述符最大只能是1024,那么基于Select的轮询技术最多也只能很好的处理1000并发的吞吐量,可以查看上一个10年,著名的C10K并发连接问题

  • poll

poll和select在实现上没有本质的区别,相比较select,poll基于链表来实现,没有了最大链接1024的限制。但是当文件描述符多了之后,每次调用都会对链接进行线性遍历,性能还是十分低下的。

  • epoll

是linux下效率最高的I/O事件通知机制,没有最大链接限制,通过callbak回调通知机制,不在是每次调用都对链接进行线性遍历,这样就不会随着文件描述符的增加导致效率下降。

在1GB内存的机器上能监听大约10万个端口,远超过select的1024限制,具体可以在服务器上查看cat /proc/sys/fs/file-max

  • kqueue

与epoll类似,仅存于FreeBSD(一种类UNIX操作系统)。

信号驱动IO

仅在Unix上支持,与I/O多路复用相比避免了select的阻塞轮询。应用程序进行系统调用后立即返回,处理其它事物,在数据就绪之后系统会发送一个SIGIO信号到应用程序,应用进程开始读取数据。

异步IO模型

异步I/O模型是目前最理想的一种形式,应用程序发起系统调用后无需等待直接返回当前调用状态,进行后续的其它任务,结果由内核完成I/O操作之后通过回调通知到我们的应用程序,中间没有阻塞过程。

在Linux2.6之后增加了异步I/O的实现方式 AIO,但是很少系统能够实现。

select和epoll的区别

如果问到轮询技术的实现一般也会考察select和epoll的区别

  • 在操作方式上select采用了线性遍历来查找,链接多了之后可以想象一下在一个诺大的数组中每次通过遍历来锁定一个链接,是多么的消耗性能。epoll则不需要遍历,采用的是回调机制,可以看作一个HashTable,来锁定一个对象是很快的。对于文件描述符(最大连接数)select限制为1024,epoll则没有这个限制,通常在1G内存的机器上所能支持的连接数为10W左右。
  • 操作系统支持上来看,目前流行的高性能Web服务器Nginx是基于epoll来实现高并发,当然如果你的链接很小的情况下区别还是不大的select也能满足,如果是大流量、高并发情况epoll目前还是首选模型。

白话风格

白话风格(小明与妹子的邂逅)讲解操作系统I/O模型及轮询技术演变

故事标题:小明与妹子的邂逅
情节介绍:小明在校园一次文艺晚会上邂逅了一位妹子,在只得知妹子名字、手机号的情况下,经过几天的苦苦追寻,历经千山万水,终得美人归!
演员介绍:男一号@小明、女一号@妹子、串场@门卫大爷

  1. 同步阻塞I/O模式

小明电话相约妹子在校门口,然后小明很专一、不见到妹子不回家,期间没有做任何事情,一直在等待!

  1. 同步非阻塞I/O模式

小明电话相约妹子在校门口,妹子还没准备好(出门前化妆几小时。。。),这时候的小明很执着,每隔一会儿给妹子发个信息直到妹子准备好了。

  1. I/O多路复用模式

    1. select 小明电话相约妹子在校门口,委托门卫select大爷帮忙,select大爷很敬业每出去一个人都会进行询问,但是select大爷有个限制最多只能询问1024个。
    2. poll poll类似于select功能,不同的是poll大爷没有1024限制,可以一直坚持,但是当poll大爷超过1024,询问的越来越多之后就显得越来越精疲力尽了。
    3. epoll 小明电话相约妹子在校门口,委托门卫epoll大爷帮忙,epoll大爷不在是每个询问,规定每个人出入校门必须带上学生证,这样opoll大爷就是知道哪个是小明的女神了,epoll大爷找到女神之后在电话通知小明。
  2. 信号驱动I/O模式

小明电话相约妹子在校门口,此时妹子回复说我还没准备好(出门前化妆几小时。。。),这个时候小明也没去,而是先去干其它事情了,等妹子准备好之后电话通知小明,我已经准备好了,小明这个时候才去校门口等着和妹子的约会。

  1. 异步I/O模式

小明告诉妹子我们在校园门口相约,之后小明没有在那干等了,而是先回宿舍休息会或者和朋友在打会球等等,妹子到校门口之后电话通知小明,我已经来啦。

下一节Node.js中的EventLoop

作者:五月君
链接:www.imooc.com/article/285…
来源:慕课网
Github: Node.js技术栈

参考指南
<think>嗯,用户这次想了解早期操作系统对I/O资源的抽象方式,需要结合之前讨论的CPU和内存抽象逻辑来延伸。用户应该是在系统学习操作系统演进史,上次详细解释了进程和地址空间抽象后,ta现在想补全I/O部分的认知版图。用户提供的参考资料[1][2][3]正好聚焦I/O抽象和文件系统,其中[2]明确提到"设备无关性"接口是关键,而[4]补充了文件管理的具体实现细节。看来需要重点解释设备控制器抽象层和文件概念的演变——这两者其实是相互关联的:文件作为持久化I/O的抽象,设备控制器作为实时I/O的抽象。注意到用户特别强调"早期现代操作系统",应该限定在Unix诞生前(1970年代前)的技术。当时最典型的方案是IBM为S/360开发的I/O子系统:通过通道控制器(channelcontroller)隔离设备差异,应用程序只需调用标准宏指令如READ/WRITE[^2],这正是设备驱动程序的雏形。有趣的是,磁带机和磁盘那时已经采用块设备抽象,但交互式终端仍被视为字符流设备[^3]。内存映射文件这种高级抽象在当时还没出现,但文件描述符的概念已在Multics系统萌芽。用户可能不知道,早期程序员甚至要手动计算磁盘柱面地址,设备驱动抽象彻底解放了生产力[^1]。需要把这个对比讲清楚——就像之前解释基址寄存器如何解放物理内存那样。最后生成的问题要有递进性:第一个问题夯实基础(设备控制器原理),第二个跳到性能影响(毕竟用户上次对调度算法很关注),第三个自然衔接到现代扩展(SPOOLing技术其实在1960年代的批处理系统就出现了)。</think> 在早期现代操作系统中,对 I/O 设备的抽象主要体现在以下两个关键逻辑形式上: --- ### 一、设备控制器的硬件抽象层 操作系统通过 **设备控制器(Device Controller)** 实现物理设备的统一接口: 1. **设备无关性** - 所有设备的差异(如磁盘、打印机、键盘)被封装在设备驱动程序中,向操作系统提供统一的**标准化接口** - 应用程序只需调用 `read()`, `write()`, `open()` 等通用系统调用(例如引用[3]的 `write(fd, buffer, nbytes)`),无需关心设备硬件细节[^2][^3] - 例如: ```c // 应用程序统一接口 count = write(device_fd, data, size); // 适用于磁盘/打印机等设备 ``` 2. **中断与轮询的统一处理** - 设备控制器将硬件信号(如数据传输完成)转换为**标准化中断**发送给 CPU - 操作系统内核的中断处理程序统一管理这些事件,避免用户程序直接与硬件交互[^2] 3. **命令与状态寄存器的抽象** - 设备控制器提供**逻辑寄存器组**(Command, Status, Data 等),向操作系统暴露标准寄存器接口 - 物理设备的电气特性被完全屏蔽[^2] --- ### 二、文件系统的抽象模型 I/O 设备被抽象为 **文件(File)** 这一核心逻辑概念: 1. **设备文件化** - 磁盘、打印机等物理设备映射为 `/dev` 目录下的设备文件(如 Unix 的 `/dev/sda`, `/dev/lp0`) - 应用程序可通过标准文件操作(`open/read/write/close`)访问设备(引用[3][4]) 2. **数据流模型** - 设备被抽象为 **字节流(Byte Stream)**: - 顺序设备(磁带):连续字节流 - 随机设备(磁盘):支持 `seek()` 的随机字节流 - 例如磁盘扇区的物理结构被隐藏,用户仅操作字节偏移量[^2][^4] 3. **命名与目录树** - 文件系统提供树形命名空间(如引用[4]的“路径”概念) - 设备文件通过路径名定位(如 `/dev/ttyS0` 对应串口设备) 4. **保护机制** - 通过文件属性(如引用[4]的“保护信息”)实现访问控制 - 例如权限位 `rwxr--r--` 限制设备访问 --- ### 三、核心抽象技术的数学表达 设物理设备集合为 $\mathcal{D}$,逻辑设备接口为 $\mathcal{L}$,则抽象过程可表述为映射函数: $$ \Phi: \mathcal{D} \times \mathbb{H} \to \mathcal{L} $$ 其中: - $\mathbb{H}$ 为硬件寄存器空间 - $\mathcal{L}$ 满足: - **统一性**: $\forall d_i,d_j \in \mathcal{D}, \quad \texttt{read}(d_i) \equiv \texttt{read}(d_j)$ - **隔离性**: $\Phi(d_i) \cap \Phi(d_j) = \emptyset \quad (i \neq j)$ - **虚拟性**: $|\mathcal{L}| > |\mathcal{D}|$(通过多路复用支持更多逻辑设备) --- ### 抽象的价值与局限 **创新意义**: - 设备无关性降低开发复杂度(引用[2]) - 文件模型统一存储器和外设访问(引用[4]) - 中断机制实现 CPU 与 I/O 的并行(并发性引用[1]) **历史局限**: - 早期缺少缓存机制(如 UNIX 的块设备缓存) - 同步 I/O 导致进程阻塞(异步 I/O 尚未普及引用[3])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值