浅析Linux内核scatter-gather list实现

文章目录

概述

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_buffersg_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;
}

相关参考

相关推荐
FoldWinCard7 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计9 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1239 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质9 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX9 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.9 小时前
Linux系统管理
linux·运维
阿龙的工作记录10 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手10 小时前
Linux的基本指令
linux·运维·服务器
hkj880810 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动12 小时前
Linux编程笔记3【个人用】
linux·笔记