概述
Linux Scatter-Gather List(离散/聚合列表,简称SGL)是Linux内核中用于描述物理内存不连续内存块的数据结构,主要用于DMA传输和IO操作。现代的DMA控制器普遍都支持Scatter-Gather方式进行数据传输,通过SGL直接描述数据分布,可以避免单独申请物理连续内存拷贝数据,减少CPU参于的数据搬运,提升IO效率。
数据结构
Linux内核使用struct sg_table
结构描述Scatter-Gather List,其定义如下;
struct sg_table {
struct scatterlist *sgl; // SGL表项数组首地址
unsigned int nents; // 已映射的SGL表项数量
unsigned int orig_nents; // SGL表项总数量
};
sg_table结构记录了SGL表项数组的首地址,具体的SGL表项信息由struct scatterlsit
结构进行描述,当数据块较多,单个SGL表项数组无法记录时,数组内的最后一个SGL表项会指向下一个SGL表项数组,构成多级SGL,如下:
单个SGL表项数组支持的数量由SG_MAX_SINGLE_ALLOC
进行定义,并与物理内存页面的大小强相关,如下:
#define SG_MAX_SINGLE_ALLOC (PAGE_SIZE / sizeof(struct scatterlist))
struct scatterlist
struct scatterlist
是Linux内核SGL的核心数据结构,用于描述一个物理地址连续的内存块,它的定义如下:
struct scatterlist {
unsigned long page_link;
unsigned int offset;
unsigned int length;
dma_addr_t dma_address;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
unsigned int dma_length;
#endif
#ifdef CONFIG_NEED_SG_DMA_FLAGS
unsigned int dma_flags;
#endif
};
数据结构主要字段意义如下:
- page_link:记录数据块所在物理页面信息,其中page_link低2位保留,有特殊含义:
bit 0
:SG_CHAIN,记录下一个 SGL表项数组的首地址;bit 1
:SG_END,指示当前SGL表项是SGL中的最后一个表项。
- offset:记录数据块在物理页面中起始偏移;
- length:记录数据块大小;
- dma_address:DMA地址,设备传输数据使用。
API操作接口
申请分配SGL
int sg_alloc_table(struct sg_table *table, unsigned int nents, gfp_t gfp_mask)
{
int ret;
ret = __sg_alloc_table(table, nents, SG_MAX_SINGLE_ALLOC,
NULL, 0, gfp_mask, sg_kmalloc);
if (unlikely(ret))
sg_free_table(table);
return ret;
}
遍历SGL表项
for_each_sg
遍历SGL中所有的有效scatterlist:
#define for_each_sg(sglist, sg, nr, __i) \
for (__i = 0, sg = (sglist); __i < (nr); __i++, sg = sg_next(sg))
sg_next
返回当前SGL表项的下一个表项,通常下一个表项位于SGL表项数组的下一项,在多级SGL的情况下,可能跳到下一级SGL表项数组的首项。
struct scatterlist *sg_next(struct scatterlist *sg)
{
if (sg_is_last(sg))
return NULL;
sg++;
if (unlikely(sg_is_chain(sg)))
sg = sg_chain_ptr(sg);
return sg;
}
SGL数据拷贝
SGL API提供了sg_copy_from_buffer
和sg_copy_to_buffer
接口,用于从线性缓冲区拷贝数据到分离的数据块,或反之。sg_copy_xxx_buffer
调用sg_copy_buffer
进行实现:
size_t sg_copy_buffer(struct scatterlist *sgl, unsigned int nents, void *buf,
size_t buflen, off_t skip, bool to_buffer)
{
unsigned int offset = 0;
struct sg_mapping_iter miter;
unsigned int sg_flags = SG_MITER_ATOMIC;
if (to_buffer)
sg_flags |= SG_MITER_FROM_SG;
else
sg_flags |= SG_MITER_TO_SG;
sg_miter_start(&miter, sgl, nents, sg_flags);
if (!sg_miter_skip(&miter, skip))
return 0;
while ((offset < buflen) && sg_miter_next(&miter)) {
unsigned int len;
len = min(miter.length, buflen - offset);
if (to_buffer)
memcpy(buf + offset, miter.addr, len);
else
memcpy(miter.addr, buf + offset, len);
offset += len;
}
sg_miter_stop(&miter);
return offset;
}