Linux内核中的DMA管理:直接内存访问机制详解

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的工作原理和使用方法,对于开发高性能的设备驱动至关重要。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值