文章目录
-
- [I/O 端口编址](#I/O 端口编址)
-
- [独立编址(Port-Mapped I/O, PMIO)](#独立编址(Port-Mapped I/O, PMIO))
- [独立编址 vs 统一编址](#独立编址 vs 统一编址)
- 两种编址的优缺点深度分析
- 现代系统的混合实践
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)