5.0 KiB
5.0 KiB
这三个问题正好卡在“虚拟内存—总线—中断控制器”的交界处,我直接给你一个工程级、可运行在脑子里的模型。
一、DMA 用的是虚拟地址还是物理地址?
短答:DMA 面向的是“物理侧地址”,不是普通的 CPU 虚拟地址。 但在现代系统里会多一层:I/O 虚拟地址(IOVA)。
1)最基础模型(无 IOMMU)
CPU: 虚拟地址(VA) ──页表──> 物理地址(PA)
DMA: 直接使用 PA
流程:
- 驱动在内核里分配一块内存(这时它有 VA 和对应的 PA)
- 驱动把物理地址 PA写给设备(DMA 描述符里)
- 设备按这个 PA 直接读/写内存
👉 结论:
DMA 不认识进程的虚拟地址(VA),只认物理地址(PA)
2)现代模型(有 IOMMU)
引入一个关键组件:IOMMU
CPU: VA ──> PA
DMA: IOVA ──IOMMU──> PA
此时流程变成:
- 驱动向 IOMMU 注册一段内存映射
- 得到一个 IOVA(给设备用的“虚拟地址”)
- 设备用 IOVA 发起 DMA
- IOMMU 翻译成真实物理地址 PA
👉 关键好处:
✔ 安全:设备不能乱访问内存
✔ 灵活:可以映射不连续的物理页
✔ 虚拟化支持(多VM)
3)一句话总结
无 IOMMU:DMA 用物理地址
有 IOMMU:DMA 用 IOVA(再映射到物理地址)
二、DMA 会不会和 CPU 冲突?能不能同时工作?
短答:可以完全并行,但会“竞争资源”,不是互斥。
1)并行性(关键认知)
CPU:执行指令
DMA:走总线搬数据
👉 两者是独立硬件单元:
✔ 可以同时工作
✔ 不需要互相等待
2)那会不会冲突?——会,但在“带宽层面”
冲突发生在这里:
内存控制器 / 总线(如 PCIe)
多个参与者:
CPU(load/store)
DMA(设备)
GPU(显存访问)
都要访问内存:
→ 需要仲裁(arbitration)
3)真实情况
CPU读写内存 ←→ DMA读写内存
↓
内存控制器调度
结果:
✔ 可以并行
✔ 但会抢带宽(变慢)
❌ 不是“谁工作另一个就停”
4)一个直觉类比
内存 = 公路
CPU = 小汽车
DMA = 卡车
✔ 可以同时跑
✔ 但车多会堵
三、DMA 如何让 CPU 产生中断?
这个是很多人最模糊的一点,我们拆到“硬件信号级”。
1)关键:DMA 本身“不发中断”
👉 非常重要:
DMA ≠ 中断源
设备(或控制器)才是
2)完整路径(标准 PC 架构)
设备/NIC/USB控制器
↓
发中断信号(MSI / MSI-X / 旧IRQ)
↓
中断控制器(APIC)
↓
CPU
3)现代方式:MSI(Message Signaled Interrupt)
不用传统“中断线”,而是:
设备发一个“特殊的内存写”
具体:
设备(通过 PCIe):
写入某个固定地址(APIC寄存器)
这次“内存写” = 中断信号
4)流程串起来(DMA + 中断)
1)设备 DMA 写数据到内存 buffer
2)设备决定:
- 立刻中断?
- 延迟(coalescing)?
3)如果要中断:
设备发 MSI(本质是一次内存写)
4)APIC 接收到
→ 通知 CPU
5)CPU:
- 保存上下文
- 跳转中断处理函数
5)为什么设计成“内存写触发中断”?
好处:
✔ 不需要专用中断线(可扩展)
✔ 支持多核(可以指定哪个CPU)
✔ 和DMA同一套总线(PCIe)
四、把三件事彻底统一(你要形成这个模型)
1)地址体系
CPU:VA → PA
DMA:IOVA → PA(或直接 PA)
2)执行关系
CPU 执行程序
DMA 搬数据
→ 完全并行
3)通知机制
DMA完成(只是数据好了)
↓
设备决定是否发中断
↓
MSI → APIC → CPU
五、一个完整“工业级时间线”(你可以脑内跑)
t0:驱动配置 DMA buffer(IOVA/PA)
t1:设备收到数据
t2:DMA → 写入内存
t3:CPU 正在跑别的程序(完全并行)
t4:设备决定:
“现在通知CPU”
t5:发 MSI(一次PCIe内存写)
t6:APIC → CPU
CPU 被打断
t7:中断处理函数:
从 buffer 读数据
六、最后给你三句“压缩理解”
1)DMA 只负责“搬数据”,不负责“通知”
2)地址上:DMA 不用 CPU 的虚拟地址(除非经 IOMMU 转换)
3)CPU 和 DMA 并行运行,只在“内存带宽”上竞争
如果你想再往下走一步,下一层是非常关键的:
- cache coherence(DMA 写内存,CPU cache 怎么一致?)
- zero-copy 为什么难(缓存 + 映射问题)
- DPDK 为什么要关 cache / 绑核
这些是“性能优化”的真正核心。