关于linux-kernel:scsiallocsgtables函数分析

28次阅读

共计 3330 个字符,预计需要花费 9 分钟才能阅读完成。

scsi_alloc_sgtables 函数剖析

struct scatterlist {
    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
};
示意的是 sg entry 在内存中的地址和长度。

page_link 的作用:
依据 page_link 的 bit0 和 bit1 的具体值(bit0 sg 是否是链,bit1 sg 是否是最初一个)。
两种作用复用,
一是示意 sg entry 指向的内存页 page 构造体,
二是对于链起来的 sg list,示意 sg_table 指向的下一个 sg_table。
一个内存页里,对应 sg_table 外面的各 sg_entry 是内存地址间断的(数组),
如果 sg list 超过一个内存页,就得链起来。

/*
 * 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().
 *
 */

#define SG_CHAIN    0x01UL
#define SG_END        0x02UL

/*
 * We overload the LSB of the page pointer to indicate whether it's
 * a valid sg entry, or whether it points to the start of a new scatterlist.
 * Those low bits are there for everyone! (thanks mason :-)
 */
#define sg_is_chain(sg)        ((sg)->page_link & SG_CHAIN)
#define sg_is_last(sg)        ((sg)->page_link & SG_END)
#define sg_chain_ptr(sg)    \
    ((struct scatterlist *) ((sg)->page_link & ~(SG_CHAIN | SG_END)))

数组的 sg_table:

/**
 * sg_init_table - Initialize SG table
 * @sgl:       The SG table
 * @nents:       Number of entries in table
 *
 * Notes:
 *   If this is part of a chained sg table, sg_mark_end() should be
 *   used only on the last table part.
 *
 **/
void sg_init_table(struct scatterlist *sgl, unsigned int nents)
{memset(sgl, 0, sizeof(*sgl) * nents);
    sg_init_marker(sgl, nents);
}

static inline void sg_init_marker(struct scatterlist *sgl,
                  unsigned int nents)
{sg_mark_end(&sgl[nents - 1]);
}

__sg_alloc_table 被调用:

/*
 * 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))
ret = __sg_alloc_table(table, nents, SG_MAX_SINGLE_ALLOC,
                   NULL, 0, gfp_mask, sg_kmalloc);
int __sg_alloc_table(struct sg_table *table, unsigned int nents,
             unsigned int max_ents, struct scatterlist *first_chunk,
             unsigned int nents_first_chunk, gfp_t gfp_mask,
             sg_alloc_fn *alloc_fn)
{
    struct scatterlist *sg, *prv;
    unsigned int left;
    // 一次调配的最大 size,如果第一个 chunk 有,就先用第一个 chunk 的。unsigned curr_max_ents = nents_first_chunk ?: max_ents;
    unsigned prv_max_ents;

    memset(table, 0, sizeof(*table));

    if (nents == 0)
        return -EINVAL;
#ifdef CONFIG_ARCH_NO_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 > curr_max_ents) {
            alloc_size = curr_max_ents;
// 如果 alloc_size 比一次能申请的最大个数还大,那 sg list 就得链起来,最初一个 sg entry 就必须用作 sg_table 的指针,而不能当做一般的 sg entry,所以理论调配掉的 sg size 比调配的 sg entry 个数要减一。sg_size = alloc_size - 1;
        } else
            sg_size = alloc_size;

        left -= sg_size;

        if (first_chunk) {
            sg = first_chunk;
            first_chunk = NULL;
        } else {sg = alloc_fn(alloc_size, gfp_mask);
        }
        if (unlikely(!sg)) {
            /*
             * Adjust entry count to reflect that the last
             * entry of the previous table won't be used for
             * linkage.  Without this, sg_kfree() may get
             * confused.
             */
            if (prv)
                table->nents = ++table->orig_nents;

            return -ENOMEM;
        }
        // 一次调配的是内存间断的 sg_entry(数组)sg_init_table(sg, alloc_size);
        table->nents = table->orig_nents += sg_size;

        /*
         * If this is the first mapping, assign the sg table header.
         * If this is not the first mapping, chain previous part.
         */
        if (prv)
            sg_chain(prv, prv_max_ents, sg);
        else
            table->sgl = sg;

        /*
         * If no more entries after this one, mark the end
         */
        if (!left)
            sg_mark_end(&sg[sg_size - 1]);

        prv = sg;
        prv_max_ents = curr_max_ents;
        // 如果第一个 chunk 有,用完之后,前面每次都是用的 max_ents(一个内存页能放下的最多 g_entry 的个数)curr_max_ents = max_ents;
    } while (left);

    return 0;
}

正文完
 0