scatterlist
- 物理内存的散列表。通俗讲,就是把一些分散的物理内存,以列表的形式组织起来
诞生背景
- 假设有三个模块可以访问memory:CPU、DMA控制器和某个外设。
- CPU通过MMU以虚拟地址(VA)的形式访问memory;
- DMA直接以物理地址(PA)的形式访问memory;
- Device通过自己的IOMMU以设备地址(DA)的形式访问memory。
- 然后,某个“软件实体”分配并使用了一片存储空间。该存储空间在CPU视角上(虚拟空间)是连续的,起始地址是va1(实际上,它映射到了3块不连续的物理内存上,我们以pa1,pa2,pa3表示)。那么,如果该软件单纯的以CPU视角访问这块空间(操作va1),则完全没有问题,因为MMU实现了连续VA到非连续PA的映射。
- 不过,如果软件经过一系列操作后,要把该存储空间交给DMA控制器,最终由DMA控制器将其中的数据搬移给某个外设的时候,由于DMA控制器只能访问物理地址,只能以“不连续的物理内存块”为单位递交(而不是我们所熟悉的虚拟地址)。此时,scatterlist就诞生了:为了方便,我们需要使用一个数据结构来描述这一个个“不连续的物理内存块”(起始地址、长度等信息),这个数据结构就是scatterlist。而多个scatterlist组合在一起形成一个表(可以是一个struct scatterlist类型的数组,也可以是kernel帮忙抽象出来的struct sg_table),就可以完整的描述这个虚拟地址了。
- 最后,从本质上说:scatterlist(数组)是各种不同地址映射空间(PA、VA、DA、等等)的媒介(因为物理地址是真实的、实在的存在,因而可以作为通用语言),借助它,这些映射空间才能相互转换(例如从VA转到DA)
介绍
struct scatterlist
-
struct scatterlist用于描述一个在物理地址上连续的内存块(以page为单位),它的定义位于“include/linux/scatterlist.h”中,如下:
- page_link,指示该内存块所在的页面。bit0和bit1有特殊用途(可参考后面的介绍),因此要求page最低4字节对齐。
- offset,指示该内存块在页面中的偏移(起始位置)。
- length,该内存块的长度。
- dma_address,该内存块实际的起始地址(PA,相比page更接近我们人类的语言)。
- dma_length,相应的长度信息。
struct sg_table
- 多个scatterlist组成一个数组,以表示在物理上不连续的虚拟地址空间。通常情况下,使用scatterlist功能的模块,会自行维护这个数组(指针和长度),例如[2]中所提到的struct mmc_data:
-
不过呢,为了使用者可以偷懒,kernel抽象出来了一个简单的数据结构:struct sg_table,帮忙保存scatterlist的数组指针和长度
- 其中sgl是内存块数组的首地址,orig_nents是内存块数组的size,nents是有效的内存块个数(可能会小于orig_nents)。
- scatterlist数组中到底有多少有效内存块呢?这不是一个很直观的事情,主要有如下2个规则决定:
- 1)如果scatterlist数组中某个scatterlist的page_link的bit0为1,表示该scatterlist不是一个有效的内存块,而是一个chain(铰链),指向另一个scatterlist数组。通过这种机制,可以将不同的scatterlist数组链在一起,因为scatterlist也称作chain scatterlist。
- 2)如果scatterlist数组中某个scatterlist的page_link的bit1为1,表示该scatterlist是scatterlist数组中最后一个有效内存块(后面的就忽略不计了)。
- table中sgl数组大小最大为PAGE_SIZE,如果nents个数超过了SG_MAX_SINGLE_ALLOC,那么就需要使用到chain了。具体实现可以查看sg_alloc_table -> __sg_alloc_table
- 使用提供的API函数就可以访问所有的sg了,例如for_each_sg()
参考链接
- Linux内核scatterlist API介绍 - AlanTu - 博客园
- Linux内存管理 - 随笔分类(第2页) - AlanTu - 博客园
- 聊聊scatterlist的chain结构
- Welcome to The Linux Kernel’s documentation! — The Linux Kernel 4.7 documentation
- Linux Kernel Crypto API — The Linux Kernel 4.7 documentation
- 内核解压缩 · Linux Inside 中文版
- Linux内核加密模块crypto的使用 | 杨磊的博客