最近写一个驱动,涉及DMA内存管理,不可避免的需要了解到scatterlist。网上能够搜到很多方面的资料,例如这个和这个。同时内核源码里scatterlist的实现代码include/linux/scatterlist.h和lib/scatterlist.c也不是很长,稍微读读源码也能对它的原理和使用有更深的理解。这里参考的内核版本为4.14。
这里不对scatterlist的原理和使用做解释说明,只是聊聊我自己在看以上材料时比较疑惑的一点:scatterlist是如何组成链的。
struct scatterlist结构体内有一个成员unsigned long page_link,头文件中对其有特别有一段注释进行说明:
/*
* Notes on SG table design.
*
* We use the unsigned long page_link field in the scatterlist struct to place
* the page pointer AND encode information about the sg table as well. The two
* lower bits are reserved for this information.
*
* If bit 0 is set, then the page_link contains a pointer to the next sg
* table list. Otherwise the next entry is at sg + 1.
*
* If bit 1 is set, then this sg entry is the last element in a list.
*
* See sg_next().
*
*/
以上文字说明page_link的低两位是用做chain的标识位的,以便将scatterlist链起来。那么这么多的scatterlist是如何组织起来的呢?这就涉及到struct sg_table了。
sg_table的sgl成员为struct scatterlist类型指针,指向数组,数组成员类型即是一个scatterlist内存。数组成员个数则由table的nents成员表示。这是一个典型的数组指针加成员个数的搭配,如此看来,似乎这样就足够了,为什么还需要chain呢?看看sg_alloc_table()的一段注释:
* Description:
* Allocate and initialize an sg table. If @nents@ is larger than
* SG_MAX_SINGLE_ALLOC a chained sg table will be setup.
而SG_MAX_SINGLE_ALLOC的定义如下:
/*
* Maximum number of entries that will be allocated in one piece, if
* a list larger than this is required then chaining will be utilized.
*/
#define SG_MAX_SINGLE_ALLOC (PAGE_SIZE / sizeof(struct scatterlist))
可以得知,table中sgl数组大小最大为PAGE_SIZE,如果nents个数超过了SG_MAX_SINGLE_ALLOC,那么就需要使用到chain了。具体实现可以查看sg_alloc_table -> __sg_alloc_table。总的来说,当table中所要包含的scatterlist的个数没有超过SG_MAX_SINGLE_ALLOC时,例如只有10个,其数组的组织如下图所示:
sg_table.sgl -----> +---------+
sg_table.nents=10 | entry 0 |
+---------+
| entry 1 |
+---------+
| entry 2 |
+---------+
......
+---------+
| entry 7 |
+---------+
| entry 8 |
+---------+
| entry 9 |
+---------+
当个数超过了SG_MAX_SINGLE_ALLOC(假设为10)时,加入为20,其数组的组织如下所示:
sg_table.sgl -----> +---------+ +--> +---------+ +--> +---------+
sg_table.nents=20 | entry 0 | | | entry 9 | | | entry 18|
+---------+ | +---------+ | +---------+
| entry 1 | | | entry 10| | | entry 19|
+---------+ | +---------+ | +---------+
| entry 2 | | | entry 11| | | entry 20|
+---------+ | +---------+ | +---------+
...... | ...... |
+---------+ | +---------+ |
| entry 7 | | | entry 16| |
+---------+ | +---------+ |
| entry 8 | | | entry 17| |
+---------+ | +---------+ |
| next | ---+ | next | ---+
+---------+ +---------+
上图中,每个next的entry,其pagelink&(~0x3)为下一个数组的首地址,sg_is_chain将返回为真。而最后entry 20为end,其sg_is_last(sg)则将返回为真。
不过对于使用者来说,其实不必理会一个sg_table内部是如何链起来各个sg的,使用提供的API函数就可以访问所有的sg了,例如for_each_sg()。
至于为什么需要设置page_size的大小限制呢,我觉得应该是分配一个page作为内存管理的基本单位,一个page一个page的获取比一下子获取多个连续的page更容易吧。