操作系统笔记05:设备管理

Posted by LiYixian on Monday, November 20, 2023 | 阅读 | ,阅读约 5 分钟

设备(e.g.鼠标、键盘、显示器)的特点:速度慢、难以共享、工作逻辑 varies
操作系统的设备管理使得应用程序不需要关心设备的细节,就可以统一响应

外设借助一个 controller/adapter 和计算机连接在一起
适配器采集设备上操作系统关心的数据,有三类接口:数据、命令、状态
操作系统整理每个厂商提交的设备不同的数据、命令、状态格式,经过封装和抽象,抹去硬件的差异,交给上层应用程序
设备驱动程序 device driver 负责识别 controller 的数据,完成翻译;现代操作系统已经预先继承了若干个通用的 driver

设备管理的思路:

  1. 标准化
    • 上层 application 尽量少感知硬件之间的差异(所有鼠标传给上层的数据都一样)
    • 操作系统减少为不同硬件做的修改(所有鼠标的控制逻辑都一样)
  2. 灵活性
    • 能支持新款、新型设备

设备的分类与抽象

标准化:操作系统中预置了 application 可能使用到的设备的类别,然后按照类别(虚基类)设计对应的数据类型和函数
分类方法:function/data management/assignment

为同一类设备定义一套统一的接口,设备厂商只需要按照接口实现对应的函数,applications 遵从接口使用设备即可
灵活性:虚基类设计时需要考虑到所有可能的情况
-> 操作系统从来没有见过的设备怎么办?

外设的机械变化/模拟信号变化 -> 电信号 -> 计算机可以处理的数字信号
设备将得到的数据放在自己的寄存器/数据 buffer 等结构中,通过接口将数据上传

设备数据的传输

CPU 如何读写设备提供的数据接口?
把 CPU 的数据总线对应的选通信号和 adapter 的使能信号连起来,这样 CPU 发出某个地址上的读信号时,可以选通 adapter
RISC 不区分 memory 和 I/O

如何获取数据?

  1. 用专用的 I/O 指令读写专用的 I/O 端口地址
    • 理论上应该是特权指令,这样所有 I/O 读写都必须借由操作系统完成,但由于历史遗留问题是普通指令
  2. 把 I/O 的地址区域放在内存的某个特定区间,对这个区域发出的读写信号会被转译到对应的外设接口上;没有单独的 I/O 指令,用 load/store 就可以完成
    • 专门有一部分地址线选通外设,因此是在物理地址空间预留的区域
    • 只能映射在操作系统的内核空间,否则可能会被普通用户直接访问,引发控制逻辑混乱
    • 可以使得 I/O 设备和内存共享寻址机制,因此在 MIPS/RISCV 广泛使用
  3. 混合编址,在内存空间放一个 I/O buffer
    • x86 使用,I/O 专用端口只用来兼容老式设备,新式设备的数据都放在内存里

如果要使用外设的地址空间,就必须运行在特权态
-> driver 是操作系统的一部分,或与操作系统一并运转在特权模式
即,driver 可以拿到 OS 的最高权限 -> 试图让 driver 在一个特殊的权限位上,只能访问自己这个设备相关的部分
输入法也是一个 driver

设备交互

设备的速度非常慢 -> 如何及时发现数据并传输?

发现:

  • busy waiting 查询
    • 轮询外设的状态寄存器,查询操作完成了没有
    • 浪费 CPU 时间
  • interrupt
    • 在需要等待的时间去运行其他进程,设备完成操作时,触发一个中断,唤醒 CPU
      • CPU 有一些管脚可以感知中断的电平变化,然后将 PC 跳转到中断响应函数
    • 中断的代价太高
      • 外设将数据交付给 DMA controller,直到处理完一块,DMA 再给 CPU 产生一个中断

传输:

  • CPU 主动逐字节传输数据
  • 引入 DMA (direct/disk memory access),不需要 CPU 干预就可以直接访问内存,以块为单位传输数据
    • DMA 类似于一个简化版的 CPU,只能完成地址的递增/递减,产生控制器的读写信号
    • DMA 只能访问连续的物理地址,访问不了页表
      • 内核会专门为 DMA 预留一块连续的地址空间(操作系统控制物理内存分配,它可以特意选一块连续的),用户需要的时候再将其复制到用户空间
      • 使用 buffer 可以匹配设备的速度差异;如果不够,就要叫远端不要再发送数据了
    • CPU 会给 DMA 一个内存地址,希望它传输的数据块大小,以及把数据传到硬盘上的 target address
    • CPU 需要访问内存的时候,就得叫停 DMA,二者不能并行工作

中断的代价是什么?

  • 需要切换到内核态,做权限管理工作
  • 操作系统把中断当作了进程调度的机会,频繁的 context switch 很影响性能

中断响应的时间决定了网络传输的上限,CPU 主频不够时,中断占据的时间过多
-> 使用轮询 + DMA
solution:用户态中断;或将网卡对应的存储空间直接映射到用户地址空间,用户轮询发现其状态变化,把数据取走

设备的数据存在内核态,application 运行在用户态,如何传输?
有些操作系统借助文件系统的 syscall 接口 read/write,把内核空间的数据复制到用户空间,或者反之
-> 特例:如何控制向一个输入设备发送控制命令?
Linux 的新接口 ioctl,专门为 I/O 设备传递指令

设备在进程间共享

几个例子

  1. 磁盘

磁盘磁道移动会花费一定时间,连续从磁盘里读数据时,为了避免等待转一圈,使扇区编号交错(写进技术),controller 有一定时间完成检查

  1. CD-ROM

为了得到相同的数据读取速率,光盘 driver 在激光发射器的位置需要有一个稳定的线速度,控制电机的转速,读外围数据就转得慢,内围就转得快

  1. flash

稳定的电容器,放电(把 1 变成 0)很容易(接地),充电(擦除,把 0 变成 1)比较困难
将连续的介质划分为 page,若干个 page 叠加在一起成为一个 block
为了均摊充电的成本,以 block 为单位充电
-> 如果充不上电,这个 block 就被标记为不能再用
出厂时需要检测 flash,进行一次擦除和再读取的工作,如果发现有些 block 不能用,就从总体容量里剔除,所以实际上的存储容量会不够

flash 的数据读写:拿到一个完整的 block 全都充上电,然后把这些 page 里应该是 0 的位置放电
使用过程中由于频繁充放电,会把 block 弄坏;随着擦除次数增长,磁盘慢慢趋向于老化
在 controller 里有一个 FTL table,可以看到计量每个 block 被擦除了多少次
flash 会预留一部分空间,在 block 坏掉的时候从中拿出一个顶替它

  1. 显卡