操作系统的设备管理
4月 28, 2021
计算机除了 CPU、内存、硬盘,还需要各种设备才能实现对计算机的操作。本文讨论计算机是如何管理设备的输入输出的。
设备控制器 #
设备控制器接收 CPU 发来的命令,去控制 I/O 设备工作,使 CPU 从繁杂缓慢的设备控制事务中解放出来。当 CPU 要操作 I/O 设备时,首先要启动这个设备,一方面将启动命令发送给设备控制器,另一方面通过地址线将 I/O 设备的地址发给设备控制器,设备控制器对收到的地址进行译码,再根据所译出的命令对相应的设备进行操作。
I/O 设备大致分为块设备(block device)和字符设备(character device)。块设备把信息存储在固定大小的块中,每个块都有自己的地址,比如硬盘、U盘都是常见的块设备。另一类 I/O 设备是字符设备,字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构,字符设备是不可寻址的,打印机、鼠标等大多数与磁盘不同的设备都可看作是字符设备。
可编程 I/O #
CPU 通常以读写设备寄存器的方式与设备进行通信,一个设备通常有多个寄存器,可以在内存地址空间或 I/O 地址空间中被访问。设备的寄存器可以分为状态寄存器、命令寄存器和数据寄存器。
- 状态寄存器:用于反馈当前设备的工作状态。
- 命令寄存器:用于接收来自驱动程序的命令。
- 数据寄存器:用于驱动程序和设备之间的数据交互。
访问设备寄存器通常有两种方式,一种是通过内存映射 I/O(Memory- Mapped I/O,MMIO),另一种是通过端口映射 I/O(Port- Mapped I/O,PMIO),这两种统称为可编程 I/O(Programmed I/O,PIO)。
- MMIO:将设备寄存器直接映射到内存空间上并拥有独立的地址,CPU 通过读写内存指令即可控制设备。
- PMIO:通过专门的端口操作指令(如 x86 的 in/out 指令)。
DMA 机制 #
CPU 可以从设备控制器每次请求一个字节的数据,但是这样做太浪费 CPU 的时间,所以经常用到一种称为直接内存访问(Direct Memory Access,DMA)的方案,与 PIO 不同,DMA 机制允许设备绕过处理器直接读写系统内存的数据。在同等程度的处理器负担下,DMA是一种快速的数据传送方式,很多硬件的系统会使用DMA,比如硬盘控制器、绘图显卡、网卡和声卡。
DMA 的发起者可以是处理器,也可以是 I/O 设备,以处理器发起 DMA 为例,设备驱动首先在内存中分配一块 DMA 缓冲区,随后发起 DMA 请求,设备收到请求后通过 DMA 机制将数据传输至 DMA 缓冲区,DMA 操作完成后,设备触发中断通知处理器对 DMA 缓冲区中的数据进行处理。
DMA 的发起还需要 DMA 控制器(DMA Controller)的参与,DMA 控制器和处理器与内存连接到同一系统总线(system bus),因为 DMA 控制器相当于收发双方(处理器和设备)的第三方,因此这种机制还被称为 第三方 DMA(third-party DMA),也被称为标准 DMA。
设备地址翻译(IOMMU) #
处理器上执行的操作系统代码在进行内存访问时使用的是虚拟地址,经过 MMU 翻译成物理地址。设备进行 DMA 时访问内存的地址是总线地址(bus address),其不同于虚拟地址和物理地址,当操作系统向 DMA 控制器注册 DMA 内存缓冲区时,需要填写的时总线地址。设备和内存之间的输入输出内存管理单元(Input-Output Memory Management Unit,IOMMU)负责将总线地址翻译成物理地址。
发展历史 #
早期 #
早期的计算机没有很复杂的图形功能,CPU 的核心频率也不高,跟内存频率一样,它们都是直接连接在同一个总线(Bus)上的,由于 I/O 设备诸如显示设备、键盘、软盘和磁盘等速度与 CPU 和内存相比还是慢很多,为了协调 I/O 设备与总线之间的速度,也为了能够让 CPU 能够和 I/O 设备进行通信,一般每个设备都会有一个相应的设备控制器(如上图)。
后来 #
后来由于 CPU 核心频率的提升,导致内存跟不上 CPU 的速度,于是产生了与内存频率一致的系统总线,而 CPU 采用倍频的方式与系统总线进行通信。随着图形化的操作系统普及,特别是 3D 游戏和多媒体的发展,使得图形芯片需要跟 CPU 和内存之间大量交换数据,慢速的 I/O 总线已经无法满足图形设备的巨大需求,为了协调 CPU、内存和告诉的图形设备,人们专门设计了一个高速的北桥芯片,以便它们之间能够高速地交换数据。
由于北桥运行的速度非常高,相对低速的设备如果全部直接连接在北桥上,北桥既须处理高速设备,有须处理低速设备,设计就会十分复杂。于是人们又设计了专门处理低速设备的南桥芯片,磁盘、USB、键盘、鼠标等设备的连接都连接在南桥上,由南桥将它们汇总后连接在北桥上。20 世纪 90 年代的 PC 机在系统总线上采用的是 PCI 结构,而在低速设备上采用的是 ISA 总线。
现在 #
Intel 从第一代 Core i7 开始,将原属于北桥功能的内存控制器整合到 CPU 当中,在主流机 Core i 系列中更将 PCI-e 控制器(主要负责连接显卡)整合到 CPU 当中,这时候传统意义上的北桥的所有功能都已经整合到 CPU 内部了,所以 Intel 50 系芯片“组”(X58除外,这是搭配i7 9xx用的,还有北桥)已经没有传统意义的北桥了,而南桥依然负责处理低速设备(SATA/USB/PCI等)、时钟等功能。由于只剩下一个芯片了,也没有“芯片组”的说法了,只剩下孤零零的PCH(Platform Controller Hub)。
AMD 平台的发展轨迹类似,在 K8 架构(第一代 AMD64 处理器)开始就把内存控制器集成到 CPU 内部,后来先是 APU 再是桌面的 FX 系列,陆陆续续把 PCI-e 控制器也整合到 CPU 中,也剩下孤零零的 FCH(Fusion Controller Hub)。
这个整合过程叫 SoC 化,这两家的南桥(PCH/FCH)现在差不多也变成了一个 PCI-e Hub 1。
设备中断机制 #
上面已经提到,当 CPU 发给设备一个指令,处理完成后,会通过触发中断(interrupt)通知处理器对 DMA 缓冲区中的数据进行处理。通过中断机制,设备可以通过向 CPU 发送中断来打断 CPU 的执行,使得 CPU 去处理中断。
中断控制器 #
为了接收各种各样设备的中断,引入了中断控制器(interrupt controller)来管理中断。为了响应中断,操作系统实现了一套处理逻辑,即中断处理函数(IRQ handler)。
当一个 I/O 设备完成它的工作后,它就会产生一个中断信号,主板上的中断控制器芯片会检测到这个信号,并将其转换成中断向量,交由 CPU 从中断向量表中读取一个新的程序计数器,这一程序计数器指向相应的中断处理函数的开始。中断向量的位置可以硬连线到机器中,也可以位于内存中的任何位置,由 CPU 寄存器指向其起点。
例如,用户使用键盘时,每击一键都发出一个中断信号,通知 CPU 有“键盘输入”事件发生,要求 CPU 读入该键的键值,CPU 就暂时中止手头的程序,转去处理键值的读取程序,在读取操作完成后,CPU 又返回原来的程序继续运行。
上面提到的这个中断的例子属于硬件中断,硬中断处理函数又称为(Interrupt Service Routine,ISR)。
中断类型 #
通常将的中断类型有硬中断和软中断。以 Linux 为例,Linux 操作系统将中断处理过程分为两个阶段:上半部(top half)和下半部(bottom half)。上半部分负责完成一些必要但轻量级的操作,下半部负责完成剩余的、复杂且时延要求较低的操作。硬中断处理函数实质上是 Linux 中断处理的上半部,软中断属于下半部。
硬中断 #
由硬件触发的中断就是硬中断,硬中断又分为外部中断和内部中断。
外部中断 #
一般是指由计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断等。外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备的中断请求。
内部中断 #
内中断主要指在处理器和内存内部产生的中断。内中断一般称为陷阱(trap)或异常。它包括程序运算引起的各种错误,如地址非法、校验错、页面失效、存取访问控制错、算术操作溢出、数据格式非法、除数为零、非法指令、用户程序执行特权指令、分时系统中的时间片中断以及从用户态到核心态的切换等都是陷阱的例子。
软中断 #
软件中断其实并不是真正的中断,它们只是可被调用执行的一般程序。
软中断的概念主要来源于 UNIX 系统。软中断是对应于硬中断而言的,它是在通信进程之间通过模拟硬中断而实现的一种通信方式。中断源发出软中断信号后,CPU 或者接收进程在“适当的时机”进行中断处理或者完成软中断信号所对应的功能。这里“适当的时机”,表示接收软中断信号的进程须等到该接收进程得到处理器之后才能进行。如果该接收进程是占据处理器的,那么,该接收进程在接收到软中断信号后将立即转去执行该软中断信号所对应的功能。
中断优先级 #
每个中断号都有对应的中断信息,中断信息中包含中断号的优先级(priority)。当多个中断信号同时触发时,中断控制器根据优先级响应中断。优先级有两个作用:
- 当产生不同优先级的中断时,高优先级的中断比低优先级的中断有限接受响应。
- 低优先级的中断在处理过程中,可以被高优先级的中断抢占(preemption)。
中断与信号 #
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
设备驱动程序 #
设备驱动程序(device driver)是一种内核模块,负责管理硬件设备的底层 I/O 操作。设备驱动程序是使用标准接口编写的,内核可通过调用该标准接口与设备进行交互。
上面提到了中断处理程序,可以想象到,鼠标和键盘的中断处理程序肯定是不同的,通常,设备驱动程序初始化的时候,就要先注册一个该设备的中断处理函数。