Linux-scatterlist
在内核驱动程序的很多代码中,都能够看到类似sgdma的内容,sgdma全称为Scatter/Gather DMA(散列/收集 DMA),内核中抽象了scatterlist和sg table用来描述和管理这种需要做散列和收集的DMA缓冲区。在内核中设计scatterlist,主要出于两方面的原因,从硬件层面来说,目前很多DMA控制器都支持硬件的SG DMA模式,由开发人员在内存中构建一个链式的DMA缓冲区描述符(Buffer Descriptor, BD),DMA控制器根据这个链式BD进行一对多、多对一或者多对多的拷贝操作。例如Xilinx的AXI DMA IPCore,参考PG021文档中的介绍,该IP核的Block Diagram中就集成了Scatter/Gather逻辑电路
使能了SG模式后的DMA BD描述符,其内部有一个Next字段,用于指向下一个描述符的地址,控制器通过这个Next字段即可遍历整个BD链,而遍历过程完全是由硬件完成,软件只需要构造这个BD链
Gather是将多个离散的buffers拷贝到一个连续的buffer中
Scatter是将一个连续的buffer拷贝到多个离散的buffers中
从软件层面来说,对于存在虚拟内存管理的系统而言,由应用程序创建的大内存块在虚拟地址空间中是连续的,但是在物理内存的层面,大概率是离散的,而如果想要在一个单独的I/O操作中搬运这些由应用层创建的物理非连续buffer,比较笨重的办法是申请与其大小相当的连续物理页面,并将数据先拷贝到这些物理页面上,然后再进行DMA操作。但是这样做的缺陷也很明显,一方面是增加了数据拷贝的过程,性能较差,另一方面是增加了对连续物理页面资源的要求,在长时间使用过后碎片化严重的物理内存中,申请一大块连续物理内存会非常困难。使用scatterlist,结合DMA的SG功能,能够非常有效的节省对大块连续物理内存的要求,也避免了不必要的额外拷贝过程
数据结构
刚接触scatterlist的朋友可能容易被这个结构体的名字误导,scatterlist本身并不是一种链表结构,而只是用于描述一个单独的内存块
1 | struct scatterlist { |
page_link
用于记录该内存块所在的页面号,page_link
的bit[0]和bit[1]有特殊用途,因此页面必须是4字节对齐的。bit[0]为1表示该节点是一个铰链,为0表示一个普通内存节点,铰链是用于将多个链串成一个更大的链。bit[1]为1表示该节点是尾节点,为0表示一个普通节点。offset
表示内存块在页面内的偏移,length
表示内存块长度,dma_address
表示物理地址
要使用scatterlist,开发者可以自己定义一个scatterlist的数组
1 | struct scatterlist sgt[8]; |
也可以使用内核提供好的封装后的sg table结构
1 | struct sg_table { |
其中sgl
是一个指向scatterlist数组的指针,nents
是scatterlist数组的长度,orig_nents
是scatterlist的原始长度
创建
创建一个scatterlist链有两种方式,一种是静态声明,并调用API进行初始化
1 | struct scatterlist sgt[8]; |
sg_init_table()函数定义如下
1 | /** |
传入的参数sgl是scatterlist的数组地址,nents是数组长度,函数内部将数组内存初始化,然后调用了sg_mark_end
将数组的最后一个元素的page_link
标记为尾节点
另一种方法是动态创建
1 | struct sg_table sgt; |
sg_alloc_table()函数定义如下
1 | /** |
其内部通过__sg_alloc_table()
来动添申请内存创建。这里的SG_MAX_SINGLE_ALLOC
是一个由于各种原因的限制,即动态申请的连续内存的sg table必须在一个页面中,默认一个页面4096的话,scatterlist结构体大小20字节,那么一个页面最大能够创建大小为204的scatterlist链,而如果要创建更大数目的话,需要通过铰链的方式将多个分散在不同页面的scatterlist链串联起来,这在__sg_alloc_table()
内部已经实现,用户不需要关心
使用
通常的应用场景是将应用程序分配的buffer映射到sg table上
1 | void map_user_buf_to_sgl(struct sg_table *sgt, void __user *buf, int len) |
首先根据buf和len得到页面数量,然后根据页面数量创建对应长度的sg table,然后获取buf对应的页面,最后遍历sg table,通过sg_set_page()
将页面设置到scatterlist上
其他的一些API
sg_alloc_table_from_pages:从一组页面创建sg table
sg_nents:获取sg的entity数量
sg_copy_from_buffer:从一个连续的buffer拷贝到sg list
sg_chain:将两个单独的sg list串联