C++ Concurrency in Action | IO
目录
警告
本文最后更新于 2023-12-11,文中内容可能已过时。
I/O 硬件原理
- I/O 设备就是可以将数据输入到计算机(如鼠标、键盘),或者可以接收计算机输出数据的外部设备(如显示器)
- I/O 设备按信息交换单位可分为两类
- 块设备(block device):把信息存储在固定大小的块中,每个块都有自己的地址。块设备的基本特征是,传输速率快,可寻址,每个块都能独立于其他块而读写。磁盘就是最常见的块可寻址设备,无论磁盘臂当前处于什么位置,总是能寻址其他柱面并且等待所需要的磁盘块旋转到磁头下面
- 字符设备(character device):以字符为单位发送或接收一个字符流,而不考虑任何块结构,因此传输速率较慢,不可寻址,也没有任何寻道操作,在输入/输出时常采用中断驱动方式。打印机、鼠标就是常见的字符设备
- I/O 设备一般由机械部件和电子部件两部分组成
- 机械部件主要用于执行具体 I/O 操作,如鼠标的按钮、键盘的按键、显示器的屏幕、硬盘的磁盘臂
- 电子部件也称作设备控制器(device controller)或适配器(adapter),通常是主板上的芯片,或一块插入主板扩充槽的印刷电路板
- CPU 无法直接控制机械部件,因此需要通过设备控制器作为中介来控制机械部件。设备控制器的主要功能有
- 接收和识别 CPU 发出的命令:每个控制器有几个寄存器用于与 CPU 通信,通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或者执行其他某些操作
- 向 CPU 报告设备的状态:通过读取这些寄存器,操作系统可以了解设备的状态,是否准备好接收一个新的命令等
- 数据交换:除了控制寄存器外,许多设备还有一个操作系统可以读写的数据缓冲区,比如在屏幕上显示像素的常规方法是使用一个视频 RAM,这一 RAM 基本上只是一个数据缓冲区,可供程序或操作系统写入数据
- 地址识别:为了区分设备控制器中的寄存器,需要给每个寄存器设置一个地址,控制器通过 CPU 提供的地址来判断 CPU 要访问的寄存器
- 设备控制器中有多个寄存器,为这些寄存器编址有两种方式
- 内存映射 I/O(memory-mapped I/O):所有设备控制器的寄存器映射到内存空间中,每个控制寄存器被分配一个唯一的内存地址,并且不会有内存被分配到这一地址
- 寄存器独立编址:每个寄存器被分配一个 I/O 端口(port)号,所有端口号形成 I/O 端口空间(I/O port space),并且受到保护使得普通用户程序不能对其进行访问,只有操作系统可以访问。这一方案中,内存地址空间和 I/O 地址空间是不同且不相关的
I/O 软件原理
- I/O 软件的设计有以下目标
- 设备独立性(device independence):允许编写出的程序可以访问任意 I/O 设备而无需事先指定设备,比如读取一个文件作为输入的程序,应该能在硬盘、DVD 或 USB 盘上读取文件,无需为每一种不同的设备修改程序
- 统一命名(uniform naming):一个文件或一个设备的名字应该是一个简单的字符串或一个整数,不应依赖于设备
- 错误处理(error handling):一般来说,错误应该尽可能在接近硬件的层面得到处理。当控制器发现一个读错误时,如果它能够处理,就应该自己设法纠正错误。如果控制器处理不了,设备驱动程序就应当予以处理,可能只需要重读一次这块数据就正确了
- 同步(synchronous,即阻塞)和异步(asynchronous,即中断驱动)传输:大多数物理 I/O 是异步的,比如 CPU 启动传输后便转去做其他工作,直到中断发生。如果 I/O 操作是阻塞的,用户程序就更容易编写,比如 read 系统调用之后程序将自动被挂起,直到缓冲区中的数据准备好,而正是操作系统将实际异步的操作变为了在用户程序看来是阻塞式的操作
- 缓冲(buffering):数据离开一个设备之后通常不能直接存放到最终目的地,比如从网络上进来一个数据包时,直到将该数据包存放到某个地方,并对其进行检查,操作系统才知道要将其置于何处。缓冲涉及大量复制工作,经常对 I/O 性能有重大影响
- 共享设备和独占设备:共享设备能同时让多个用户使用(如磁盘),独占设备则只能由单个用户独占使用(如磁带机)。独占设备的引入带来了各种问题(如死锁),操作系统必须能处理共享设备和独占设备以避免问题发生
- I/O 有三种实现方式
- 程序控制 I/O(programmed I/O):这是 I/O 的最简单形式。CPU 轮询设备状态,当设备准备好时,CPU 向控制器发出读指令,从 I/O 设备中读取字,再把这些字写入到存储器。这种方式的优点是实现简单,缺点是在完成全部 I/O 之前,CPU 的所有时间都被其占用,如果 CPU 有其他事情要做,轮询就导致了 CPU 利用率低
- 中断驱动 I/O :用中断阻塞等待 I/O 的进程,CPU 在等待 I/O 设备就绪时,通过调度程序先执行其他进程。当 I/O 完成后(比如打印机打印完一个字符,准备接收下一个字符),设备控制器将向 CPU 发送一个中断信号,CPU 检测到中断信号后保存当前进程的运行环境信息,然后执行中断驱动程序来处理中断。CPU 从设备控制器读一个字的数据传送到 CPU 寄存器,再写入主存,接着 CPU 恢复其他进程的运行环境并继续执行(打印下一个字符)。中断的优点是提高了 CPU 利用率,缺点是每次只能读一个字,每次都要发生一个中断,频繁的中断处理将浪费一定的 CPU 时间
- 使用 DMA(Direct Memory Access)的 I/O :让 DMA 控制器来完成 CPU 要做的工作,使得 CPU 可以在 I/O 期间做其他操作。有了 DMA 控制器,就不用每个字中断一次,而是减少到每个缓冲区一次。DMA 控制器通常比 CPU 慢很多,如果 CPU 在等待 DMA 中断时没有其他事情要做,采用中断驱动 I/O 甚至程序控制 I/O 也许更好
I/O 软件层次
- I/O 软件通常组织成四个层次,从上层到底层依次为
- 用户级 I/O 软件:实现了与用户交互的接口,为用户提供 I/O 操作相关的库函数接口,如
printf
- 与设备无关的操作系统软件:向用户层提供系统调用,如为
printf
提供write
,另外还要提供设备保护(设置访问权限)、缓冲、错误报告、分配与释放专用设备、建立逻辑设备名到物理设备名的映射关系等功能 - 设备驱动程序(device driver):每个连接到计算机上的 I/O 设备都需要某些设备特定的代码来对其进行控制,这样的代码称为设备驱动程序
- 中断处理程序:进行中断处理
- 用户级 I/O 软件:实现了与用户交互的接口,为用户提供 I/O 操作相关的库函数接口,如
盘
- 盘有多种多样的类型,最常用的是磁盘,它具有读写速度同样快的特点,适合作为辅助存储器(用于分页、文件系统等)
- 磁盘被组织成柱面,每一个柱面包含若干磁道,磁道数与垂直堆叠的磁头个数相同,磁道又被分为若干扇区,通过
(柱面号, 盘面号, 扇区号)
即可定位一个磁盘块 - 磁盘臂调度算法有
- 先来先服务算法(First-Come First-Served,FCFS):按照请求接收顺序完成请求,优点是公平简单易实现,缺点是平均寻道时间较长
- 最短寻道时间优先算法(Shortest Seek Time First,SSTF):下一次处理,磁头向所有请求中距离最近的位置移动。缺点是可能出现饥饿现象
- 扫描算法(SCAN):也叫电梯算法(elevator algorithm),磁头持续向一个方向移动,直到到达最内侧或最外侧时才改变方向。优点是平均寻道时间较短,不会产生饥饿现象
- LOOK 调度算法:对扫描算法稍作优化,如果磁头移动方向上已没有需要处理的请求,则直接改变方向
- 循环扫描算法(C-SCAN):SCAN 算法对于各个位置磁道的响应频率不平均,靠近磁盘两侧的可能更快被下一次访问。为了解决这个问题,C-SCAN 算法的原理是,只在一个移动方向上处理请求,磁头返回时不处理任何请求
- C-LOOK:只在一个移动方向上处理请求,如果该方向之后没有要处理的请求,则磁头返回,并且只需要返回到第一个有请求的位置
Buy me a coffee~
支付宝
微信