为request的每一个bio创建DMA映射

xiaoxiao2021-02-28  64

为request的每一个bio创建DMA映射

先了解下几个相关的数据结构

struct scsi_data_buffer { struct sg_table table; unsigned length; int resid; }; struct sg_table { struct scatterlist *sgl; /* the list */ unsigned int nents; //映射后表中有效sg的个数,即用了多少个scatterlist(sg)元素 unsigned int orig_nents; //表中原来有分配了多少个scatterlist(sg)元素 }; struct scatterlist { #ifdef CONFIG_DEBUG_SG unsigned long sg_magic; #endif 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 };

如果对”分散-聚集DMA”以及”scatterlist”不太了解的同学,建议先看下一篇,否则你会看的云里雾里,或许看了下篇你还是云里雾里(2333),读内核代码就是这样,读不懂就多读几遍,不要忽略细节,往往这些细节才是读懂整个逻辑的关键。

req->nr_phys_segments是指请求在物理内存中占据的不连续的段的数目,之前一直误认为是磁盘上不连续的段数。这个细节对我理解下面代码有很大帮助。

static int scsi_init_sgtable(struct request *req, struct scsi_data_buffer *sdb, gfp_t gfp_mask) { int count; /* * 为分散表(sg表)分配存储空间,该表必须能容纳至少与request请求所拥有的(内存)物理段同样多 * 的入口项, */ if (unlikely(scsi_alloc_sgtable(sdb, req->nr_phys_segments, gfp_mask))) { return BLKPREP_DEFER; } req->buffer = NULL; //这个函数为request的每一个bio的每一个段创建DMA映射 count = blk_rq_map_sg(req->q, req, sdb->table.sgl); BUG_ON(count > sdb->table.nents); sdb->table.nents = count; sdb->length = blk_rq_bytes(req); return BLKPREP_OK; }

blk_rq_map_sg从指定的请求rq中获得全部的段,然后把它们填写到给定的表sglist中,在内存中相邻的段

int blk_rq_map_sg(struct request_queue *q, struct request *rq, struct scatterlist *sglist) { struct bio_vec *bvec, *bvprv; struct req_iterator iter; struct scatterlist *sg; int nsegs, cluster; nsegs = 0; cluster = blk_queue_cluster(q); bvprv = NULL;//bvprv代表的上一个遍历的段,作用就是为了看看当前bio的段能否和上一个段合并 sg = NULL; rq_for_each_segment(bvec, rq, iter) //该宏就是遍历rq中么个bio对应的每个段bvec { __blk_segment_map_sg(q, bvec, sglist, &bvprv, &sg, &nsegs, &cluster); } ... if (sg) sg_mark_end(sg); return nsegs;//nsegs代表映射完后分散表中的入口项个数。(也就是有多少个有效sg) } static void __blk_segment_map_sg(struct request_queue *q, struct bio_vec *bvec, struct scatterlist *sglist, struct bio_vec **bvprv, struct scatterlist **sg, int *nsegs, int *cluster) { int nbytes = bvec->bv_len; if (*bvprv && *cluster) { if ((*sg)->length + nbytes > queue_max_segment_size(q)) goto new_segment; if (!BIOVEC_PHYS_MERGEABLE(*bvprv, bvec))//两个段是否在物理内存上相邻 goto new_segment; if (!BIOVEC_SEG_BOUNDARY(q, *bvprv, bvec)) goto new_segment; (*sg)->length += nbytes;//能合并就把该bio的段加入当前sg } else { new_segment: if (!*sg) *sg = sglist;//第一次进入这个函数的时候,sg就是sglist表的第一个元素 else { sg_unmark_end(*sg); *sg = sg_next(*sg);//获取表中下一个sg } sg_set_page(*sg, bvec->bv_page, nbytes, bvec->bv_offset); //设置sg,此时sg的page_link指向的是存储的页地址。 (*nsegs)++; } *bvprv = bvec; }

看完上面request往分散表(sg表)的映射过程后,我们再来看看sg表的分配的过程

static int scsi_alloc_sgtable(struct scsi_data_buffer *sdb, int nents, gfp_t gfp_mask)//nents是req->nr_phys_segments { int ret; ret = __sg_alloc_table(&sdb->table, nents, SCSI_MAX_SG_SEGMENTS, gfp_mask, scsi_sg_alloc); if (unlikely(ret)) __sg_free_table(&sdb->table, SCSI_MAX_SG_SEGMENTS, scsi_sg_free); return ret; } int __sg_alloc_table(struct sg_table *table, unsigned int nents, unsigned int max_ents, gfp_t gfp_mask, sg_alloc_fn *alloc_fn) { struct scatterlist *sg, *prv; /*因为一次申请sg的数量最多不能超过max_ents,如果申请数量nents超过这个值, *就可能需要申请多次,前一次申请的sg就用prv表示 */ unsigned int left; memset(table, 0, sizeof(*table)); if (nents == 0) return -EINVAL; #ifndef ARCH_HAS_SG_CHAIN if (WARN_ON_ONCE(nents > max_ents)) return -EINVAL; #endif left = nents; prv = NULL; do { unsigned int sg_size, alloc_size = left; if (alloc_size > max_ents) { /*进入该分支说明我们申请sg的数量大于一次最大申请数,因此需要申请多次,细心的 *读者会发现此层申请了alloc_size个sg,但只用了sg_size个sg,剩下的一个sg是 *干嘛的呢,后面会发现这个sg作用是将这俩次申请的sg表链接起来,此时sg的 *page_link不在是指向存储数据页,而是指向下一个sg表的头部。 */ alloc_size = max_ents; sg_size = alloc_size - 1; } else sg_size = alloc_size; //说明一次可以分完,不需要链接下一个sg表,因此不用多申请一个sg left -= sg_size; sg = alloc_fn(alloc_size, gfp_mask);//申请alloc_size个sg if (unlikely(!sg)) { if (prv) table->nents = ++table->orig_nents; return -ENOMEM; } sg_init_table(sg, alloc_size); table->nents = table->orig_nents += sg_size; if (prv) sg_chain(prv, max_ents, sg);//将新申请的sg表和上一次申请的链接起来 else table->sgl = sg;//第一次申请sg表时 if (!left) sg_mark_end(&sg[sg_size - 1]); //已经申请完所以sg了,给最后一个元素标上结束标识 prv = sg; } while (left); return 0; }
转载请注明原文地址: https://www.6miu.com/read-40799.html

最新回复(0)