1. 从“搬家”到数据传输:为什么需要Scatter-Gather List?
想象一下,你正在搬家。你的物品散落在房间的各个角落:书在书架上,衣服在衣柜里,锅碗瓢盆在厨房。现在,搬家公司来了,你有两种选择。第一种,你花一整天时间,把所有东西都从原处拿出来,集中堆到客厅中间,形成一个巨大的包裹,然后让工人一次性搬走。第二种,你直接告诉工人:“书架上的这些书、衣柜里的那几件衣服、厨房的这几个箱子,都帮我搬走。” 显然,第二种方式更高效,因为你省掉了自己“集中整理”这个费时费力的中间步骤。
在计算机的世界里,尤其是当数据需要在内存和设备(比如硬盘、网卡、GPU)之间传输时,也面临着同样的“搬家”问题。应用程序的数据在内存中,往往不是整整齐齐地存放在一个连续的大块里,而是像你的物品一样,分散在内存的各个角落,由操作系统动态分配和管理。这些分散的小块数据,在专业术语里叫做“非连续内存缓冲区”。
传统的、简单的数据传输方式,就像第一种搬家方案。它要求数据在物理内存上必须是连续的。如果数据不连续怎么办?那就得靠CPU这位“苦力”,辛辛苦苦地把分散的数据先拷贝到一个临时开辟的、物理连续的大内存块里,然后再启动设备(比如DMA控制器)从这个连续块里读取数据。传输完成后,可能还需要再拷贝回分散的位置。这个过程我们称之为“聚集”(Gather)和“分散”(Scatter),完全由CPU软件完成,大量消耗宝贵的CPU周期,增加了延迟,也浪费了内存带宽。
而现代计算机硬件,特别是直接内存访问(DMA)控制器,早就变得非常“聪明”了。它们支持一种叫做 Scatter-Gather 的传输模式。这就对应了第二种搬家方案:你不需要自己整理,只需要给DMA控制器一张“物品清单”,上面清楚地写着“第一件物品在A地址,长度是X;第二件物品在B地址,长度是Y……”。DMA控制器拿到这张清单,就能自己按图索骥,一次操作中,自动从多个分散的地址读取数据(Gather),或者将数据写入多个分散的地址(Scatter)。
在Linux内核中,用来描述这张“物品清单”的数据结构,就是 Scatter-Gather List,简称 SGL。它本质上是一个“元数据”数组,数组中的每一项(一个struct scatterlist)都精确描述了一块物理地址连续的内存区域。通过SGL,驱动程序可以告诉DMA控制器:“嘿,伙计,这次要传输的数据分布在这些地方,你照着这个清单去取/存就行了。”
所以,SGL机制的核心价值在于解放CPU,提升效率。它避免了不必要的内存拷贝,让DMA引擎的能力得到充分发挥,特别适合处理网络数据包、文件系统I/O、图形纹理传输等涉及大量非连续数据块的场景。理解SGL,是编写高性能Linux设备驱动和内核模块的必修课。
2. 庖丁解牛:SGL的核心数据结构长什么样?
要玩转SGL,首先得把它拆开看明白。Linux内核中,SGL主要涉及两个关键结构体:struct sg_table 和 struct scatterlist。它们的关系,就像一个文件夹和里面的文件列表。
2.1 总管:struct sg_table
struct sg_table 是SGL的“管理者”或“容器”。它不直接描述内存块,而是管理着描述内存块的scatterlist数组。它的定义非常简洁:
struct sg_table {
struct scatterlist *sgl; // 指向scatterlist数组的指针
unsigned int nents; // 当前表中有效的、已映射的表项数量
unsigned int orig_nents; // 最初分配的表项总数量
};
sgl: 这是一个指针,指向动态分配出来的struct scatterlist数组的首元素。这个数组就是“物品清单”的实体。nents: 这个值非常关键。它表示在当前sg_table中,实际有多少个scatterlist项是有效的、并且已经与真实的物理内存页面建立了映射关系。在DMA映射操作之后,这个值可能会小于orig_nents(例如,某些项可能映射失败)。orig_nents: 记录最初调用sg_alloc_table时请求分配的scatterlist项的总数。它代表了这张“清单”的最大容量。
sg_table的存在,使得内核可以方便地以“表”为单位来分配、使用和释放整个SGL,而不是零散地操作单个scatterlist。
2.2 清单项:struct scatterlist
struct scatterlist 是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 i

1537

被折叠的 条评论
为什么被折叠?



