我的理解是,硬件架构和操作系统的设计不会阻塞CPU。当需要发生任何类型的阻塞操作时,操作系统会记录中断并继续执行其他操作,以确保始终有效地利用 CPU 的宝贵时间。
这让我想知道为什么大多数编程语言都是用阻塞 API 设计的,但最重要的是,由于操作系统在 IO 方面以异步方式工作,注册中断并在以后准备好时处理结果,我真的很困惑我们的编程语言 API 如何摆脱这种异步。操作系统如何使用阻塞 API 为我们的编程语言提供同步系统调用?
这种同步从何而来?当然不是在硬件级别。那么,在触发一些中断之前,我不知道旋转和旋转的地方是否存在无限循环?
您的观察是正确的 - 操作系统与底层硬件异步交互以执行 I/O 请求。
阻塞I/O 的行为来自线程。通常,OS 提供线程作为抽象供用户模式程序使用。但有时,绿色/轻量级线程是由用户模式虚拟机提供的,如 Go、Erlang、Java(Project Loom)等。如果您不熟悉线程作为抽象,请阅读任何操作系统教科书中的一些背景理论。
每个线程都有一个状态,该状态由一组固定的寄存器、一个动态增长/收缩的堆栈(用于函数参数、函数调用寄存器和返回地址)和一个下一个指令指针组成。阻塞 I/O 的实现方式是,当线程调用 I/O 函数时,托管该线程的底层平台(Java VM、Linux 内核等)立即挂起该线程,使其无法调度执行,并将 I/O 请求提交到下面的平台。当平台收到 I/O 请求的完成时,它会将结果放在该线程的堆栈上,并将线程放在调度程序的执行队列中。这就是魔术的全部。
为什么线程很受欢迎?好吧,I/O 请求发生在某种上下文中。您不只是读取文件或将文件作为独立操作写入;读取文件,运行特定算法来处理结果,并发出进一步的 I/O 请求。话题是跟踪进度的一种方法。另一种方式称为"延续传递样式",每次执行 I/O 操作 (A) 时,都会传递一个回调或函数指针以显式指定 I/O 完成后需要发生的情况 (B),但调用 (A) 会立即返回(非阻塞/异步)。这种异步 I/O 编程方式被认为很难推理,甚至更难调试,因为现在您没有有意义的调用堆栈,因为它在每次 I/O 操作后都会被清除。这在伟大的文章"你的功能是什么颜色?
请注意,平台没有义务向其用户提供线程抽象。操作系统或语言 VM 可以很好地向用户代码公开异步 I/O API。但是绝大多数平台(Node.js除外)选择提供线程,因为人类更容易推理。
我的理解是,硬件架构和操作系统的设计不会阻塞CPU。
任何合理设计的操作系统都会有一个系统服务接口,可以按照你所说的去做。但是,有许多非理性操作系统在进程级别不以这种方式工作。
阻塞 I/O 比非阻塞 I/O 更易于编程。让我举一个来自VMS操作系统的例子(Windoze在幕后的工作方式相同)。VMS有一个名为SYS$QIO和SYS$QIOW的系统服务。即队列 I/O 请求和队列 I/O 请求并等待。系统服务具有相同的参数。一对参数是完成例程的地址和该例程的参数。但是,这些参数很少用于 SYS$QIOW。
如果您执行 SYS$QIO 调用,它会立即返回。当 I/O 操作完成时,完成例程将作为软件中断调用。然后,您必须在应用程序中执行中断编程。我们一直在这样做。如果您希望应用程序能够同时读取 100 个输入流,这就是您必须这样做的方式。这比使用一台设备进行简单的阻塞 I/O 要复杂得多。
如果编程语言将这样的回调系统合并到其I/O语句中,它将镜像VMS/RSX/Windoze。Ada 使用任务概念以独立于操作系统的方式实现此类系统。
在太监的世界里,传统上为每个设备创建一个单独的过程。这更简单,直到您必须读取和写入每个设备。