DMA技术详解:解放CPU的数据搬运工

前言

在理解mmap的"零拷贝"原理时,我们反复提到了DMA(Direct Memory Access,直接内存访问)。DMA是计算机系统中一项至关重要的技术,它让数据在内存和设备之间直接传输,而无需CPU的持续介入。

本文将深入剖析DMA的本质、工作原理,以及它如何与mmap配合实现高性能数据通路。


一、没有DMA的时代:CPU做苦力

1.1 传统PIO模式

在DMA出现之前,CPU与外设的数据传输使用**PIO(Programmed I/O,程序控制I/O)**模式:

复制代码
// PIO模式下,CPU逐字节搬运数据
for (int i = 0; i < len; i++) {
    data[i] = read_from_device();  // CPU读取设备寄存器
    // 每个字节都要CPU执行指令
}

CPU               磁盘控制器              内存
 │                    │                    │
 │ 读数据命令         │                    │
 │───────────────────▶│                    │
 │                    │                    │
 │ 读取1字节          │                    │
 │◀───────────────────│  数据(1字节)       │
 │                    │                    │
 │ 写入1字节          │                    │
 │────────────────────────────────────────▶│ 数据(1字节)
 │                    │                    │
 │ 读取1字节          │                    │
 │◀───────────────────│  数据(1字节)       │
 │                    │                    │
 │ 写入1字节          │                    │
 │────────────────────────────────────────▶│ 数据(1字节)
 │                    │                    │
 │  ... 重复N次      │                    │

严重问题

  • CPU被完全占用,无法处理其他任务
  • 传输速度受限于CPU指令执行速度
  • 每个字节都要经过CPU寄存器

1.2 典型场景:40MB/s的SCSI磁盘

复制代码
CPU速度:100MHz → 每秒1亿条指令
磁盘速度:40MB/s → 每秒传输4000万字节

如果每个字节需要10条指令:
4000万 × 10 = 4亿条指令/秒
需要400MHz的CPU才能满足!

→ CPU完全被IO任务占满,无法做其他工作

这就是DMA诞生的原因让CPU从数据搬运中解放出来


二、DMA的本质与工作原理

2.1 DMA的定义

DMA(Direct Memory Access):一种硬件机制,允许外设直接读写系统内存,无需CPU介入。

核心思想

复制代码
CPU发号施令(指挥) → DMA控制器执行(搬运)
而不是 CPU既指挥又搬运

2.2 DMA控制器

DMA控制器是一个独立的硬件单元,专门负责数据搬运:

复制代码
                    ┌──────────────────┐
                    │   DMA控制器      │
                    │  ┌────────────┐  │
CPU                 │  │  控制寄存器 │  │
 │                  │  │  - 源地址   │  │
 │  配置DMA传输     │  │  - 目标地址 │  │
 │─────────────────▶│  │  - 传输长度 │  │
 │                  │  │  - 传输模式 │  │
 │  开始传输命令    │  └────────────┘  │
 │─────────────────▶│         │         │
 │                  │    ┌────┴────┐    │
 │  其他任务...     │    │  DMA引擎 │    │
 │  继续执行        │    └────┬────┘    │
 │                  │         │         │
 │  ◀─ DMA完成中断  │         │         │
 │──────────────────│         │         │
                    └─────────┼─────────┘
                              │
                    ┌─────────┴─────────┐
                    │    数据传输总线     │
                    │   (不经过CPU)      │
                    └─────────┬─────────┘
                              │
                    ┌─────────┴─────────┐
                    │                   │
                  磁盘控制器          内存

2.3 DMA工作流程

复制代码
1. CPU配置DMA控制器
   ├── 源地址:磁盘控制器的数据寄存器
   ├── 目标地址:内存缓冲区地址
   ├── 传输长度:4KB
   └── 传输模式:读取

2. CPU启动DMA传输
   └── 写控制寄存器启动位

3. DMA控制器独立工作
   ├── 从磁盘控制器读取数据
   ├── 直接写入内存缓冲区
   └── 重复直到传输完成

4. DMA传输完成
   ├── 向CPU发送中断信号
   └── CPU响应中断,处理数据

5. CPU继续执行其他任务
   └── 传输期间CPU完全自由

2.4 一个完整的DMA传输示例

复制代码
// 伪代码:从磁盘读取4KB数据到内存
void disk_read_dma(void *buffer, uint32_t sector, uint32_t count) {
    // 1. 配置DMA控制器
    dma_set_source(DISK_CONTROLLER_DATA_REG);
    dma_set_destination(buffer);
    dma_set_length(count * 512);  // 扇区大小
    dma_set_mode(DMA_READ);
    
    // 2. 启动DMA传输
    dma_start();
    
    // 3. CPU可以干其他事
    do_other_work();
    
    // 4. 等待DMA完成(中断方式)
    wait_for_dma_interrupt();
    
    // 5. 数据已在buffer中
}

三、DMA与CPU的协作模式

3.1 三种传输模式

模式 CPU占用 数据流向 适用场景
轮询模式 高(持续检查) 外设↔内存 小数据量、实时性要求高
中断模式 低(仅在完成时中断) 外设↔内存 大数据量、块设备
周期挪用 中(窃取CPU周期) 外设↔内存 多媒体、实时数据流

3.2 DMA vs CPU直接控制

复制代码
DMA方式:
CPU: [配置DMA] → [执行其他任务] ← [中断通知完成]
DMA:              [数据搬运中...] → [完成]

CPU占用率:5%(配置+中断处理)

PIO方式:
CPU: [读1字节] → [写1字节] → [读1字节] → [写1字节] ...
CPU: 100%占用,无法执行其他任务

四、DMA在mmap中的作用

4.1 mmap + DMA = 零拷贝

现在我们可以完整理解mmap的"零拷贝"数据流:

复制代码
步骤1:用户程序调用mmap
       └── 内核建立VMA映射(仅建映射,不读取数据)

步骤2:用户访问mmap地址(如data[0])
       └── MMU缺页中断 → 内核发起IO

步骤3:内核启动DMA传输
       ├── CPU配置DMA控制器
       │   ├── 源:磁盘扇区X
       │   └── 目标:页缓存物理页Y
       ├── CPU启动DMA
       └── CPU继续执行(或被中断)

步骤4:DMA独立工作
       ├── 磁盘 → DMA → 页缓存(物理内存)
       └── 无需CPU参与数据搬运

步骤5:DMA传输完成
       ├── 发送中断给CPU
       └── 内核建立页表映射

步骤6:用户程序继续执行
       └── 直接访问页缓存(虚拟地址→物理地址由MMU转换)

关键

  • 数据拷贝次数:1次(磁盘→页缓存,由DMA完成)
  • CPU参与数据搬运:0次(只参与配置和中断处理)
  • 系统调用次数:1次(mmap本身)

4.2 数据流完整图示

复制代码
用户态               内核态              硬件层
┌──────────┐         ┌──────────┐      ┌──────────┐
│  进程    │         │  页表    │      │  DMA     │
│  虚拟地址│────────▶│  映射    │─────▶│ 控制器   │
│  0x1000  │         │  到物理页│      │          │
└──────────┘         └──────────┘      └────┬─────┘
     │                     │                  │
     │  直接访问           │                  │  DMA传输
     │  (无系统调用)       │                  │  (无CPU参与)
     ▼                     ▼                  ▼
┌──────────┐         ┌──────────┐      ┌──────────┐
│  用户程序│         │  页缓存  │◀─────│  磁盘    │
│  读取数据│         │  (物理页)│      │  文件    │
│  从内存  │         │          │      │          │
└──────────┘         └──────────┘      └──────────┘
     │                     ▲                  │
     │   CPU参与          │  DMA完成         │  CPU只发命令
     │   只管理映射       │  中断通知        │  DMA执行搬运
     └─────────────────────┘                  │
                                               │
                                          CPU发命令启动DMA

五、现代DMA的高级特性

5.1 分散-聚集DMA(Scatter-Gather DMA)

传统DMA要求内存连续,现代DMA支持不连续的内存:

复制代码
// 分散-聚集列表
struct sg_list {
    {addr: 0x1000, len: 4096},
    {addr: 0x2000, len: 4096},
    {addr: 0x3000, len: 4096},
};

// DMA将数据分别写入不连续的内存块
dma_set_sg_list(sg_list, 3);
dma_start();
// 数据分散到多个内存区域,无需CPU重组

5.2 环形缓冲区DMA

网卡驱动常用:

复制代码
// 预先分配环形缓冲区
struct ring_buffer {
    void *buffers[64];  // 64个缓冲区
    int head;
    int tail;
};

// DMA直接写入环形缓冲区
dma_set_ring_buffer(ring_buffer);
dma_start_continuous();

// 驱动处理时直接读取DMA写入的数据

5.3 RDMA(Remote DMA)

跨网络直接内存访问:

复制代码
本地机器               网络              远程机器
┌─────────┐                           ┌─────────┐
│  RDMA   │───── 直接访问远程内存 ────▶│  RDMA   │
│ 网卡    │                           │ 网卡    │
└─────────┘                           └─────────┘
     │                                      │
     │                                      │
   应用程序                              应用程序内存
   内存                                   (被直接读写)

六、实际性能对比

6.1 不同方式读取1GB文件

方式 CPU占用 耗时 上下文切换
PIO(无DMA) 100% ~10秒 无(但CPU忙死)
传统read(DMA) 10% ~500ms 262,144次系统调用
mmap(DMA+页表) 5% ~50ms 3次系统调用

6.2 DMA解放CPU的效果

复制代码
没有DMA:
CPU: [读取1字节] [写入内存] [读取1字节] [写入内存] ...
     100%用于IO,无法运行应用程序

有DMA:
CPU: [配置DMA] [运行应用程序] [响应中断] [运行应用程序] ...
     95%用于应用程序,5%用于IO管理

七、总结

DMA的本质

DMA = 让硬件直接搬运数据,CPU只做指挥

复制代码
传统方式:CPU = 搬运工 + 指挥官
DMA方式: CPU = 指挥官(DMA = 搬运工)

DMA的核心价值

  1. 解放CPU:CPU从数据搬运中解放出来,专注于计算和控制
  2. 提高吞吐量:硬件搬运速度快于CPU逐字节搬运
  3. 降低延迟:减少CPU处理路径,数据更快到达
  4. 支持并行:CPU和DMA可以同时工作

DMA与mmap的关系

复制代码
mmap提供了"不拷贝"的访问路径
DMA提供了"不占CPU"的搬运方式
两者结合 = 零拷贝 + 零CPU开销

适用场景

适合DMA的场景

  • 块设备(磁盘、SSD)
  • 网络设备(网卡)
  • 多媒体(音频、视频)
  • 大数据量传输

不适合DMA的场景

  • 小数据量(DMA配置开销大于收益)
  • 简单设备(无需复杂控制)

DMA是现代计算机系统中不可或缺的技术。它与mmap配合,实现了"数据从磁盘直接到用户程序虚拟地址,全程无需CPU参与"的高性能数据通路。理解DMA,是理解整个IO子系统性能的关键。

相关推荐
xiaoxiangsiyan1 小时前
RHCE之2026年全套考点详解
运维·前端·网络·chrome·学习·rhce
腾科IT教育1 小时前
网络安全与网络空间安全:概念、产业与防护建议
网络·安全·web安全·网络安全·网络空间安全
0Kilobyte1 小时前
自建拨号服务器上网
网络
zhao32668575111 小时前
静态IP代理固定IP与动态IP的根本差异,账号操作安全性如何体现
网络·网络协议·tcp/ip
淼澄研学17 小时前
Kimi API黑产倒卖技术解析与Python合规接入指南
开发语言·网络·python
无锡耐特森19 小时前
Profinet转ModbusTCP协议网关:数字化工厂跨协议互联
网络
MartinYeung519 小时前
[论文学习]ProAct:针对LLM越狱的主动防禦框架
网络·学习·安全
发量惊人的中年网工20 小时前
中小企业网络管理进阶:从设备堆叠到统一管理
服务器·网络·安全·网络安全·php·制造
workflower20 小时前
案例 :某交通厅基于AI技术的智能安全运营实践
网络·人工智能·安全·设计模式·机器人