IO设备——I/O端口编址

文章目录

I/O 端口编址

为什么需要 I/O 端口编址?

CPU 需要能够精确地指定"我要和哪个设备的哪个寄存器通信"。I/O 端口编址就是为每个可访问的寄存器分配一个唯一的"身份证号"(地址),使 CPU 的访问指令能够准确路由到目标寄存器。

I/O 端口 ≈ I/O 接口中可被 CPU 直接访问的寄存器 。数据缓冲寄存器 = 数据端口,状态寄存器 = 状态端口,控制寄存器 = 控制端口。

独立编址(Port-Mapped I/O, PMIO)

plain 复制代码
  ┌──────────────────────┐      ┌──────────────────────┐
  │     内存地址空间       │      │    I/O 端口地址空间    │
  │   0x00000000         │      │   0x0000 ~ 0xFFFF    │
  │         ↓            │      │   (64K 个 8位端口)    │
  │   0xFFFFFFFF         │      │                      │
  │   (4GB, 32位CPU)     │      │                      │
  └──────────────────────┘      └──────────────────────┘
          ▲                              ▲
     MOV 指令访问                    IN / OUT 指令访问
     (普通访存指令)                  (专用 I/O 指令)

  x86 示例:
    MOV DX, 0x3F8        ; 无效------0x3F8 是 I/O 端口地址, 不是内存地址
    IN  AL, DX            ; 正确!从 I/O 端口 0x3F8 (COM1 串口) 读取一字节到 AL
    OUT DX, AL            ; 将 AL 的值写入 I/O 端口 0x3F8

Intel x86 I/O 地址空间分配示例:

I/O 端口范围 设备
0x0000 ~ 0x001F DMA 控制器 1 (8237)
0x0020 ~ 0x003F 中断控制器 1 (8259 PIC Master)
0x0040 ~ 0x005F 可编程定时器 (8254 PIT)
0x0060 ~ 0x006F 键盘控制器 (8042)
0x0070 ~ 0x007F CMOS/实时钟 (RTC)
0x00A0 ~ 0x00BF 中断控制器 2 (8259 PIC Slave)
0x01F0 ~ 0x01F7 主 IDE 硬盘控制器
0x03F0 ~ 0x03F7 软盘控制器
0x03F8 ~ 0x03FF COM1 串口
0x0CF8 ~ 0x0CFF PCI 配置空间访问

独立编址 vs 统一编址

plain 复制代码
  独立编址 (Port-Mapped I/O):        统一编址 (Memory-Mapped I/O):
  
  ┌────────────┐                      ┌────────────────────────┐
  │ 内存空间    │                      │     统一地址空间         │
  │ (4GB)     │                      │                        │
  │            │                      │  0x00000000 ┌────────┐ │
  │ 地址 0~N   │                      │             │  DRAM  │ │
  │            │                      │  0x7FFFFFFF │  (2GB) │ │
  └────────────┘                      │             └────────┘ │
  ┌────────────┐                      │  0x80000000 ┌────────┐ │
  │ I/O 空间   │                      │             │  I/O   │ │
  │ (64KB)    │                      │  0xFFFFFFFF │ 设备   │ │
  │            │                      │             │ (2GB)  │ │
  │ 端口 0~64K │                      │             └────────┘ │
  └────────────┘                      └────────────────────────┘
  
  访问方式:                            访问方式:
  IN AL, 0x60    ; 读键盘              LDR R0, =0x80001000  ; 加载地址
  OUT 0x60, AL   ; 写键盘              STR R1, [R0]         ; 写入设备
                                       LDR R2, [R0]         ; 读取设备
维度 独立编址 (PMIO) 统一编址 (MMIO)
地址空间 I/O 与内存各自独立,互不占用 I/O 地址纳入内存地址空间统一编排
访问指令 专用 I/O 指令 (IN/OUT,x86) 普通访存指令 (MOV/LDR/STR)
指令数量 I/O 指令少(通常只有读/写端口两类) 访存指令丰富(AND/OR/XOR/TEST 等均可操作 I/O)
控制信号 两套:MEMR#/MEMW# + IOR#/IOW# 一套:MEMR#/MEMW#(靠地址高位区分 I/O)
占用内存空间 ❌ 不占用 ✅ 会"吃掉"一部分地址空间(现代 64 位系统下几乎可忽略)
执行速度 I/O 指令短,解码快 与访存一致,但地址计算可能多占几字节
安全性 天然隔离------用户态无法执行 IN/OUT 需依赖 MMU 的页表权限位(设置对应页不可缓存/不可用户访问)
典型架构 Intel x86/x64(虽然 x64 也支持 MMIO) ARM、MIPS、RISC-V、PowerPC

两种编址的优缺点深度分析

独立编址

:::color1

优点

  • I/O 地址空间完全独立,绝不侵占宝贵的内存地址空间(在 32 位时代非常重要)
  • 专用 I/O 指令使得程序中 I/O 操作一目了然(IN/OUT 很清楚)
  • I/O 指令通常较短(2 字节),执行速度快

:::

:::color4

缺点

  • I/O 指令功能单一------只能做简单的端口读写,不像访存指令那样支持丰富的寻址模式和位操作
  • 需要额外的控制引脚(M/IO#)来区分当前是内存访问还是 I/O 访问,增加 CPU 引脚数和主板布线复杂度
  • I/O 端口地址空间受限(x86 只有 64K 个 8 位端口),对于拥有大量寄存器的现代设备可能不够用

:::

统一编址

:::color1

优点

  • 指令丰富 :所有访存指令都可以操作 I/O 设备------AND/OR/XOR/TEST/CMP/位域操作等可直接用于控制/状态寄存器的原子修改
  • 地址空间巨大:在 64 位系统下可映射海量的 I/O 寄存器,没有 64K 端口的限制
  • 统一编程模型:内存访问和 I/O 访问用同一套机制,简化编译器和工具链设计

:::

:::color4

缺点

  • 需要从地址空间划出一块给 I/O,在 32 位时代这是一个真实的取舍(例如 ARM 系统常将高 512MB~2GB 留给 I/O 外设)
  • 无法对 I/O 区域使用 Cache(需要标记为 Uncacheable),但这在 MMIO 中是标准做法
  • I/O 访问和内存访问共用一个地址空间 → 地址译码需要更复杂(判断地址落在 DRAM 范围还是 I/O 范围)

:::

现代系统的混合实践

大多数现代系统实际上是混合使用两种方式:

plain 复制代码
  x86/x64 系统:
  · 传统 ISA 设备 (PIT, PIC, KBC, COM, LPT) → 独立编址 (PMIO)
  · PCI/PCIe 设备的 BAR (Base Address Register) → 可由 BIOS/OS 配置为 MMIO 或 PMIO
  · 现代高性能设备 (GPU, NVMe, AHCI 等) → 统一编址 (MMIO)
  · APIC (高级可编程中断控制器) → MMIO

  ARM 系统:
  · 几乎全部使用统一编址 (MMIO)
  · 通过页表将外设寄存器所在的物理页标记为 Device-nGnRnE 类型
    (Non-Gathering, Non-Reordering, No Early Write Acknowledgement)