应用程序 I/O 接口

5.1.3 说设备独立性软件”向上提供统一接口”,这一节讲这个接口具体长什么样。

关键在于:统一不等于只有一种。 设备之间的差异大到无法用单一接口覆盖——磁盘能定位、键盘不能,网络还要处理连接——因此接口按设备的根本属性分成三类。

机制

字符设备接口

字符设备以字符为单位收发数据,不可寻址,因此接口只能提供”取下一个""送下一个”这种形式:

get 与 put 操作——一次读写一个字符,没有位置参数,因为字符设备根本没有”位置”这个概念。

由于设备速度与程序处理速度相差悬殊,字符设备通常还配有一个字符缓冲区,允许程序一次读写一串字符,而不必每个字符都陷入内核一次。

块设备接口

块设备以数据块为单位、可寻址,因此接口可以带位置:

read / write / seek——seek 用来修改读写指针的位置,这正是”可寻址”带来的能力。

块设备接口的核心价值是隐藏磁盘的物理复杂性:上层只需说”读第 100 块”,至于它在第几柱面、第几磁头、第几扇区,由驱动程序去换算。用户程序完全看不到磁盘有盘面、磁道这回事。

内存映射文件接口是块设备接口的另一种形态:把磁盘上的文件映射到内存地址空间,程序像访问内存一样访问文件,用访存指令代替 read/write 调用。它的机制与 3.2.7 内存映射文件完全一致。

网络设备接口

网络 I/O 与前两者的差别在于它需要先建立连接、且通信双方是对等的,因此单独提供一类接口:网络套接字接口(socket)。

它的典型用法是:创建套接字 → 将本地套接字连接到远程地址 → 收发数据 → 关闭。 操作系统还提供 select 这类调用,用来管理多个套接字、判断哪些已经有数据可读——这正是网络接口区别于前两者的地方:一个程序常常要同时应付很多条连接。

阻塞 I/O 与非阻塞 I/O

这是与上面三类接口正交的另一个维度,描述的是”调用之后进程会不会被挂起”:

阻塞 I/O——应用程序发出 I/O 系统调用后,进程转为阻塞态等待,直到 I/O 完成才被唤醒并返回。

非阻塞 I/O——应用程序发出 I/O 系统调用后,系统调用立即返回,进程不被挂起;返回值告诉程序”实际完成了多少”(可能是 0)。

两者各有适用场景:阻塞 I/O 的编程模型简单——一句 read 返回时数据一定就绪,代码顺序写下来即可;非阻塞 I/O 让进程在等待期间还能做别的事,代价是程序要自己不断检查或重试,逻辑复杂得多。

边界

三类接口的划分依据是设备的根本属性

不要把三类接口记成”三种设备”的清单,它们的划分是有内在依据的:

字符设备接口没有位置参数,是因为字符设备不可寻址;块设备接口有 seek,是因为块设备可寻址;网络接口需要连接管理与多路复用,是因为网络通信是双向、对等、多连接的。

这条依据一旦立住,接口有哪些操作就是推出来的,不用背。

阻塞 I/O 与”程序直接控制方式”完全不同

这两个概念都涉及”等待”,但一个是应用层的接口语义,一个是硬件层的控制方式,毫无关系。

阻塞 I/O 说的是”进程要不要被挂起”,属于系统调用的语义。程序直接控制方式说的是”CPU 用什么方式获知设备就绪”,属于硬件控制方式。

两者甚至在”进程状态”上的结论是相反的:阻塞 I/O 会让进程进入阻塞态;程序直接控制方式下进程反而一直处于运行态(占着 CPU 轮询)。

判据:看这个词描述的是”进程”还是”CPU 与设备之间的交互”。

应用程序为什么必须经由系统调用访问设备

这一条解释了本节这些接口存在的根本原因:不是为了方便,而是为了保护。

I/O 设备是系统资源。若用户程序能够直接读写设备寄存器,就能绕过一切检查——覆写别的进程正在使用的设备、读走不属于自己的数据、甚至通过误配置让设备失控。

因此操作系统把 I/O 指令设为特权指令,只能在内核态执行;用户态既不能执行 I/O 指令,也不能访问 I/O 端口所在的地址空间。用户程序想做 I/O,唯一的通路是发出系统调用。

完整的通路因此是固定的:

用户程序 → 系统调用 → 内核检查权限与参数 → 设备独立性软件 → 设备驱动程序 → 设备控制器 → 设备

这条通路上”检查”发生在最前面,“操作硬件”发生在最后面,中间隔着好几层——这正是本节三类接口必须存在的理由:它们是用户程序唯一被允许使用的入口。

阻塞 I/O 不等于同步 I/O

这两个词经常被混用,但严格说切分的角度不同。

阻塞 / 非阻塞关注的是调用会不会让进程挂起。同步 / 异步关注的是数据准备好之后由谁来完成搬运、以及应用如何得知完成。

一个非阻塞的调用仍然可能是同步的:调用立即返回”还没好”,但程序仍要自己再来问一次、并自己完成后续处理。真正的异步 I/O 是”发出请求后就不管了,系统完成全部工作后再通知应用”。

408 中对这一组的要求不深,掌握”阻塞与否看进程挂不挂起”即可。

对照速查

接口典型操作为什么是这些操作
字符设备接口get / put不可寻址,只能取下一个
块设备接口read / write / seek可寻址,故有 seek
网络设备接口套接字 socket、select需建立连接、常要同时应付多条连接
阻塞 I/O非阻塞 I/O
调用后进程转为阻塞态立即返回
返回时数据一定就绪返回”实际完成了多少”
编程简单复杂,要自己重试
易混两对判据
阻塞 I/O vs 程序直接控制前者说进程挂不挂起,后者说 CPU 怎么获知就绪
阻塞/非阻塞 vs 同步/异步前者看进程挂不挂起,后者看谁完成搬运与如何通知

考点

  • 三类接口及其操作,划分依据是可否寻址与是否需要连接管理
  • 字符设备接口无位置参数;块设备接口有 seek
  • 内存映射文件接口属于块设备接口的一种形态
  • 阻塞 I/O 使进程进入阻塞态;程序直接控制方式下进程仍是运行态(易混)
  • 阻塞/非阻塞与同步/异步是两个不同的切分角度
  • I/O 指令是特权指令;用户程序访问设备的唯一通路是系统调用

链接