- 文档
- 教程
- 知识库
【免费下载链接】CS-Base
图解计算机网络、操作系统、计算机组成、数据库,共 1000 张图 + 50 万字,破除晦涩难懂的计算机基础知识,让天下没有难懂的八股文!🚀 在线阅读:https://xiaolincoding.com
键盘是我们每天接触最多的输入设备,但很少有人能完整讲清楚:当你敲下字母A的那一刻,操作系统内部到底发生了什么。要回答这个问题,得先理解操作系统是如何屏蔽千差万别的硬件设备、把它们统一管理起来的。本篇技术指南以 图解系统 · 设备管理章节 为核心,结合本仓库中关于中断、文件系统、磁盘调度等模块的配套资料,完整梳理「设备控制器 → I/O 控制方式 → 设备驱动程序 → 通用块层 → 存储 I/O 分层 → 键盘中断处理」这条主线。读完你不仅能讲清楚键盘输入的全链路,还能透彻理解 DMA、中断上下半部、块设备与字符设备、I/O 调度算法这些高频面试点与实战概念。
一、设备控制器:操作系统与设备打交道的桥梁
电脑可以外接非常多输入输出设备,键盘、鼠标、显示器、网卡、硬盘、打印机、音响等,每个设备的用法和功能都各不相同。操作系统不可能为每类设备单独定制一套交互逻辑,于是硬件上引入了一个统一组件——设备控制器(Device Controller):硬盘有硬盘控制器、显示器有视频控制器、键盘有键盘控制器、网卡有网卡控制器。
这些控制器很清楚地知道对应设备的用法和功能,因此CPU 从不直接操作设备,而是通过设备控制器与设备打交道。设备控制器本质上是一块带有芯片的小型处理器板卡:芯片可执行自己的逻辑,同时还带有自己的寄存器,用来与 CPU 通信:
- 通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或执行某些其他操作;
- 通过读取这些寄存器,操作系统可以了解设备的状态,比如是否准备好接收一个新的命令。
1. 控制器里的三类寄存器
设备控制器内部有三类寄存器,分工非常明确:
| 寄存器类型 | 作用 |
|---|---|
| 数据寄存器(Data Register) | CPU 向 I/O 设备写入需要传输的数据。比如要打印的内容是「Hello」,CPU 就要先把H字符发送到对应的 I/O 设备 |
| 命令寄存器(Command Register) | CPU 发送命令,告诉 I/O 设备要进行输入/输出操作,设备接手工作,任务完成后把状态寄存器里的状态标记为「完成」 |
| 状态寄存器(Status Register) | 告诉 CPU 设备当前"正在工作中"还是"工作已完成"。若设备正在工作,CPU 再发送数据或命令都是无效的,必须等状态标记为完成,CPU 才能发送下一个字符和命令 |
从源码结构看,这套"命令 / 状态 / 数据"三寄存器的模型,正是后来所有设备驱动程序访问硬件的基础范式:驱动代码向命令寄存器写命令、轮询或等待状态寄存器、通过数据寄存器搬运数据。
2. 块设备与字符设备
按数据传输的单位,I/O 设备可分为两大类:
- 块设备(Block Device):把数据存储在固定大小的块中,每个块有自己的地址,可以随机访问。硬盘、USB 是常见的块设备;
- 字符设备(Character Device):以字符为单位发送或接收字符流,不可寻址,也没有任何寻道操作。鼠标、键盘是常见的字符设备。
由于块设备通常传输的数据量非常大,控制器为此设立了一个可读写的数据缓冲区:
- CPU 向控制器的缓冲区写入数据时,只有当缓冲区囤够一部分数据,才会发给设备;
- CPU 从控制器的缓冲区读取数据时,也需要缓冲区攒够一部分,才拷贝到内存。
这样做的根本目的是减少对设备的频繁操作——设备的读写是缓慢的物理动作,攒批发送比一次一字节高效得多。
3. CPU 如何访问控制器的寄存器和缓冲区?
存在两种主流方法:
- 端口 I/O(Port I/O):每个控制寄存器被分配一个 I/O 端口,通过特殊的汇编指令操作这些寄存器,例如
in/out指令; - 内存映射 I/O(Memory-Mapped I/O,MMIO):将所有控制寄存器映射到内存地址空间中,这样 CPU 就可以像读写内存一样读写数据寄存器与缓冲区,无需专门指令。
内存映射的方式在现代系统中更为普遍,因为驱动程序可以用普通的指针读写来操控硬件,简化了内核编程模型。
二、I/O 控制方式:轮询、中断与 DMA
设备控制器相当于一个"小 CPU",可以自行处理一些事情。但当 CPU 给控制器下发读取指令后,控制器读完数据时如何通知 CPU,是 I/O 系统设计的核心问题。控制器的状态寄存器一般有标记位来标识输入/输出操作是否完成,由此演化出三种控制方式。
1. 轮询等待(Polling):傻瓜但简单
第一种方法是让 CPU 一直查询状态寄存器,直到状态标记为「完成」。这种方式非常"傻瓜"——它会占用 CPU 的全部时间。在设备响应慢(如磁盘寻道、网络包到达)的场景下,CPU 空转浪费严重。
2. 中断(Interrupt):异步通知
第二种方法引入硬件中断控制器:设备完成任务后触发中断,中断控制器通知 CPU 产生了一个中断,CPU 停下当前手里的事情来处理中断。这彻底解放了 CPU,使其在设备工作期间可以继续执行其他任务。
中断按来源分为两种(详见本仓库 什么是软中断?):
- 软中断:由代码显式触发,例如执行
INT指令; - 硬件中断:由硬件通过中断控制器触发,设备完成 I/O 时就是这种。
3. DMA:让数据"绕过" CPU 直通内存
中断方式对频繁读写数据的磁盘并不友好——设备每完成一次小传输就打一次中断,CPU 频繁被打断、占用大量时间。解决办法是引入DMA(Direct Memory Access,直接内存访问):它允许设备在 CPU 不参与的情况下,自行把 I/O 数据放入内存。要实现 DMA,需要「DMA 控制器」这个硬件组件的支持。
DMA 的工作流程如下:
- CPU 下发指令给 DMA 控制器:告诉它想读取多少数据,读完后把数据放在内存的某个位置即可;
- DMA 控制器向磁盘控制器发指令:通知它从磁盘读数据到其内部缓冲区,接着磁盘控制器把缓冲区数据传输到内存;
- 磁盘控制器发确认信号:当数据拷贝到内存的操作完成后,磁盘控制器在总线上发出一个成功信号到 DMA 控制器;
- DMA 控制器发中断:收到信号后,DMA 控制器向 CPU 发出中断,通知指令完成,CPU 就可以直接使用内存里现成的数据。
可以看到,CPU 读取磁盘数据时只需在传送开始和结束时各参与一次(下发指令 + 接收完成中断),中间大块数据搬运完全由 DMA 控制器与磁盘控制器在总线上完成,CPU 可以返回去做其他事情。这正是现代高速存储与网卡能够实现高吞吐的基础。
三、设备驱动程序:屏蔽控制器差异的软件层
设备控制器虽然屏蔽了"设备本身"的众多细节,但每种设备控制器的寄存器、缓冲区使用模式依然各不相同。为了屏蔽「设备控制器」的差异,操作系统引入了设备驱动程序。
这里需要厘清一个边界:
- 设备控制器属于硬件,不属于操作系统范畴;
- 设备驱动程序属于操作系统的一部分,是面向设备控制器的代码,它发出操控设备控制器的指令后,才可以操作设备控制器;
- 操作系统内核代码可以像本地调用代码一样使用设备驱动程序的接口。
不同设备控制器功能不同,但设备驱动程序会向上提供统一的接口,这样不同设备的驱动程序就可以以相同的方式接入操作系统。这是"屏蔽差异"思想的第二层:第一层由硬件控制器屏蔽"设备差异",第二层由驱动屏蔽"控制器差异"。
中断处理程序与驱动注册
设备完成了事情会发送中断通知操作系统,操作系统必须有一个地方来响应中断——这个"地方"就在设备驱动程序里:驱动程序会及时响应控制器发来的中断请求,并根据中断类型调用相应的中断处理程序。通常,设备驱动程序初始化的时候,要先注册一个该设备的中断处理函数。
中断处理程序的处理流程:
- I/O 时,设备控制器准备好数据后,通过中断控制器向 CPU 发送中断请求;
- 保护被中断进程的 CPU 上下文(保存寄存器现场);
- 转入相应的设备中断处理函数;
- 进行中断处理(读取数据、响应设备等);
- 恢复被中断进程的上下文,让进程从被打断的位置继续执行。
硬中断与软中断:中断的上半部与下半部
中断处理程序要"短且快",因为执行期间可能临时关闭中断,处理程序执行过长会导致其他中断丢失。Linux 为此将中断处理拆成两个阶段(详细图解见 什么是软中断?):
- 上半部(硬中断):由硬件触发,快速处理与硬件紧密相关、时间敏感的事,比如禁止设备再次发中断;
- 下半部(软中断):由内核触发,以内核线程方式延迟处理耗时较长的剩余工作,每个 CPU 对应一个软中断内核线程,名字形如
ksoftirqd/0。
以网卡收包为例:网卡通过 DMA 把数据写入内存后发硬件中断,内核上半部先禁止网卡中断、触发软中断,下半部的软中断处理程序再从内存中按网络协议栈逐层解析数据并送给应用程序。
Linux 中可通过以下文件实时观察中断情况:
cat /proc/interrupts:查看硬中断累计次数;cat /proc/softirqs:查看软中断累计次数(如NET_RX网络接收、NET_TX网络发送、TIMER定时、SCHED内核调度、RCU锁等类型);watch -d cat /proc/softirqs:查看中断次数的变化速率,定位到底是哪种软中断在快速增长。
四、通用块层:统一管理块设备的抽象层
对于块设备,为了减少不同设备之间的差异带来的影响,Linux 通过一个统一的**通用块层(Generic Block Layer)**来管理它们。通用块层处于文件系统和磁盘驱动程序之间,主要有两个功能:
- 向上提供标准接口:为文件系统和应用程序提供访问块设备的标准接口;向下把各种不同的磁盘设备抽象为统一的块设备,并在内核层面提供一个框架来管理这些设备的驱动程序;
- I/O 请求排队与调度:把文件系统和应用程序发来的 I/O 请求排队,对队列重新排序、请求合并,也就是 I/O 调度,目的是提高磁盘读写效率。
Linux 的 5 种 I/O 调度算法
Linux 支持 5 种 I/O 调度算法,各有适用场景:
| 调度算法 | 核心思想 | 适用场景 |
|---|---|---|
| 没有调度算法(None) | 不对 I/O 做任何处理 | 虚拟机 I/O:磁盘 I/O 调度交由物理机系统负责 |
| 先入先出(FIFO) | 先进入 I/O 队列的请求先处理 | 最简单的调度,适用于对公平性无特殊要求的场景 |
| 完全公平(CFQ) | 为每个进程维护一个 I/O 队列,按时间片均匀分布各进程的 I/O 请求 | 大部分系统的默认 I/O 调度器 |
| 优先级调度 | 优先级高的 I/O 请求先发生 | 运行大量进程的系统,如桌面环境、多媒体应用 |
| 最终期限(Deadline) | 分别为读、写请求创建不同队列,确保达到最终期限的请求被优先处理 | I/O 压力较大的场景,如数据库 |
其中,最终期限调度算法尤为值得关注:它把读请求和写请求分开排队,读请求通常被赋予更短的期限,因为读操作直接阻塞依赖它的进程。这种设计既能提高机械磁盘的吞吐量,又能保证临界请求不被饿死,因此在数据库这类高 I/O 压力场景下表现更好。
补充说明:本仓库的 磁盘调度算法 一节还给出了物理层面的磁盘调度(磁头寻道优化)全景,包括先来先服务、最短寻道时间优先(SSF)、扫描算法(SCAN,即"电梯算法")、循环扫描(C-SCAN)以及 LOOK / C-LOOK 优化变体。这里的"通用块层 I/O 调度"与"磁盘磁头调度"是两个不同层面:前者在内核软件层面对 I/O 请求排序合并,后者在硬件设备层面对磁头寻道顺序进行优化,两者协同提升磁盘访问性能。
五、存储系统 I/O 软件分层:三层结构透视
结合文件系统原理,Linux 存储系统的 I/O 由上到下可分为三个层次:文件系统层、通用块层、设备层。
| 层次 | 包含内容 | 职责 |
|---|---|---|
| 文件系统层 | 虚拟文件系统(VFS)和其他文件系统的具体实现 | 向上为应用程序统一提供标准的文件访问接口;向下通过通用块层来存储和管理磁盘数据 |
| 通用块层 | 块设备的 I/O 队列和 I/O 调度器 | 对文件系统的 I/O 请求排队,再通过 I/O 调度器选择一个 I/O 发给下一层的设备层 |
| 设备层 | 硬件设备、设备控制器和驱动程序 | 负责最终物理设备的 I/O 操作 |
关于虚拟文件系统(VFS)的细节,可参见本仓库 文件系统全家桶:VFS 定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员不需要了解文件系统的工作原理,只要掌握 VFS 提供的统一接口即可。
设备在 Linux 下只是一个特殊的文件
有了文件系统接口之后,不但可以通过文件系统的命令行操作设备,也可以通过应用程序调用read、write函数,像读写文件一样操作设备——这就是 Linux 那句名言"一切皆文件"的落地:设备在 Linux 下也只是一个特殊的文件。
但除了读写操作,还需要检查特定于设备的功能和属性,于是有了ioctl接口(Input/Output Control):它是用于配置和修改特定设备属性的通用接口。ioctl是设备管理中最常用的"旁路"通道,设备的专属能力(如设置终端参数、调节声卡音量、查询磁盘容量)都是通过它实现的。
缓存机制:为最慢的环节加速
存储系统的 I/O 是整个系统最慢的一个环节,Linux 为此提供了不少缓存机制来提高 I/O 效率:
- 为了提高文件访问的效率,使用**页缓存(Page Cache)、索引节点缓存(inode cache)、目录项缓存(dentry cache)**等多种缓存机制,目的是减少对块设备的直接调用;
- 为了提高块设备访问的效率,使用**缓冲区(Buffer Cache)**来缓存块设备的数据。
关于二者的关系,本仓库 进程写文件时,进程发生了崩溃,已写入的数据会丢失吗? 有更深入的解读:Page Cache 用于缓存文件的页数据,buffer cache 用于缓存块设备(如磁盘)的块数据。在 Linux 2.4 内核之后,两者近似融合——文件页加载进 Page Cache 后,buffer cache 只需维护块指向页的指针;只有没有文件表示的块、或绕过文件系统直接操作的块,才会真正放到 buffer cache。
文件 I/O 的分类也与缓存机制密切相关:默认的非直接 I/O会经过内核页缓存(write先把数据写入 page cache,把页标记为 dirty,再由内核按时机落盘);而指定O_DIRECT标志的直接 I/O则绕过页缓存,直接经文件系统访问磁盘,读写真正落盘/真正从盘读。非直接 I/O 下内核把脏页刷入磁盘的时机包括:缓存数据过多、用户主动调用sync、内存紧张、缓存时间超限等。
六、键盘敲入 A 字母时,操作系统期间发生了什么?
有了前面的铺垫,现在可以完整回答开头的问题了。先看 CPU 的硬件连接架构:CPU 的内存接口直接与系统总线通信,系统总线接入I/O 桥接器;桥接器一边接入内存总线(CPU 与内存通信),另一边接入 I/O 总线(连接键盘、显示器等 I/O 设备)。
整个过程的完整时序如下:
- 键盘控制器产生扫描码:用户敲下字母键,键盘控制器产生扫描码数据,并将其缓冲在键盘控制器的寄存器中;
- 发送中断请求:键盘控制器通过总线向 CPU 发送中断请求;
- 保存被中断进程的 CPU 上下文:CPU 收到中断请求后,操作系统保存当前正在执行的进程的 CPU 上下文(寄存器现场);
- 调用键盘中断处理程序:该处理程序是在键盘驱动程序初始化时注册的。它的核心功能是:
- 从键盘控制器的寄存器缓冲区读取扫描码;
- 根据扫描码找到用户在键盘上输入的字符;
- 如果输入的是显示字符,就把扫描码翻译成对应字符的ASCII 码(例如敲下字母 A,翻译成字符
A的 ASCII 码 65);
- 放入读缓冲区队列:得到显示字符的 ASCII 码后,将其放入「读缓冲区队列」;
- 显示设备驱动搬运数据:显示设备的驱动程序定时从「读缓冲区队列」读取数据,放入「写缓冲区队列」,再把「写缓冲区队列」中的数据逐个写入显示设备控制器的寄存器中的数据缓冲区,最终将字符显示在屏幕上;
- 恢复被中断进程的上下文:显示完成后,操作系统恢复之前保存的进程上下文,被打断的进程继续运行。
这条链路里隐藏的所有知识点
把上面 7 步与前面的章节一一对应,会发现这条链路几乎用上了设备管理的全部机制:
- 设备控制器:键盘控制器、显示控制器各司其职,它们的寄存器完成扫描码缓冲与数据写入;
- 中断机制:键盘通过硬件中断打断 CPU,中断处理程序短且快——只做"读扫描码 + 翻译 ASCII"的轻量工作,后续的队列搬运交给驱动与设备异步完成,这正是中断"上半部快处理"思想的体现;
- 设备驱动程序:键盘驱动负责注册中断处理函数,显示驱动负责定时把数据从读缓冲区搬到写缓冲区再刷到控制器;
- 缓冲区与队列:读缓冲区队列 / 写缓冲区队列的存在,让字符的到达速率与屏幕的刷新速率解耦,避免设备频繁被打断;
- 上下文保护与恢复:进程被打断时现场被完整保存与恢复,这是操作系统得以"边打字边干其他事"的根本保障。
更宏观地看,这条链路还可以延伸:如果输入的是数据而非显示字符(如密码、文本内容),ASCII 码最终会通过内核的输入子系统进入进程的输入缓冲区,被正在等待read的应用程序读取——于是"一次键盘敲击"最终变成了一次普通的文件式读取,Linux"一切皆文件"的设备抽象在此闭环。
七、总结
本篇文章以「键盘敲入 A 字母」为引,完整梳理了操作系统设备管理的知识体系:
- 设备控制器用三类寄存器(数据 / 命令 / 状态)+ 数据缓冲区屏蔽设备差异,CPU 通过端口 I/O 或内存映射 I/O 访问它们;
- 设备可分为块设备与字符设备,块设备通过缓冲区攒批传输以减少频繁操作;
- I/O 控制方式从轮询演进到中断再到DMA,DMA 让数据在传送期间完全绕过 CPU,仅在开始和结束时各打断一次;
- 设备驱动程序在软件层屏蔽控制器差异、注册中断处理函数,配合中断"上半部 + 下半部"(硬中断 + 软中断)机制保证系统响应性;
- 通用块层统一管理块设备并提供 I/O 调度,Linux 支持无调度、FIFO、完全公平、优先级、最终期限五种算法,按场景各取所需;
- 存储 I/O 按文件系统层 → 通用块层 → 设备层三层分层,设备以特殊文件形式暴露给用户,配合页缓存、目录项缓存与 buffer cache 加速最慢的 I/O 环节。
如果把这一整套知识再放回仓库的完整图谱中:中断机制可与 什么是软中断? 交叉阅读,文件系统层细节见 文件系统全家桶,缓存与 I/O 模型深入分析见 进程写文件时,进程发生了崩溃,已写入的数据会丢失吗?,磁盘磁头调度算法见 磁盘调度算法。设备管理是操作系统五大核心结构(进程管理、内存管理、文件系统、设备管理、网络系统)的底座之一,吃透这条主线,后续理解网络系统里的零拷贝、I/O 多路复用(参见 I/O 多路复用:select/poll/epoll 与 什么是零拷贝?)也就水到渠成了。
- 文档
- 教程
- 知识库
【免费下载链接】CS-Base
图解计算机网络、操作系统、计算机组成、数据库,共 1000 张图 + 50 万字,破除晦涩难懂的计算机基础知识,让天下没有难懂的八股文!🚀 在线阅读:https://xiaolincoding.com
相关推荐
CS-Base 图解操作系统:Linux 文件系统全家桶——从 inode、目录项到文件存储与文件 I/O 全解析
CS Base 图解操作系统:Linux 文件系统全家桶——从 inode、目录项到文件存储与文件 I/O 全解析 本篇技术指南以 CS Base 仓库《图解系
文档教程知识库Linux系统I/O性能瓶颈深度解析:从/proc/diskstats到块设备驱动
Linux系统I/O性能瓶颈深度解析:从/proc/diskstats到块设备驱动 你是否曾经面对服务器I/O性能问题束手无策?当应用程序响应缓慢,磁盘使用率居
操作系统内核驱动驱动开发虚拟化嵌入式网络存储Spring Cloud与gRPC集成终极指南:构建高性能微服务通信架构
Spring Cloud与gRPC集成终极指南:构建高性能微服务通信架构 在当今微服务架构盛行的时代, Spring Cloud与gRPC集成 已成为构建高性能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考