文章摘要
DMA(直接内存访问)与程序控制IO的主要区别在于CPU参与程度。程序控制IO(如轮询串口数据)需要CPU持续检查状态并逐字节搬运数据(代码示例展示忙等待过程),效率低下。而DMA模式下(如硬盘数据读取),CPU仅需配置源/目标地址和长度(伪代码示例),启动后DMA控制器自动完成传输,CPU可处理其他任务。实际应用中,操作系统的read()等接口可能隐藏DMA细节,嵌入式开发(如STM32)则通过HAL库直接调用DMA功能。核心优势在于DMA解放了CPU资源,提升系统并发性能。
(字数:150)
一、没有DMA(程序控制IO/轮询IO)代码例子
以读取串口(UART)数据为例,很多单片机/嵌入式系统都用这种方式。
1. 伪代码流程
#define UART_DATA_REG (*(volatile unsigned char*)0x4000) // 假设串口数据寄存器地址
#define UART_STATUS_REG (*(volatile unsigned char*)0x4001) // 假设串口状态寄存器地址
#define UART_RX_READY 0x01 // 数据准备好标志
void read_uart_data(char* buffer, int length) {
for(int i = 0; i < length; i++) {
// 轮询等待数据准备好
while((UART_STATUS_REG & UART_RX_READY) == 0) {
// CPU一直在这里忙等,不能干别的事
}
// 读取数据
buffer[i] = UART_DATA_REG;
}
}
说明:
- CPU每次都要检查状态寄存器,数据准备好才读。
- 这就是“老板亲自一趟一趟搬快递”,CPU全程参与。
二、有DMA的伪代码例子
以读取硬盘数据到内存为例(实际代码通常在驱动层,这里简化说明流程)。
1. 伪代码流程
// 假设有DMA控制器的寄存器
#define DMA_SRC_ADDR (*(volatile unsigned int*)0x5000)
#define DMA_DST_ADDR (*(volatile unsigned int*)0x5004)
#define DMA_LENGTH (*(volatile unsigned int*)0x5008)
#define DMA_CONTROL (*(volatile unsigned int*)0x500C)
#define DMA_START 0x01
void dma_read_from_device(unsigned int device_addr, char* buffer, int length) {
// 1. 设置DMA源地址(外设缓冲区)
DMA_SRC_ADDR = device_addr;
// 2. 设置DMA目标地址(内存)
DMA_DST_ADDR = (unsigned int)buffer;
// 3. 设置传输长度
DMA_LENGTH = length;
// 4. 启动DMA
DMA_CONTROL = DMA_START;
// 5. CPU可以去干别的事,等DMA完成后会触发中断
}
说明:
- CPU只负责配置DMA,启动后就不用管了。
- DMA控制器自动搬运数据,搬完后通过中断通知CPU。
三、对比总结
方式 | 代码特点 | CPU状态 |
---|---|---|
程序控制IO | 轮询+逐字节搬运 | 全程参与 |
DMA方式 | 配置DMA,自动搬运 | 可做其他任务 |
四、现实中的应用层代码
在操作系统中,应用层代码通常看不到DMA的细节,比如用read()
读取文件,底层可能用DMA,也可能不用,取决于驱动实现。
// 应用层代码
int fd = open("/dev/ttyS0", O_RDONLY);
char buf[100];
read(fd, buf, 100); // 你看不到底层是轮询还是DMA
五、嵌入式开发中DMA的常见用法(STM32为例)
// STM32 HAL库伪代码
HAL_UART_Receive_DMA(&huart1, buffer, length);
// 启动DMA接收,CPU不用管,接收完成后会回调中断函数