跳转至

设备和输入输出

设备和计算机

  • 在 nemu 和 npc 中, 程序的结束需要依靠运行的环境.
  • 设备: 计算机与物理世界交互的桥梁, 用户通过设备来使用计算机
    • 设备 = 电气部分 + 数字部分 (设备控制器) image.png
    • 设备控制器: 可以理解为将处理器发出的二进制命令翻译为电气信号的部件
      • 一侧连接处理器, 接收来自处理器的命令
      • 一侧连接电气部分, 向电气部分发送命令
  • 主板和总线:
    • 总线的表现形式: 电缆, 插槽, 主板走线, 引脚打线, 芯片内部绕线
    • 北桥与南桥芯片: 进行转发
      • 北桥: 连接高速设备, 如 DDR, 显卡, PCI-e
      • 南桥: 连接低速设备, 如 BIOS, 磁盘, USB

        现在由于芯片的集成度大幅提升, 可以在 CPU 中集成高速设备的控制器, 而对于低速设备, 使用 pcie 和 usb 协议就可以连接大部分的设备, 使得现在的使用南北桥芯片的设备越来越少了

  • 芯片封装: 将芯片的端口信号引出来

设备模型

  • 设备控制器中有什么?
    • 数据缓冲寄存器 -> 数据交换
    • 控制寄存器 -> 命令控制
    • 状态寄存器 -> 状态检测
    • 其他部件
  • 对于 CPU, 寄存器就是设备功能的抽象, CPU 只需要访问设备寄存器即可控制设备工作, 无须关心其具体实现

设备寄存器的编址

  • 独立编址 (port-mapped I/O, PIO): 内存和设备的地址空间不同, 需要新的 I/O 指令来访问设备 image.png
  • 统一编址 (memory-mapped I/O, MMIO): 内存和设备的地址空间相同, 根据访存地址决定访问什么 image.png
    • CPU 可以通过普通的访存指令来访问设备
    • 将一部分物理内存的访问重定向到 I/O 地址空间中, CPU 尝试访问这部分物理内存的时候, 实际上最终是访问了相应的 I/O 设备, 而 CPU 却浑然不知
    • 需要在访问设备时候通过 volatile 标识, 告诉编译器这个行为要严格执行

      [!tip] 为什么需要 volatile 标识 先来看看没有 volatile 标识情况下编译出来的汇编代码 ```c

      define BUSY 0

      void uart_putch (char c) { char status = (char *) 0x10000000ul; char *data = (char *) 0x10000004ul; while (status == BUSY); *data = c; } 以上代码编译出来的结果为:assembly 00000000 : 0: 100007b7 lui a5,0x10000 4: 0007c703 lbu a4,0 (a5) # 10000000 <.L5+0xffffff0> 8: 00071463 bnez a4,10 <.L5> 0000000c <.L4>: c: 0000006f j c <.L4> 00000010 <.L5>: 10: 00a78223 sb a0,4 (a5) 14: 00008067 ret 我们可以看到在 `.L4` 标志处是一个死循环, 这是经过了优化的结果, 因为一般来说内存中的某个位置<u></u>在此时是不会改变的, 而实际上, 这是一个访问设备的代码段, 当设备的状态机发生变化时候, 对应的内存位置上的数据是可能发生变化的, 而编译器并不知道这一点, 就对其进行了优化, 这样显然是不符合我们的预期的, 因此我们需要使用 `volatile` 关键字来避免编译器的优化, 告诉编译器这个访问要严格执行. 接下来我们将 `status` 和 `data` 都改为使用 `volatile` 关键字修饰的变量, 再来看看编译出的汇编代码.assembly 00000000 : 0: 10000737 lui a4,0x10000 00000004 <.L2>: 4: 00074783 lbu a5,0 (a4) # 10000000 <.L2+0xffffffc> 8: fe078ee3 beqz a5,4 <.L2> c: 00a70223 sb a0,4 (a4) 10: 00008067 ret `` 此时就可以看到, 每一次都需要重新从0x10000000` 内存位置出取出最新的数据, 并且进行比较.

输入输出的状态机模型

  • 执行普通指令, 按照 TRM 模型转移状态
  • 执行设备输出指令:
    • 除了更新 PC 外, 其他状态不变
    • 设备状态和物理世界发生相应的改变
  • 执行设备输入指令, 根据输入进行状态转移
    • 取决于设备的输入

AM 中的 IOE

  • 不同指令集访问设备的方式有所不同
  • 设备型号不同
  • 地址不同

常用设备

GPIO

可传输 1bit 的数据, 无须额外的状态和控制

串口

  • 双向的字符传输