原本计划本节介绍request的分配,发现会涉及到数据组织从bio到sgl的映射,因此本节介绍数据的SGL组织方式。
在BLOCK层数据的组织形式为bio和request,通过这两个结构体就可以找到数据的位置。但若传输到SCSI层,硬盘位置用scsi command表示,内存中位置用scatterlist表示。本小节讲述scatterlist是如何将数据组织起来的。
结构体scatterlis包含从CPU视角和设备视角看到的数据的地址和长度(包括页内偏移)。但其中page_link有两个特殊作用:(1)当第0 bit为1时即设置SG_CHAIN,表明当前sgl是链接sgl,只起链接到下一个sgl的作用,这个sgl不会指向数据;(2)当第1bit为1时即设置SG_END,表明当前sgl是最后一个sgl,这个sgl仍指向数据。当其他情况时指向数据对应的第一个页信息。
成员offset表示数据在页中的偏移。成员length表示数据的长度。
成员dma_address表示数据的DMA地址即设备看到的地址,成员dma_length表示数据的DMA长度。
SGL分为non-chained SGL和chained SGL。
Non-chained SGL表示多个sgl是连续的,内核这里对最大的non-chained SGL数目做限制,最大不超过SG_MAX_SINGLE_ALLOC,当申请的SGL个数超过此限制时会自动生成chained SGL。
Chained SGL存在多个的SGL链,每个SGL链中的SGL是连续的,其中每个SGL链(除了最后一个SGL链)最后一个SGL起链接作用。每个SGL链最大的SGL数目为SG_CHUNK_SIZE(128)。
函数sg_alloc_table()用来分配non-chained SGL(当申请的数目nents超过限制时,也会生成chained SGL)。
int sg_alloc_table(struct sg_table *table, unsigned int nents, gfp_t gfp_mask)
函数sg_alloc_table_chained()来分配chained SGL。其中nents为需要分配的SGL数目,first_chunk为sg_table中已经存在的SGL链,SGL链数目为first_chunk_nents。后面申请的SGL链与first_chunk相链。
int sg_alloc_table_chained(struct sg_table *table, int nents, struct scatterlist *first_chunk, unsigned nents_first_chunk)
两者最终会调用__sg_alloc_table()。这里处理四种情况:
(1)first_chunk = NULL, nents <= max_ents时
这种情况为sg_table,并没有提前分配SGL链,且需要分配SGL数目小于最大一次性能够分配的数目,此时通过alloc_fn()直接分配即可,生成non-chained SGL链。
(2)first_chunk = NULL, nents > max_ents时
这种情况为sg_table,并没有提前分配SGL链,且需要分配SGL数目大于最大一次性能够分配的数目,此时通过alloc_fn()会分配一个包含max_ents数目的SGL的SGL链,剩下的部分分配另一个SGL链,并将它们链起来,生成chained SGL链。
(3)first_chunk != NULL, nents <= max_ents时
这种情况为sg_table,提前分配SGL链, 且提前分配的SGL链上SGL数目满足需要的SGL链,此时会直接返回当前的sg_table。
(4)first_chunk != NULL, nents > max_ents时
这种情况为sg_table,提前分配SGL链, 但提前分配的SGL链上SGL数目不能够满足nents数目,此时需要通过alloc_fn()分配剩余的数目的SGL组成SGL链,并与提供分配的SGL链连接起来,生成chained SGL链。
在SCSI层在分配request时默认内置了2个SGL(实际真正使用的仅1个,另一个做链接SGL使用),在申请时使用sg_alloc_table_chained(&cmd->sdb.table, nr_segs, cmd->sdb.table.sgl, SCSI_INLINE_SG_CNT)申请链式SGL。
下图为申请150个SGL的情况,其中内置2个SGL为第一个SGL链,这个是随request提前分配的(静态分配),后面两个SGL链是从sgpool中申请分配的(动态分配)。