Linux内核中的DMA管理:直接内存访问机制详解
作为一名深耕操作系统和嵌入式开发的工程师,我对Linux内核中的DMA(Direct Memory Access)管理机制有着深入的理解。DMA是一种重要的硬件特性,它允许外设直接访问系统内存,减少CPU的负担。
DMA的基本概念
DMA的核心思想是:
- 直接访问:外设直接访问系统内存,不需要CPU介入
- 减少中断:减少CPU中断次数,提高系统性能
- 批量传输:支持大吞吐量的数据传输
DMA的类型
1. 系统DMA
系统DMA由芯片组提供,通常用于传统外设:
- PCI DMA:PCI设备的DMA传输
- ISA DMA:传统ISA设备的DMA传输
2. 设备自带DMA
现代设备通常集成自己的DMA控制器:
- 网络控制器:支持DMA的网络接口卡
- 存储控制器:支持DMA的磁盘控制器
- 音频控制器:支持DMA的音频设备
DMA的核心API
1. 分配DMA缓冲区
// 分配一致性DMA缓冲区
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp)
{
// 分配物理连续的内存
// 确保CPU和DMA设备都能访问
return __dma_alloc_coherent(dev, size, dma_handle, gfp);
}
// 分配流式DMA缓冲区
void *dma_alloc_noncoherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp)
{
// 分配内存(可能不连续)
// 需要显式同步
return __dma_alloc_noncoherent(dev, size, dma_handle, gfp);
}
// 释放DMA缓冲区
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle)
dma_free_noncoherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle)
2. DMA映射
// 映射内存到DMA地址(写操作)
dma_addr_t dma_map_single(struct device *dev, void *addr, size_t size, enum dma_data_direction direction)
// 映射内存到DMA地址(批量操作)
int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
// 取消映射
dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction)
dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
3. 同步操作
// 同步CPU写操作到DMA设备
dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction)
// 同步DMA设备写操作到CPU
dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction)
// 批量同步
dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
DMA的实现原理
1. DMA地址映射
// DMA地址映射结构
struct dma_mapping_ops {
void *(*alloc_coherent)(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp);
void (*free_coherent)(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle);
dma_addr_t (*map_single)(struct device *dev, void *ptr, size_t size, enum dma_data_direction direction);
void (*unmap_single)(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
int (*map_sg)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
void (*unmap_sg)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
void (*sync_single_for_cpu)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction);
void (*sync_single_for_device)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction);
void (*sync_sg_for_cpu)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
void (*sync_sg_for_device)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
};
2. DMA引擎
// DMA引擎结构
struct dma_chan {
struct dma_device *device;
struct list_head device_node;
dma_cookie_t cookie;
spinlock_t cookie_lock;
struct list_head client_list;
struct tasklet_struct tasklet;
enum dma_status status;
void *private;
};
// DMA设备
struct dma_device {
struct list_head channels;
struct device *dev;
char name[32];
int chancnt;
struct dma_chan *chan;
struct module *owner;
struct dma_device_ops *ops;
};
使用场景
1. 网络设备
// 网络设备的DMA使用
struct sk_buff *skb = netdev_alloc_skb(dev, size);
if (!skb)
return -ENOMEM;
// 分配DMA缓冲区
void *buf = dma_alloc_coherent(&pdev->dev, size, &dma_addr, GFP_ATOMIC);
if (!buf)
goto free_skb;
// 映射到DMA地址
skb->dma_addr = dma_addr;
// 启动DMA传输
dev->dma_engine->start_tx(dma_addr, size);
2. 存储设备
// 磁盘设备的DMA使用
struct request *req = blk_fetch_request(q);
if (!req)
return;
// 准备散射/聚集列表
struct scatterlist sg[SG_MAX];
int nents = blk_rq_map_sg(q, req, sg);
if (!nents) {
blk_end_request(req, -EIO, blk_rq_bytes(req));
return;
}
// 映射SG列表
dma_map_sg(&pdev->dev, sg, nents, DMA_FROM_DEVICE);
// 启动DMA传输
dma_async_memcpy_issue_pending(chan);
3. 音频设备
// 音频设备的DMA使用
struct snd_pcm_substream *substream = ...;
// 分配DMA缓冲区
void *buf = dma_alloc_coherent(&pdev->dev, substream->runtime->dma_bytes, &substream->runtime->dma_addr, GFP_KERNEL);
if (!buf) {
return -ENOMEM;
}
// 设置音频缓冲区
substream->runtime->dma_area = buf;
substream->runtime->dma_addr = dma_handle;
// 启动DMA传输
dmaengine_submit(tx_desc);
dma_async_issue_pending(chan);
性能优化建议
1. 选择合适的DMA缓冲区类型
// 对于持续使用的缓冲区,使用一致性DMA
void *buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
// 对于临时缓冲区,使用流式DMA
void *buf = kmalloc(size, GFP_KERNEL);
dma_addr_t dma_handle = dma_map_single(dev, buf, size, DMA_TO_DEVICE);
2. 批量传输
// 使用散射/聚集列表进行批量传输
struct scatterlist sg[4];
// 填充SG列表
sg_init_table(sg, 4);
sg_set_buf(&sg[0], buf1, size1);
sg_set_buf(&sg[1], buf2, size2);
sg_set_buf(&sg[2], buf3, size3);
sg_set_buf(&sg[3], buf4, size4);
// 映射SG列表
int nents = dma_map_sg(dev, sg, 4, DMA_TO_DEVICE);
3. 避免频繁映射/取消映射
// 错误:频繁映射/取消映射
for (int i = 0; i < 1000; i++) {
dma_addr_t dma_addr = dma_map_single(dev, buf, size, DMA_TO_DEVICE);
// 传输
dma_unmap_single(dev, dma_addr, size, DMA_TO_DEVICE);
}
// 正确:一次性映射
DMA_addr_t dma_addr = dma_map_single(dev, buf, size * 1000, DMA_TO_DEVICE);
for (int i = 0; i < 1000; i++) {
// 传输
}
dma_unmap_single(dev, dma_addr, size * 1000, DMA_TO_DEVICE);
4. 合理设置DMA缓冲区大小
// 选择合适的缓冲区大小
size_t buffer_size = PAGE_SIZE * 4; // 16KB
void *buf = dma_alloc_coherent(dev, buffer_size, &dma_handle, GFP_KERNEL);
常见陷阱
1. 忘记同步
// 错误:使用非一致性DMA但没有同步
void *buf = dma_alloc_noncoherent(dev, size, &dma_handle, GFP_KERNEL);
// 写入数据到缓冲区
memcpy(buf, data, size);
// 错误:没有同步就启动DMA
dev->start_dma(dma_handle, size);
// 正确:同步后再启动
memcpy(buf, data, size);
dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE);
dev->start_dma(dma_handle, size);
2. 缓冲区对齐
// 确保缓冲区对齐
#define DMA_ALIGNMENT 16
void *buf = kzalloc(size + DMA_ALIGNMENT - 1, GFP_KERNEL);
void *aligned_buf = (void *)(((unsigned long)buf + DMA_ALIGNMENT - 1) & ~(DMA_ALIGNMENT - 1));
3. 内存屏障
// 在DMA操作前后使用内存屏障
dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE);
// 内存屏障确保所有写操作完成
mb();
// 启动DMA传输
dev->start_dma(dma_handle, size);
调试技巧
1. 查看DMA统计
# 查看DMA通道信息
cat /proc/dma
# 查看PCI设备的DMA信息
lspci -v | grep -A 10 "DMA"
2. DMA错误处理
// 检查DMA映射失败
int ret = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
if (!ret) {
dev_err(dev, "DMA mapping failed\n");
return -ENOMEM;
}
总结
DMA是Linux内核中实现高性能数据传输的关键机制,它通过减少CPU干预,提高了系统的整体性能。作为嵌入式开发者,理解DMA的工作原理和使用方法,对于开发高性能的设备驱动至关重要。
4129

被折叠的 条评论
为什么被折叠?



