scatterlist && DMA

本文深入探讨了DMA(Direct Memory Access)数据传输机制的核心原理,包括无CPU参与的高效数据传输过程、DMA控制器的作用、缓冲区管理以及硬件与地址转换机制。重点介绍了在不同平台上的DMA使用规则与内存申请技巧,并详细阐述了设备与内存交互时的地址管理策略。同时,文章还讨论了如何在特定设备如MMC上实现DMA操作,通过scatterlist和scatterlist相关操作实现不连续物理空间的有效利用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以是系统CPU从实际的IO数据传输过程中摆脱出来,从而大大提

供系统的吞吐率。DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发地执行其他任务,当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后续处理。

在内存中用于与外设交互数据的一块区域被称作DMA缓冲区,在设备不支持scatter/gatherCSG,分散/聚集操作的情况下,DMA缓冲区必须是物理上联系的。

对于ISA设备而言,其DMA操作只能在16MB以下的内存进行,因此,在使用kmalloc()和__get_free_pages()及其类似函数申请DMA缓冲区时应使用GFP_DMA标志,这样能保证获得的内存是具备DMA能力的。


DMA的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的内存地址,物理地址是从CPU角度上看到的未经转换的内存地址(经过转换的那叫虚拟地址)。

在PC上,对于ISA和PCI而言,总线即为物理地址,但并非每个平台都是如此。由于有时候接口总线是通过桥接电路被连接,桥接电路会将IO地址映射为不同的物理地址。


设备不一定能在所有的内存地址上执行DMA操作,在这种情况下应该通过下列函数执行DMA地址掩码:

int dma_set_mask(struct device *dev, u64 mask);

DMA映射包括两个方面的工作:

  • 分配一片DMA缓冲区;
  • 为这片缓冲区产生设备可访问的地址。

内核中提供了一下函数用于分配一个DMA一致性的内存区域:

void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

这个函数的返回值为申请到的DMA缓冲区的虚拟地址。此外,该函数还通过参数handle返回DMA缓冲区的总线地址。与之对应的释放函数为:

void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);

以下函数用于分配一个写合并(writecombinbing)的DMA缓冲区:

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

与之对应的是释放函数:dma_free_writecombine(),它其实就是dma_free_conherent,只不过是用了#define重命名而已。


对于单个已经分配的缓冲区而言,使用dma_map_single()可实现流式DMA映射:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);  
如果映射成功,返回的是总线地址,否则返回NULL.最后一个参数DMA的方向,可能取DMA_TO_DEVICE, DMA_FORM_DEVICE, DMA_BIDIRECTIONAL和DMA_NONE;
与之对应的反函数是:
void dma_unmap_single(struct device *dev,dma_addr_t *dma_addrp,size_t size,enum dma_data_direction direction);

MMC的scatter list相关操作


MMC作为块设备,它的存储空间,最小单位由struct bio_vec 描述,它代表一段物理地址范围。

struct bio_vec {
	struct page	*bv_page;
	unsigned int	bv_len;
	unsigned int	bv_offset;
};

一次块设备传输请求,会涉及到很多个这样的不连续的物理空间。不连续的物理空间,不能直接使用DMA

这时,可以利用sg操作,让每个bio_vec结构,对应一个scatterlist结构:

struct scatterlist {
    unsigned long    page_link;
    unsigned int    offset;        /* buffer offset         */
    dma_addr_t    dma_address;    /* dma address             */
    unsigned int    length;        /* length             */
};


在MMC的请求处理函数中,遍历每一request中所有bio_vec结构,对应一个scatterlis结构描述:

  rq_for_each_segment(bvec, rq, iter) {
        ... ...
        sg = sg_next(sg);           //指向sg链表中的下一个scatterlist                                 
        sg_set_page(sg, bvec->bv_page, bvec->bv_len, bvec->bv_offset);    //使用sg描述一个页   
        ... ...        
  } 
  ... ...
  sg_mark_end(sg); //标志sg链表到此sg节点就结束了


上面sg这个链表初始化代码如下:

sg_init_table(mq->bounce_sg, bouncesz / 512);
第一个参数为链表头,第二个为成员数量。


上边所涉及到的几个函数:

void sg_init_table(struct scatterlist *sg, unsigned int nents);
sg是sg链表(数组)的表头,nents是要分配的数组的个数。


void sg_set_page(struct scatterlist *sg, struct page *page,
		     unsigned int len, unsigned int offset);
void sg_set_buf(struct scatterlist *sg, const void *buf,
	      	    unsigned int buflen);
使用指定的参数,填充sg结构。第一个以页为页地址,偏移量,长度为接线;第二个以地址和长度为界限。


struct scatterlist *sg_next(struct scatterlist *sg);
返回下一个sg成员地址。


也有一个宏,来遍历sg链表上的所有sg结构,它的使用方法通常如下:

int i;
    struct scatterlist *list, *sgentry;

    /* Fill in list and pass it to dma_map_sg().  Then... */
    for_each_sg(i, list, sgentry, nentries) {
	program_hw(device, sg_dma_address(sgentry), sg_dma_len(sgentry));
    }

sgentry为sg链表入口,nentryies是sg数组(链表)总长。









<think>好的,用户让我介绍一下struct scatterlist。首先,我需要回忆一下这个结构体的用途和相关知识。scatterlist通常用于Linux内核中的DMA(直接内存访问)操作,用来描述分散/聚集(scatter/gather)I/O的数据结构。用户可能是在处理驱动程序或者内核模块时遇到了这个结构体,需要了解它的定义和用法。 接下来,我要确认struct scatterlist的具体成员变量。根据内核源码,它应该包含page_link、offset、length等字段。需要解释每个成员的作用,比如page_link指向内存页,offset是页内偏移,length是数据长度。同时,还要提到SG表的链式结构,可能涉及SG_CHAIN和END等标志。 然后,用户可能想知道如何使用这个结构体。比如初始化sg表,使用sg_init_table或sg_init_one函数,遍历sg表时用sg_next或者for_each_sg宏。这些API的用法需要简明扼要地说明,避免太深入内核细节,但要让用户明白基本操作步骤。 还需要举一个例子,说明在驱动中处理分散/聚集DMA传输的典型流程,比如从用户空间获取数据,初始化sg表,映射DMA地址,执行传输,最后解除映射。这样的例子能帮助用户更好地理解实际应用场景。 另外,用户可能的深层需求是理解如何在开发驱动程序时高效处理非连续内存的DMA操作。因此,除了结构体本身的介绍,还需要强调它的作用和优势,比如减少内存拷贝,提升性能,以及使用时的注意事项,比如内存对齐、DMA地址映射等。 最后,检查是否覆盖了所有关键点:结构体定义、成员解释、相关API、使用示例和注意事项。确保语言通俗易懂,结构清晰,符合用户要求的步骤化解答。同时,注意使用中文,避免技术术语的直译,保持自然流畅。</think>struct scatterlistLinux 内核中用于实现`分散/聚集 I/O`(scatter-gather I/O)的核心数据结构。它在以下场景中尤为重要: 1. **DMA 传输**:当设备需要直接访问多个非连续内存区域时 2. **零拷贝技术**:避免数据在用户空间和内核空间之间的多次复制 3. **网络协议栈**:处理分散的数据包缓冲区 4. **文件系统**:处理非连续页缓存 --- ### 结构体定义(内核版本 5.x) ```c struct scatterlist { unsigned long page_link; // 内存页指针 + 控制标志 unsigned int offset; // 页内偏移量(字节) unsigned int length; // 数据长度(字节) dma_addr_t dma_address; // DMA 总线地址 unsigned int dma_length; // DMA 映射长度 }; ``` --- ### 关键成员解析 | 成员 | 作用 | |---------------|----------------------------------------------------------------------| | `page_link` | 低2位为标志位:`SG_CHAIN`(链接下一表)、`SG_END`(结束标志) | | `offset` | 在内存页中的起始偏移(0 ≤ offset < PAGE_SIZE) | | `length` | 有效数据长度(需满足 offset + length ≤ PAGE_SIZE) | | `dma_address` | 通过`dma_map_sg()`映射后得到的DMA总线地址 | --- ### 典型使用流程 1. **内存分配**: ```c struct scatterlist sg[10]; sg_init_table(sg, ARRAY_SIZE(sg)); // 初始化sg表 ``` 2. **建立映射**: ```c int nents = dma_map_sg(dev, sg, nelems, direction); // 生成DMA地址映射 ``` 3. **设备传输**: ```c // 将sg表信息传递给设备驱动 struct dma_async_tx_descriptor *tx = dev->device_prep_slave_sg(chan, sg, nents, dir, flags); ``` 4. **解除映射**: ```c dma_unmap_sg(dev, sg, nelems, direction); ``` --- ### 实用操作示例 ```c // 遍历sg表的标准方式 struct scatterlist *s; int i; for_each_sg(sglist, s, nents, i) { void *va = page_address(sg_page(s)) + s->offset; dma_addr_t pa = sg_dma_address(s); pr_info("Chunk %d: virt=%p, phys=%pad, len=%u\n", i, va, &pa, sg_dma_len(s)); } ``` --- ### 开发注意事项 1. **内存对齐**:确保每个sg元素满足设备的DMA对齐要求 2. **跨架构兼容**:某些体系结构对sg表有特殊限制(如最大元素数) 3. **缓存一致性**:使用正确的DMA方向(DMA_TO_DEVICE/DMA_FROM_DEVICE) 4. **复合页支持**:内核5.0+支持处理复合页结构 这个结构体通过将非连续内存区域组织成逻辑上连续的链表,有效提升了大数据块传输的效率,是现代高性能I/O子系统的基石之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值