Skip to content

输入输出应用程序接口

2026 大纲 五(一)4 输入/输出应用程序接口,补充说明点名四项:字符设备接口块设备接口网络设备接口阻塞/非阻塞 I/O

统一到什么程度才算统一

上一节的分层给出了一个目标:换设备不改应用。这一节讲它在最上面那一层 长什么样——内核到底给应用程序开了哪些口子。

理想当然是"全部统一成一套函数"。但真做起来会立刻撞到一个事实: 有些差异是语义层面的,抹不掉。

字符设备不可寻址,你没法对键盘说"从第 100 个字符开始读"; 块设备可随机访问,read 必须能带偏移量; 网络更麻烦——它有连接这个概念(要先建立才能收发)、 一个进程可能同时开着几百条连接、而且报文本身就可能丢

硬把三者塞进一套函数,结果是每个参数都得留一堆"这种设备不用填"的坑。 所以实际做法是统一调用形式、分开语义单位:三套接口长得像,但各自认自己的单位。

这一节还要分清一组同名的东西。 open/read/write 这几个名字, 在本篇指的是内核向应用开放的系统调用,而在 设备驱动程序接口里指的是驱动向内核注册的函数指针—— 同名,但分属两条完全不同的边界

最后是阻塞、非阻塞、异步这三个词。它们的差别不在"快慢", 而在两处很具体的地方:会不会被切走,以及谁把数据搬完

一、为什么"统一"里还要分三套

统一接口的目标是应用程序用同一种调用形式访问所有设备,Unix/Linux 把它推到极致叫"一切皆文件"——普通文件、设备、管道、socket 都通过文件描述符访问。但调用形式能统一,语义单位统一不了

read(fd, buf, 100) 对键盘意味着"给我 100 个字符,来一个算一个"; 对磁盘意味着"从第 k 块开始给我 100 字节,我可以随时跳到第 900 块去读"; 对网卡意味着"从这条已建立的连接上收 100 字节,对端还可能随时断开"。

接口适用设备语义单位能不能寻址典型调用
字符设备接口键盘、鼠标、打印机、串口、终端单个字符(字节流)不能get/put(每次一个字符);库层再封装成行输入
块设备接口磁盘、SSD、U 盘数据块(512 B~4 KB)(每块有块号)read/write 指定块号;另有内存映射文件接口
网络设备接口网卡报文/字节流,且带连接语义不适用socket/bind/listen/connect/send/recv

字符设备由"不可寻址"派生出两条实际约定:行缓冲——逐字符调系统调用代价太高(每个字符一次用户态/内核态切换),所以终端驱动通常把输入攒成一行再交给应用,这就是在终端敲字符时按回车前程序收不到东西的原因,输入期间用户进程被挂起;输出侧的阻塞条件——进程把一行送入缓冲区后即可继续,若它已有第二行要输出而第一行还没被取完,进程就得阻塞。

网络则单独抽象出 socket(套接字):它是一条通信端点的抽象,创建时指定协议族与类型,绑定本地地址,再连接到对端;之后才退化成"像文件一样"用文件描述符收发。先有连接这层抽象,才谈得上"一切皆文件"。

二、系统调用这一组名字

系统调用功能
open打开设备/文件,返回文件描述符
close关闭,释放描述符与相关内核结构
read从设备读数据到用户缓冲区
write把用户缓冲区数据写向设备
ioctl设备特定的控制操作

这张表与设备驱动程序接口那张长得几乎一样,是因为上层调用被逐层"下传":应用调 read 系统调用 → 设备独立软件查到该设备的驱动 → 调用这个驱动的 read 函数指针

三、阻塞、非阻塞与异步

非阻塞与异步都"立即返回",但返回时数据在哪里、后面还要不要你自己动手完全不同:

非阻塞 I/O异步 I/O
调用立即返回吗
返回时数据传完了吗可能只传了一部分,甚至一点没传没传完,但内核承诺会替你传完
谁负责把数据搬进用户缓冲区进程自己——下次再调 read 接着搬内核——传完了才通知你
进程之后要做什么反复查(轮询),直到凑够需要的数据什么都不用做,等通知(信号/回调/完成事件)
一次逻辑读要调几次可能很多次一次

一句话判据:非阻塞把"等"推给进程自己去轮询;异步把"等"和"搬"都留在内核,只在全部完成时通知一次。

四、统一接口的边界

有了这套接口,应用程序可以用相同的方式访问不同设备,差异被关在下面两层(设备独立软件与驱动)里。代价是:统一接口表达不了的东西,只能靠 ioctl 和 socket 这类"专用出口"补。这也是这一条大纲会把三套接口和阻塞/非阻塞一起点名的原因——它们描述的正是这套"统一"的边界在哪里。

五、设备独立性:应用拿到的是名字,不是设备

统一接口只解决了"函数长什么样",还有一半没解决:应用怎么指名要哪台设备。

如果应用程序里直接写死物理设备名或设备号,那么换一台打印机就要改程序、 本来指定的那台正忙着也只能干等——统一了接口却没统一设备,等于白统一。

所以真正的做法是让应用只说逻辑设备名("我要一台打印机"), 由系统查逻辑设备表(LUT)把它映射到某台具体的物理设备。这就是设备独立性

它一次性买到三样东西:

  • 换物理设备不必修改应用程序——只要改 LUT 里的映射。
  • 同类设备有一台空闲就能用,不必死等某一台。
  • 顺带支持 I/O 重定向——把"标准输出"这个逻辑名重新映射到文件即可。

还有一条常被忽略的推论:可以用文件名访问物理设备。 因为设备在这套体系里同样被表示成一个名字,Unix 干脆把它放进文件系统 (/dev/tty/dev/sda),于是访问设备和访问文件用的是同一组系统调用。

⚠️ 程序员通过系统调用打开 I/O 设备时,用的是逻辑设备名—— 不是物理设备名,也不是主/从设备号。后两者是系统内部用来定位具体硬件的, 应用拿不到、也不该拿到。

考点速记

  1. 统一的是调用形式,分开的是语义单位:字符设备按字符 get/put不可寻址;块设备按块 read/write可随机访问;网络因连接语义、多条并发连接、报文不可靠三条特性而另起 socket。
  2. ⚠️同一组函数名分属两条边界:本篇的 open/read/write内核向应用的系统调用驱动程序接口里的是驱动向内核注册的函数指针ioctl 是统一接口上唯一合法的逃生口。
  3. 阻塞与非阻塞的差别落在进程状态上(会不会被切走),非阻塞与异步的差别落在谁把数据搬完。进程还有别的事可做才用非阻塞/异步,否则阻塞反而提高系统整体吞吐
  4. 设备独立性的落点是"应用只说逻辑设备名 + 系统查 LUT 映射到物理设备"。
  5. ⚠️程序员通过系统调用打开 I/O 设备时使用的是逻辑设备名——不是物理设备名,也不是主/从设备号。
  6. 设备独立性买到的三样:换物理设备不必修改应用程序、同类设备有一台空闲就能用、支持 I/O 重定向
  7. 可以用文件名访问物理设备/dev/tty/dev/sda)——设备在这套体系里同样是一个名字,于是访问设备和访问文件用同一组系统调用。

这一节在真题里被考过的形式

两道题都在考设备独立性,而且是同一条判据的正反两面。

  • 问程序员通过系统调用打开 I/O 设备时使用的设备标识(2009-32)。答逻辑设备名。⚠️ 另三项(物理设备名、主设备号、从设备号)都是系统内部用来定位具体硬件的,应用拿不到也不该拿到——应用一旦写死物理标识,设备独立性就不成立了
  • 判断具备设备独立性的系统的四条叙述,选错误的(2020-30)。错项是"更换物理设备后必须修改访问该设备的应用程序"——这恰恰是设备独立性要消灭的东西,只需改 LUT 里的映射。另三条都对:可以用文件名访问物理设备、用户程序使用逻辑设备名、需要建立逻辑设备与物理设备之间的映射关系。

复习优先级必须拿满,两道题一句话就够——应用只说逻辑名,映射由 LUT 做。 速记第三条(阻塞/非阻塞/异步的两处分界)至今没单独考过,但它在 进程状态与转换那边是相关背景,理解一遍即可。 第二条那个"同名两条边界"是读题时防混淆用的,不会单独设问。

易错:认为应用打开设备时用的是物理设备名或设备号。用逻辑设备名,物理标识是系统内部用的。

易错:认为换了物理设备就得改应用程序。只需改 LUT 的映射——这正是设备独立性的定义。

易错:认为不能用文件名访问设备。可以——设备被放进文件系统,用同一组系统调用访问。

易错:把本篇的 read/write 和驱动接口里的同名函数当成一回事。一个是内核向应用开的口,一个是驱动向内核注册的指针

易错:认为阻塞式 I/O 一定比非阻塞差。进程没有别的事可做时,阻塞反而提高系统整体吞吐(它让出了 CPU)。

教材出处
  • 字符设备输入时缓冲区暂存一行数据、输入期间用户进程被挂起,以及输出侧的阻塞条件:汤小丹《计算机操作系统》6.7.2 节,印刷 p210
  • 系统调用与库函数的关系(C 库函数与系统调用大体一一对应,Win32 API 不一一对应):同书 6.6.1 节,印刷 p205
  • 同步传输即阻塞方式、异步传输即非阻塞方式:孙钟秀、费翔林《操作系统教程》(第 6 版)4.2.1 节,印刷 p129

相关知识

I/O 软件层次结构设备的基本概念与分类设备驱动程序接口缓冲区管理内存映射文件

真题练习