MMIO 与 Port I/O

本文介绍了计算机系统中两种常见的I/O方式:Port I/O和MMIO(内存映射I/O)。Port I/O通常用于访问有限数量的I/O端口,而MMIO则将I/O设备映射到内存空间,使得处理器能够像访问内存一样访问这些设备。文章还讨论了两者之间的区别,包括它们如何占用地址空间、访问顺序性及使用方式的不同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

I/O作为CPU和外设交流的一个渠道,主要分为两种,一种是Port I/O,一种是MMIO(Memory mapping I/O)。

前者就是我们常说的I/O端口,它实际上的应该被称为I/O地址空间。对于x86架构来说,通过IN/OUT指令访问。PC架构一共有65536个8bit的I/O端口,组成64KI/O地址空间,编号从0~0xFFFF。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。
MMIO(Memory mapping I/O)即内存映射I/O,它是PCI规范的一部分,I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。这样访问AGP/PCI-E显卡上的帧缓存,BIOS,PCI设备就可以使用读写内存一样的汇编指令完成,简化了程序设计的难度和接口的复杂性。

Port I/O和MMIO的主要区别在于
1)前者不占用CPU的物理地址空间,后者占有(这是对x86架构说的,一些架构,如IA64,port I/O占用物理地址空间)。
2)前者是顺序访问。也就是说在一条I/O指令完成前,下一条指令不会执行。例如通过Port I/O对设备发起了操作,造成了设备寄存器状态变化,这个变化在下一条指令执行前生效。uncache的MMIO通过uncahce memory的特性保证顺序性。
3)使用方式不同

<think>我们之前讨论了UEFI下读取I/O端口的方法,现在用户询问如何安全地写入I/O端口。根据UEFI的规范,写入I/O端口读取类似,主要使用IoLib库提供的函数,但需要注意安全性问题。 首先,UEFI提供了IoLib库,其中包含写入I/O端口的函数: - `IoWrite8(UINTN Port, UINT8 Value)` - `IoWrite16(UINTN Port, UINT16 Value)` - `IoWrite32(UINTN Port, UINT32 Value)` - `IoWrite64(UINTN Port, UINT64 Value)` 安全写入I/O端口的关键点包括: 1. **权限阶段**:确保在DXE阶段或之后执行,因为PEI阶段可能需要使用PPI(PEIM-to-PEIM Interface)[^1]。 2. **端口范围验证**:避免写入保留或受保护的端口(如系统关键控制器端口),这可能导致系统不稳定。 3. **原子性操作**:对于多字节写入(如16/32位),确保使用单条指令(如`out dx, ax`),避免使用多次8位写入拆分,因为中间状态可能被中断或其他操作干扰。 4. **平台差异**:x86架构直接支持`OUT`指令,而ARM架构需要通过内存映射I/O(MMIO)模拟,因此要使用UEFI抽象的函数而非直接汇编指令。 示例:安全地向串口(COM1,端口0x3F8)写入一个字符 ```c #include <Uefi.h> #include <Library/IoLib.h> #include <Library/DebugLib.h> VOID SafeSerialWrite(UINT8 data) { // 等待串口发送缓冲区为空 while ((IoRead8(0x3F8 + 5) & 0x20) == 0); // 检查LSR的第5位(THRE) IoWrite8(0x3F8, data); // 写入数据端口 } ``` **注意事项**: - **设备状态检查**:在写入前检查设备状态(如串口的发送缓冲区空标志),避免数据丢失[^2]。 - **中断处理**:若写入操作可能触发中断,需确保中断服务程序(ISR)已正确安装。 - **替代协议**:优先使用设备协议(如`EFI_SERIAL_IO_PROTOCOL`)而非直接I/O操作[^2]。 - **平台安全策略**:某些固件可能锁定I/O端口范围,需确认平台策略。 **完整示例:写入键盘控制器命令字节(端口0x64)** ```c #include <Library/IoLib.h> EFI_STATUS WriteKeyboardCommand(UINT8 command) { // 检查输入缓冲区是否为空(避免覆盖未处理数据) if ((IoRead8(0x64) & 0x02) != 0) { return EFI_NOT_READY; // 缓冲区满,等待或重试 } IoWrite8(0x64, command); return EFI_SUCCESS; } ``` **安全增强建议**: 1. **封装重试机制**:对状态依赖的写入添加超时重试。 2. **范围白名单**:在驱动中定义允许访问的端口列表。 3. **日志记录**:使用`DEBUG`宏记录关键写入操作以便调试。 4. **权限分离**:仅允许特权驱动执行敏感I/O操作。 ### 相关问题 1. 如何通过UEFI协议安全访问PCI配置空间? 2. 在UEFI中如何为自定义硬件设备编写安全I/O驱动? 3. UEFI固件如何限制用户应用对I/O端口的访问? 4. 为什么在UEFI中直接操作I/O端口被视为高风险行为? [^1]: PCI Host Bridge控制器驱动和PCI总线驱动屏蔽了不同CPU架构差异[^1]。 [^2]: 输入输出设备的状态检查是避免数据丢失的关键[^2][^3]。 [^3]: 键盘控制器状态同步机制要求严格的状态检查[^3]。</think>在 UEFI 环境中安全地执行 I/O 端口写入操作需要遵循特定的协议和最佳实践,以下是关键方法和注意事项: ### 一、安全写入 I/O 端口的核心方法 1. **使用 IoLib 库的标准函数**(最推荐方式) ```c #include <Library/IoLib.h> // 安全写入函数 IoWrite8(UINTN Port, UINT8 Value); // 写入8位 IoWrite16(UINTN Port, UINT16 Value); // 写入16位 IoWrite32(UINTN Port, UINT32 Value); // 写入32位 IoWrite64(UINTN Port, UINT64 Value); // 写入64位 ``` 2. **键盘控制器写入示例**(端口 0x60/0x64) ```c EFI_STATUS SafeWriteKeyboard(UINT8 command) { // 检查状态寄存器(0x64)的IBF位(Input Buffer Full) if ((IoRead8(0x64) & 0x02) != 0) { return EFI_NOT_READY; // 缓冲区满,禁止写入[^3] } IoWrite8(0x60, command); // 安全写入数据端口 return EFI_SUCCESS; } ``` ### 二、安全写入的关键原则 1. **状态检查优先** - 必须检查设备状态寄存器(如 IBF 位),确保设备就绪 - 示例:键盘控制器需确认 `IBF=0` 才能写入[^3] ```c while (IoRead8(0x64) & 0x02); // 等待IBF清零 ``` 2. **使用硬件抽象协议** - 优先通过设备专属协议(如 `EFI_SERIAL_IO_PROTOCOL`)操作 - 避免直接访问裸端口[^1] ```c SerialIo->Write(SerialIo, &BufferSize, Data); // 通过协议写入 ``` 3. **原子性操作** - 多字节写入使用单条指令(如 `IoWrite32`) - 避免拆分为多次8位写入导致中间状态不一致 4. **平台适配层** - 通过 PCI Host Bridge 驱动抽象硬件差异 ```c PciRootIo->Io.Write(...); // 通过PCI协议写入[^1] ``` ### 三、高风险操作防护 1. **端口范围白名单** ```c // 定义允许访问的端口 const UINTN ALLOWED_PORTS[] = {0x60, 0x64, 0x3F8}; BOOLEAN IsPortAllowed(UINTN Port) { for (UINTN i=0; i<ARRAY_SIZE(ALLOWED_PORTS); i++) { if (Port == ALLOWED_PORTS[i]) return TRUE; } return FALSE; } ``` 2. **权限分级控制** - DXE 阶段:全权限 - UEFI 应用:受限权限(通过 Protocol 代理) - 运行时服务:禁止直接 I/O 3. **写入日志记录** ```c DEBUG((DEBUG_INFO, "I/O Write: Port 0x%x Value 0x%x\n", Port, Value)); ``` ### 四、完整示例(安全配置 PCI 设备) ```c #include <Library/UefiBootServicesTableLib.h> #include <Protocol/PciRootBridgeIo.h> EFI_STATUS SafePciConfigWrite( UINT8 Bus, UINT8 Dev, UINT8 Func, UINT8 Offset, UINT32 Value) { EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootIo; // 获取PCI协议实例 gBS->LocateProtocol(&gEfiPciRootBridgeIoProtocolGuid, NULL, (VOID**)&PciRootIo); // 通过协议安全写入 return PciRootIo->Pci.Write( PciRootIo, EfiPciWidthUint32, PCI_LIB_ADDRESS(Bus, Dev, Func, Offset), 1, // Count &Value ); } ``` ### 五、必须避免的陷阱 1. **直接使用汇编 OUT 指令** ```c // 危险操作!绕过UEFI保护机制 __asm__ __volatile__ ("outb %0, %1" : : "a"(data), "Nd"(port)); ``` 2. **忽略设备状态** - 未检查状态寄存器直接写入可能导致: - 数据丢失(如串口) - 硬件锁死(如键盘控制器) - 系统崩溃(关键控制器) 3. **跨架构未适配** - x86:直接端口访问 - ARM:需通过 MMIO 映射 - 使用 `IoLib` 函数可自动处理差异 > **关键安全准则**:优先使用设备专属协议 > 使用 IoLib 库 > 直接端口操作(最后选择)[^1][^2] ### 相关问题 1. 如何通过 UEFI 协议安全访问 PCI 配置空间? 2. 在 UEFI 驱动中如何实现 I/O 端口的原子操作? 3. UEFI 如何防止恶意应用进行危险 I/O 写入? 4. 为什么在运行时服务阶段禁止直接 I/O 操作? [^1]: PCI Host Bridge 控制器驱动屏蔽了 CPU 架构差异[^1]。 [^2]: 设备状态检查是避免 I/O 冲突的关键[^2][^3]。 [^3]: 键盘控制器要求严格的 IBF 状态检查[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值