Linux scatterlist 详解

news2024/12/27 10:35:27

源码基于:Linux 5.4

约定:

  • 芯片架构:ARM64
  • 内存架构:UMA
  • CONFIG_ARM64_VA_BITS:39
  • CONFIG_ARM64_PAGE_SHIFT:12
  • CONFIG_PGTABLE_LEVELS :3

0. 前言

之前在《Linux DMA... 零拷贝》博文分享了DMA 技术和零拷贝技术,在进行I/O设备和内存的数据传输的时候,数据搬运的工作全部交给DMA 控制器,而CPU 不再参与任何与数据搬运相关的事情,这样CPU就可以去处理别的事务。

用户层与外设或子系统可能需要大量数据交互,这样就需要大量的内存,而随着系统的长时间运行,物理内存可能存在大量的碎片,想要申请很大的一块物理内存已经成为奢望。并且,从性能方面考虑,用户层和子系统都希望减少交互的次数,更希望一次性将数据都交给 DMA控制器搬运。这就诞生了 scatterlist 的概念。

scatterlist 是 Linux 内核关于内存比较核心数据结构,翻译为 “离散列表”,根据实际使用场景更确切可以理解为 “物理内存的离散列表”,就是通过这个 scatterlist 将物理不同大小的物理内存链接起来,一次性送给 DMA 控制器搬运。

因为光有离散还不够,还需要将这些离散收集,所以在 Linux kernel 中,会看到很多 sg 的名字,这就是 scatter-gather 的缩写。

本文将详细剖析 scatterlist 的原理和使用。

1. 数据结构

1.1 struct sg_table

include/linux/scatterlist.h

struct sg_table {
	struct scatterlist *sgl;	/* the list */
	unsigned int nents;		/* number of mapped entries */
	unsigned int orig_nents;	/* original size of list */
};

如本文的第2 节,分配、初始化sg_table时,会传入需要申请的 sg 数量。为了性能,这些 sg 在申请的时候不是一个一个申请,而是一堆一堆的 sg 数组方式申请,这个sg 数组允许的最大值为SG_MAX_SINGLE_ALLOC:

#define SG_MAX_SINGLE_ALLOC		(PAGE_SIZE / sizeof(struct scatterlist))

即,每次申请最后可以申请  SG_MAX_SINGLE_ALLOC 个sg (即一个page)。每个sg 结构体是32 bytes,所以系统允许一次性申请 sg 个数为 4096 / 32 = 128

sgl:每次申请的 sg 数组内部的物理内存是连续的,但每个 sg数组的首地址可能不相同,所以需要连接起来。sgl 就是用来串联每个 sg 数组;

orig_nents:sgl 中有效的 sg 个数。这里之所以说到 “有效”,是因为 sg 数组的最后一个都用来串联使用;

nents:mapped entries;

sg_table的 sgl 管理图如下:

如图,假设这里有 4 个 sg 数组:

  • 第一个sg 数组的首地址会存入 sg_table 的 sql 中;
  • 每一个 sg 数组的最后一个 sg 为sg 铰链(chain),指向下一个 sg 数组,其成员page_link 的 bit[0] bit[1] 将作为铰链的状态:
    • 若都为0,表示其为有效的、普通的 sg;
    • 若 bit[0] = 1表示该sg 为铰链 sg;
    • 若 bit[1] = 1表示该sg为 结束sg;

1.1 struct scatterlist

include/linux/scatterlist.h

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

page_link:

  • 对于chain sg 来说,记录下一个 SG 数组的首地址,并且用bit[0] 和 bit[1] 来表示是chain sg 还是 end sg;
  • 对于 end sg 来说,只有bit[1] 为1,其他无意义;
  • 对于普通 sg 来说,记录的是关联的内存页块的地址;

为什么可以使用 bit[0] 和 bit[1] ?

  • 当一次分配 sg个数为 SG_MAX_SINGLE_ALLOC,将从 buddy中分配 1 个page,该页块的首地址是页对齐的,此地址 bit[0] ~ bit[11] 都为0,所以其中 bit[0] 和 big[1] 可以另作他用;
  • 而当分配的 sg 数量不够SG_MAX_SINGLE_ALLOC,则会从 kmalloc 中分配  object,但这个 object 所在的slab 也是页对齐的,而 struct scatterlist 为 32 个字节,所以该 object 的首地址的 bit[0] ~ bit[4] 都为0,所以其中 bit[0] 和 big[1] 也可以另作他用。

offset:关联页块的页内偏移量;

length:关联的内存页块大小;

dma_address:

dma_length:

2. sg_alloc_table()

lib/scatterlist.c

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, SG_MAX_SINGLE_ALLOC, 0, sg_kfree);

	return ret;
}
EXPORT_SYMBOL(sg_alloc_table);

参数:

  • table:sg_table 的地址;
  • nents:sgl 的个数,根据此值创建nents 个sg;
  • gfp_mask:进行sg_kmalloc() 时需要的分配掩码;

该函数核心调用的是 __sg_alloc_table(),其中有两个参数:

  • sg_kmalloc:回调函数,用以批量分配 sg 的内存;
  • SG_MAX_SINGLE_ALLOC:系统规定了每次 sg_kmalloc 的最大个数,即每次批量申请的sg 总内存最多为 1个 page,每个sg 结构体是32 bytes,所以系统允许一次性申请 sg 个数为 4096 / 32 = 128 个

2.1 __sg_alloc_table()

lib/scatterlist.c


int __sg_alloc_table(struct sg_table *table, unsigned int nents,
		     unsigned int max_ents, struct scatterlist *first_chunk,
		     unsigned int nents_first_chunk, gfp_t gfp_mask,
		     sg_alloc_fn *alloc_fn)
{
	struct scatterlist *sg, *prv;
	unsigned int left;
	unsigned curr_max_ents = nents_first_chunk ?: max_ents;
	unsigned prv_max_ents;

    //准备初始化 sg_table,先memset
	memset(table, 0, sizeof(*table));

    //sg 条目数量不能为0
	if (nents == 0)
		return -EINVAL;
#ifdef CONFIG_ARCH_NO_SG_CHAIN
	if (WARN_ON_ONCE(nents > max_ents))
		return -EINVAL;
#endif

    //初始化还没有申请的sg数目
	left = nents;
	prv = NULL;
	do {
		unsigned int sg_size, alloc_size = left;

        //确定此次需要申请的sg 个数
        //申请的sg超过最大值,将分多次分配
		if (alloc_size > curr_max_ents) {
			alloc_size = curr_max_ents;
			sg_size = alloc_size - 1;     //申请的sg数组中,最后一个作为一个chain,不作为有效sg
		} else
			sg_size = alloc_size;

        //还剩余多少sg没有申请
		left -= sg_size;

		if (first_chunk) {
			sg = first_chunk;
			first_chunk = NULL;
		} else {
			sg = alloc_fn(alloc_size, gfp_mask); //调用sg分配的回调函数
		}
		if (unlikely(!sg)) {
			/*
			 * Adjust entry count to reflect that the last
			 * entry of the previous table won't be used for
			 * linkage.  Without this, sg_kfree() may get
			 * confused.
			 */
			if (prv)
				table->nents = ++table->orig_nents;

			return -ENOMEM;
		}

        /*
         * 初始化此次申请的sg 数组,这些sg 在物理上是连续的,所以可以直接memset
         * 另外,还会调用sg_mark_end() 初始化最后一个sg为 end sg
         */
		sg_init_table(sg, alloc_size);

        //更新sg_table->nents,初始化时 nents和orig_nents相同
		table->nents = table->orig_nents += sg_size;

		/*
         * 当再次进入循环时,说明需要的nents是大于max_nents的,那么上一次申请肯定是按照最大值
         * 申请.
         * 第一次申请时,会将sg数组放入sg_table的sgl
         * 当再进入循环时,需要连接新建的sg数组,所以要将prv的最后一个sg设为CHAIN
         */
		if (prv)
			sg_chain(prv, prv_max_ents, sg);
		else
			table->sgl = sg;

		//如果没剩余sg需要分配了,将推出循环,此时将最新分配的sg数组的最后一个sg设为END
		if (!left)
			sg_mark_end(&sg[sg_size - 1]);

		prv = sg;
		prv_max_ents = curr_max_ents; //能进入下一个循环的话,上一个sg数组肯定按最大值申请的
		curr_max_ents = max_ents;
	} while (left);

	return 0;
}
EXPORT_SYMBOL(__sg_alloc_table);

2.2 sg_kmalloc()

lib/scatterlist.c

static struct scatterlist *sg_kmalloc(unsigned int nents, gfp_t gfp_mask)
{
	if (nents == SG_MAX_SINGLE_ALLOC) {
		/*
		 * Kmemleak doesn't track page allocations as they are not
		 * commonly used (in a raw form) for kernel data structures.
		 * As we chain together a list of pages and then a normal
		 * kmalloc (tracked by kmemleak), in order to for that last
		 * allocation not to become decoupled (and thus a
		 * false-positive) we need to inform kmemleak of all the
		 * intermediate allocations.
		 */
		void *ptr = (void *) __get_free_page(gfp_mask);
		kmemleak_alloc(ptr, PAGE_SIZE, 1, gfp_mask);
		return ptr;
	} else
		return kmalloc_array(nents, sizeof(struct scatterlist),
				     gfp_mask);
}

函数比较简单,当申请的时候按照 SG_MAX_SINGLE_ALLOC,那么是一次性申请 4K 内存,系统直接调用 __get_free_page() 从buddy 中分配;当没有达到 4K 内存,则通过 kmalloc_array() 申请 ;

 

3. sg_free_table()

lib/scatterlist.c

void sg_free_table(struct sg_table *table)
{
	__sg_free_table(table, SG_MAX_SINGLE_ALLOC, false, sg_kfree);
}
EXPORT_SYMBOL(sg_free_table);

是sg_alloc_table() 的逆过程,唯一不同的是回调函数换成 sg_kfree()。

 

3.1 __sg_free_table()

lib/scatterlist.c

void __sg_free_table(struct sg_table *table, unsigned int max_ents,
		     unsigned int nents_first_chunk, sg_free_fn *free_fn)
{
	struct scatterlist *sgl, *next;
	unsigned curr_max_ents = nents_first_chunk ?: max_ents;

	if (unlikely(!table->sgl))
		return;

	sgl = table->sgl;
	while (table->orig_nents) {
		unsigned int alloc_size = table->orig_nents; //sg_table中还有多少个sg
		unsigned int sg_size;

        /**
         * 如果sg_table中sg大于max,取出下一个sg 数组的首地址存入next,
         * 如果sg_table中所剩不够max,那这应该是最后一个sg数组了
         */
		if (alloc_size > curr_max_ents) {
			next = sg_chain_ptr(&sgl[curr_max_ents - 1]);
			alloc_size = curr_max_ents;
			sg_size = alloc_size - 1;
		} else {
			sg_size = alloc_size;
			next = NULL;
		}

		table->orig_nents -= sg_size;
		if (nents_first_chunk)
			nents_first_chunk = 0;
		else
			free_fn(sgl, alloc_size); //调用释放回调函数
		sgl = next;
		curr_max_ents = max_ents;
	}

	table->sgl = NULL;
}
EXPORT_SYMBOL(__sg_free_table);

 

3.2 sg_kfree()

lib/scatterlist.c

static void sg_kfree(struct scatterlist *sg, unsigned int nents)
{
	if (nents == SG_MAX_SINGLE_ALLOC) {
		kmemleak_free(sg);
		free_page((unsigned long) sg);
	} else
		kfree(sg);
}

该函数是 sg_kmalloc() 的逆过程,当申请 sg 按照SG_MAX_SINGLE_ALLOC,则该sg 数组是从 buddy 申请,所以调用 free_page() 归还给 buddy;当不是按照 SG_MAX_SINGLE_ALLOC,则是从 kmalloc() 申请得来的,所以调用 kfree() 归还给kmalloc。

4. 其他 API

4.1 sg_mark_end()

include/linux/scatterlist.h

static inline void sg_mark_end(struct scatterlist *sg)
{
	/*
	 * Set termination bit, clear potential chain bit
	 */
	sg->page_link |= SG_END;
	sg->page_link &= ~SG_CHAIN;
}

清除bit[0] 中的 chain 标识,配上 bit[1] 上的 END 标识,即将该 sg 配置为 END;

4.2 sg_unmark_end()

include/linux/scatterlist.h

static inline void sg_unmark_end(struct scatterlist *sg)
{
	sg->page_link &= ~SG_END;
}

解除 END 标识; 

4.3 sg_chain()

include/linux/scatterlist.h

static inline void sg_chain(struct scatterlist *prv, unsigned int prv_nents,
			    struct scatterlist *sgl)
{
	/*
	 * offset and length are unused for chain entry.  Clear them.
	 */
	prv[prv_nents - 1].offset = 0;
	prv[prv_nents - 1].length = 0;

	/*
	 * Set lowest bit to indicate a link pointer, and make sure to clear
	 * the termination bit if it happens to be set.
	 */
	prv[prv_nents - 1].page_link = ((unsigned long) sgl | SG_CHAIN)
					& ~SG_END;
}

用以配置铰链 sg,offset 和 length 为0,通过该函数将当前的sg 数组与下一个 sg 数组通过 chain sg 捆绑在一起。

4.4 sg_set_page()

include/linux/scatterlist.h

static inline void sg_set_page(struct scatterlist *sg, struct page *page,
			       unsigned int len, unsigned int offset)
{
	sg_assign_page(sg, page);
	sg->offset = offset;
	sg->length = len;
}

本函数用以将当前 sg 与内存页块进行关联。

参数:

  • sg:当前 sg;
  • page:需要关联的内存页块;
  • len:页块的大小;
  • offset:页块内偏移量;

4.5 sg_assign_page()

include/linux/scatterlist.h

static inline void sg_assign_page(struct scatterlist *sg, struct page *page)
{
	unsigned long page_link = sg->page_link & (SG_CHAIN | SG_END);

	/*
	 * In order for the low bit stealing approach to work, pages
	 * must be aligned at a 32-bit boundary as a minimum.
	 */
	BUG_ON((unsigned long) page & (SG_CHAIN | SG_END));
#ifdef CONFIG_DEBUG_SG
	BUG_ON(sg_is_chain(sg));
#endif
	sg->page_link = page_link | (unsigned long) page;
}

本函数用以将当前 sg->page_link 重新关联到新的内存页块,但保留之前的bit[0] 和 bit[1] 属性;

参数:

  • sg:当前 sg;
  • page:需要关联的内存页块;

4.6 判断chain/last

//判断当前sg是否为chain
#define sg_is_chain(sg)		((sg)->page_link & SG_CHAIN) 

//判断当前sg是否为last
#define sg_is_last(sg)		((sg)->page_link & SG_END)

4.7 获取sg和page

//chain sg用来获取下一个指向的sg数组
#define sg_chain_ptr(sg)	\                            
	((struct scatterlist *) ((sg)->page_link & ~(SG_CHAIN | SG_END)))

//获取sg关联的页块地址
static inline struct page *sg_page(struct scatterlist *sg)
{
#ifdef CONFIG_DEBUG_SG
	BUG_ON(sg_is_chain(sg));
#endif
	return (struct page *)((sg)->page_link & ~(SG_CHAIN | SG_END));
}

参考博客:

ION 内存管理器——system heap

DMA 与 scatterlist 技术简介

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1273622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序员养生之道

程序员养生之道——如何保持健康的生活方式 摘要: 随着互联网行业的快速发展,越来越多的人选择从事程序员这个职业。然而,长时间的工作和高强度的压力容易导致程序员出现亚健康状况,如头发稀疏、视力下降、肥胖等问题。因此&#…

字节大佬整理测试用例编写规范

目录 1.1目的 1.2使用范围 二 测试用例编写原则 2.1系统性 2.2连贯性 2.3全面性 2.4正确性 2.5符合正常业务惯例 2.6仿真性 2.7容错性(健壮性) 三 测试用例设计方法 3.1 等价类划分法: 3.2 边界值分析法: 3.3 因果图…

python简单通讯录

代码: # 定义联系人类 class Contact:def __init__(self, name, phone, street, city, province, postcode):self.name nameself.phone phoneself.street streetself.city cityself.province provinceself.postcode postcodedef __str__(self):return f"…

shell编程系列(6)-使用Sort进行数据排序

文章目录 前言使用Sort命令文本排序sort命令的选项如下:基本排序对单个指定列进行排序对多个指定列进行排序删除重复的行 结语 前言 shell脚本通常用作一些自动化的操作,但是在有些场景下例如科研运算,有时候会产生大量的运算结果文件&#…

学习记录PCL-1 通过哈希表进行三维点云的虚拟格网划分

直接对整个场景的点云进行特征提取,效果很差,因此通过划分区域格网进行划分。格网划分有很多种方式,在这里尝试使用哈希表进行格网链接,后续通过在每个格网内基于点云特征进行提取。 参考博客: 点云侠的PCL 点云分块_p…

揭示堆叠自动编码器的强大功能 - 最新深度学习技术

简介 在不断发展的人工智能和机器学习领域,深度学习技术由于其处理复杂和高维数据的能力而获得了巨大的普及。在各种深度学习模型中,堆叠自动编码器[1]作为一种多功能且强大的工具脱颖而出,用于特征学习、降维和数据表示。本文探讨了堆叠式自…

影视网站影视介绍评论网站

基于Boostrap的响应式电影网站影视网站移动技术应用开发一、考核要求1、考核内容本课程的成绩主要依据平时表现、文档、答辩(设计讲解、操作演示等)等方面评定。运用所学知识,独立设计并开发一个影视介绍评论网站,具体要求如下&am…

凯旋泥巴佬正式全面亮相,Triumph TF 250-X

之前给大家曝光过凯旋将要进军越野车领域,并且发动机和车架都已经造出来了,还要去参加赛事想要获得比赛的荣誉和名次,就在今日凯旋的一台越野车TF 250-X正式亮相,售价9995美元。 这是凯旋真正意义上的第一台竞赛级越野车&#xff…

特征选择的方法:包裹法之递归特征消除

1.递归特征消除法的基本原理 递归特征消除法是一种贪婪的优化算法,致力于通过反复创建模型的方式找到性能最佳的特征子集。 首先将筛选的K个特征作为初始特征子集,开展机器学习计算得到每个特征的重要性,利用交叉验证方法得到初始特征子集的…

智慧水务系统在流域水环境规划中起到什么作用?

智慧水务系统在流域水环境规划中扮演着越来越重要的角色。作为一款集信息化、自动化、智能化、智慧化于一体的水务管理系统,智慧水务系统不仅能够提高水环境规划的效率,还能为水资源的保护和利用提供有力支持。 在流域水环境规划中,智慧水务系…

Windows10设置定时提醒

文章目录 Windows10设置定时提醒创建提醒文件新建文本文档修改文件编码和后缀双击测试 创建文件夹创建任务测试运行 Windows10设置定时提醒 创建提醒文件 新建文本文档 修改文件编码和后缀 双击测试 创建文件夹 创建任务 创建触发器 选择程序 测试运行 弹窗正常

16.Oracle数据库Row_number() over()函数排序使用方法

1.原始数据(部分) SELECT * FROM SCOTT.EMP ; 2.使用Row_number() over() 函数,排序 SELECT EMPNO,ENAME,SAL,DEPTNO,Row_number() over( order by sal) rs FROM SCOTT.EMP ; 根据工资排序并添加序号 3.使用Row_number() over() 函数,分组并排序 SELECT EMPNO,ENAME,SAL,DEPTN…

深眸科技|AI+机器视觉加速进入工业制造,成为提升智能制造必选项

随着人工智能技术的飞速发展,AI视觉与机器视觉技术正在加速进入工业制造中。机器视觉技术赋予机器新活力与新智慧,不仅让机器实现精密工作的完成,还让其能够代替部分脑力活动,具有更高的智慧与能力。 深眸科技CEO周礼表示&#x…

自动伸缩:解密HPA、VPA、CA和CPA智能调整应用大小和数量

关注【云原生百宝箱】公众号,快速掌握云原生 Kubernetes提供了多种自动伸缩机制,例如HPA(Horizontal Pod Autoscaling),可以根据不同情况动态调整Pod副本数量。此功能使 Pod 能够有效地处理当前流量,而无需…

Android启动系列之进程杀手--lmkd

本文概要 这是Android系统启动的第三篇文章,本文以自述的方式来讲解lmkd进程,通过本文您将了解到lmkd进程在安卓系统中存在的意义,以及它是如何杀进程的。(文中的代码是基于android13) 我是谁 init:“大…

P8A004-系统加固-磁盘访问权限

【预备知识】 访问权限,根据在各种预定义的组中用户的身份标识及其成员身份来限制访问某些信息项或某些控制的机制。访问控制通常由系统管理员用来控制用户访问网络资源(如服务器、目录和文件)的访问,并且通常通过向用户和组授予…

字节的“游戏心脏”,真的停止跳动了吗?

字节跳动在游戏行业的宏大叙事,终究变成了一场游戏一场梦。 近日,朝夕光年首次公开回应此前的裁员传闻,明确表示会有业务方向和组织调整,游戏业务将迎来大规模收缩,未来会更加聚焦部分创新型游戏及相关技术的探索。 …

管理类联考——写作——考点+记忆篇——论说文——记忆

文章目录 整体文字提炼/目录大纲法图像畅想/记忆宫殿法 审题立意论说文-历年真题命题类型总结 写作结构论说文-万能写作结构 分析段标题开头段结尾过渡段和措施段 整体 整体用记忆宫殿法、绘图记忆法等 文字提炼/目录大纲法 利用书本的目录、大纲,一本好书&#xff…

增加F110 付款方式的随手记录

随便记录一下,基本上有这些信息可以了 为了保持PRD与测试机一致的银行代码,需要先在DEV,QAS 改成4 外部给号 主要都是在FBZP 开户行维护-FI12_HBANK/FI12 S4hana 里面有的没有办法在FI12 维护只能去NWBC NWBC:维护银行账户并关联…

MatchPyramid实现文本匹配

引言 今天利用MatchPyramid实现文本匹配。 原论文解析→点此←。 MatchPyramid 核心思想是计算两段文本间的匹配矩阵,把它当成一个图形利用多层卷积网络提取不同层级的交互模式。 匹配矩阵是通过计算两段输入文本基本单元(比如字或词)之间相似度得到的&#xf…