news 2026/7/24 21:44:29

手把手教你学pcie-第二种:MMIO 空间(Memory-Mapped I/O)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你学pcie-第二种:MMIO 空间(Memory-Mapped I/O)

目录

五、第二种:MMIO 空间(Memory-Mapped I/O)

1️⃣ MMIO 是什么?

2️⃣ MMIO 和配置空间的本质区别

3️⃣ MMIO 从哪里来?

4️⃣ CPU 眼中的 MMIO

5️⃣ Linux 驱动如何访问 MMIO?

✅ 第一步:获取 BAR 物理地址

✅ 第二步:建立内核虚拟映射

✅ 第三步:访问设备寄存器

6️⃣ 为什么 MMIO 不能是普通指针?

① 内存序问题(Ordering)

② 端序问题(Endianness)

③ MMIO 没有“内存语义”

7️⃣ Non-Prefetchable vs Prefetchable(再强调)

✅ Non-Prefetchable(控制寄存器)

✅ Prefetchable(数据缓冲区)

8️⃣ 一个真实驱动示例(RTL8169 简化版)

9️⃣ MMIO 常见坑点(工程师必看)

❌ 坑 1:忘了开 Memory Space

❌ 坑 2:用普通指针访问 MMIO

❌ 坑 3:把 Prefetchable 当成性能优化

🔟 一句话总结(本节约核心)

六、工程师小贴士(强烈建议加框)

七、本篇结构回顾(第 7 篇)



五、第二种:MMIO 空间(Memory-Mapped I/O)

1️⃣ MMIO 是什么?

MMIO = Memory-Mapped I/O(内存映射 I/O)

一句话定义:

MMIO 是把“设备内部寄存器”映射到 CPU 物理地址空间的一种机制。

对 CPU 来说:

  • MMIO 地址看起来和普通内存一模一样

  • 可以用load / store指令访问

  • 编译器、Cache、流水线都“以为”它在访问内存

但实际上:

这不是内存,这是设备

📌MMIO = “伪装成内存的设备寄存器”


2️⃣ MMIO 和配置空间的本质区别

对比项

配置空间

MMIO

目的

管理设备

操作设备

访问方式

专用机制(CF8/CFC / ECAM)

CPU load/store

是否在 CPU 地址空间

否(ECAM 除外)

✅ 是

是否可被 C 语言直接访问

驱动核心路径

是否可预取

部分可

是否高频访问

📌配置空间是“行政楼”,MMIO 是“生产车间”


3️⃣ MMIO 从哪里来?

答案只有两个字:

BAR

我们在第 6 篇讲过:

  • 设备通过 BAR 向系统“申请地址窗口”

  • 系统批准后,把这段地址写入 BAR

  • 这段地址,就是 MMIO 空间

BAR0 = 0xF7100000 ↓ CPU 访问 0xF7100000 ↓ RC 转换为 PCIe TLP ↓ Endpoint BAR Hit ↓ 访问设备寄存器

📌MMIO = BAR 映射后的 CPU 地址


4️⃣ CPU 眼中的 MMIO

从 CPU 视角看:

volatile uint32_t *reg = (uint32_t *)0xF7100000; uint32_t val = reg[0]; reg[1] = 0x12345678;

内核里不能这么干,必须用内核 API。


5️⃣ Linux 驱动如何访问 MMIO?

✅ 第一步:获取 BAR 物理地址

resource_size_t bar_phy = pci_resource_start(pdev, 0); // BAR0 resource_size_t bar_len = pci_resource_len(pdev, 0);

✅ 第二步:建立内核虚拟映射

void __iomem *regs; regs = pci_iomap(pdev, 0, 0); if (!regs) return -ENOMEM;

📌pci_iomap()内部会调用ioremap()


✅ 第三步:访问设备寄存器

u32 val; val = readl(regs + 0x10); // 读寄存器 writel(0x1, regs + 0x10); // 写寄存器

⚠️ 严禁:

*((u32 *)(regs + 0x10)) = 0x1; // ❌ 错误

📌必须使用readl() / writel(),保证内存序和端序


6️⃣ 为什么 MMIO 不能是普通指针?

原因有三:

① 内存序问题(Ordering)

  • CPU 可能乱序执行

  • 编译器可能重排访问

  • PCIe 对访问顺序敏感

readl() / writel()会插入屏障:

mb();

② 端序问题(Endianness)

  • PCIe 是小端

  • 某些 CPU 是大端

  • 访问接口会处理转换


③ MMIO 没有“内存语义”

  • 读可能改变状态

  • 写可能有副作用

  • 不能被 Cache / 预测执行

📌MMIO 是“有副作用的 I/O”,不是数据


7️⃣ Non-Prefetchable vs Prefetchable(再强调)

✅ Non-Prefetchable(控制寄存器)

  • 每次访问必须真实发生

  • 禁止预取、合并、重排

  • 所有控制寄存器都必须用这个

Region 2: Memory at f7100000 (non-prefetchable)

✅ Prefetchable(数据缓冲区)

  • 可预读

  • 可合并写

  • 适合 DMA / Framebuffer

Region 4: Memory at f7000000 (prefetchable)

📌Prefetchable ≠ 更快,而是“更安全的数据访问模型”


8️⃣ 一个真实驱动示例(RTL8169 简化版)

struct rtl8169_priv { void __iomem *mmio_addr; }; static int rtl8169_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct rtl8169_priv *priv; int ret; pci_enable_device(pdev); priv = kzalloc(sizeof(*priv), GFP_KERNEL); priv->mmio_addr = pci_iomap(pdev, 2, 0); // BAR2 /* 写 MAC 地址 */ writel(0x12345678, priv->mmio_addr + MAC_ADDR_REG); /* 使能 DMA */ writel(DMA_EN, priv->mmio_addr + DMA_CTRL); return 0; }

📌所有“设备操作”,都发生在 MMIO 空间


9️⃣ MMIO 常见坑点(工程师必看)

❌ 坑 1:忘了开 Memory Space

Control: Mem- BusMaster+

结果:

  • BAR 映射成功

  • 访问直接异常

✅ 解决:

setpci -s xx:xx.x COMMAND=0x06

❌ 坑 2:用普通指针访问 MMIO

结果:

  • 偶发错误

  • 多平台不兼容

  • 被编译器优化掉

✅ 解决:

  • 永远用readl() / writel()


❌ 坑 3:把 Prefetchable 当成性能优化

结果:

  • 控制寄存器用 Prefetchable

  • 设备行为异常

✅ 解决:

  • 控制寄存器 = Non-Prefetchable

  • 数据区 = Prefetchable


🔟 一句话总结(本节约核心)

**MMIO 是 PCIe 设备寄存器的“主战场”,

它通过 BAR 映射到 CPU 地址空间,

驱动通过readl() / writel()与设备对话。**


六、工程师小贴士(强烈建议加框)

⚠️ 调试 PCIe 驱动时,请记住这条铁律:

**“所有‘设备为什么不工作’的问题,

先确认 MMIO 能不能稳定读写。”**​

方法:

  • devmem

  • lspci -vv

  • dmesg | grep ioremap


七、本篇结构回顾(第 7 篇)

一、三种地址空间总览 二、配置空间(管理面) ↓ 三、MMIO 空间(数据面 / 本节) ↓ 四、I/O 空间(历史包袱 / 下节)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 21:44:22

为什么别人的代码能秒跑,你却要配3小时环境

为什么别人的代码能秒跑,你却要配3小时环境 昨天群里有个人问:"大佬们,我下载了个开源项目,跑不起来怎么办?" 然后贴了二十几张报错截图,涵盖了node版本不对、python依赖装不上、数据库连不上、端…

作者头像 李华
网站建设 2026/7/24 21:41:12

【claude code实践】MCP 入门:让 Claude Code 连接外部工具与数据源

MCP 入门:让 Claude Code 连接外部工具与数据源 引言:为什么现在需要理解它 如果你在过去几个月里使用过 Claude Code、Cursor 或其他 AI 编程助手,很可能遇到过这样的场景:你让它帮你写一段查询数据库的代码,它给出了…

作者头像 李华
网站建设 2026/7/24 21:39:35

HSTracker:3大功能让你的炉石传说胜率飙升50%

HSTracker:3大功能让你的炉石传说胜率飙升50% 【免费下载链接】HSTracker A deck tracker and deck manager for Hearthstone on macOS 项目地址: https://gitcode.com/gh_mirrors/hs/HSTracker 还在为记不住对手的卡牌而烦恼?每次抽牌都像在赌博…

作者头像 李华
网站建设 2026/7/24 21:36:55

终极指南:如何快速解决ComfyUI-BrushNet安装与克隆问题

终极指南:如何快速解决ComfyUI-BrushNet安装与克隆问题 【免费下载链接】ComfyUI-BrushNet ComfyUI BrushNet nodes 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-BrushNet ComfyUI-BrushNet是一个强大的图像修复和编辑工具,它基于先进的…

作者头像 李华