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

来源:互联网 发布:华为研发投入 知乎 编辑:程序博客网 时间:2024/06/08 15:29

为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;}
原创粉丝点击