scatterlist

在内核驱动程序的很多代码中,都能够看到类似sgdma的内容,sgdma全称为Scatter/Gather DMA(散列/收集 DMA),内核中抽象了scatterlist和sg table用来描述和管理这种需要做散列和收集的DMA缓冲区。在内核中设计scatterlist,主要出于两方面的原因,从硬件层面来说,目前很多DMA控制器都支持硬件的SG DMA模式,由开发人员在内存中构建一个链式的DMA缓冲区描述符(Buffer Descriptor, BD),DMA控制器根据这个链式BD进行一对多、多对一或者多对多的拷贝操作。例如Xilinx的AXI DMA IPCore,参考PG021文档中的介绍,该IP核的Block Diagram中就集成了Scatter/Gather逻辑电路

 使能了SG模式后的DMA BD描述符,其内部有一个Next字段,用于指向下一个描述符的地址,控制器通过这个Next字段即可遍历整个BD链,而遍历过程完全是由硬件完成,软件只需要构造这个BD链

 Gather是将多个离散的buffers拷贝到一个连续的buffer中

 Scatter是将一个连续的buffer拷贝到多个离散的buffers中

 从软件层面来说,对于存在虚拟内存管理的系统而言,由应用程序创建的大内存块在虚拟地址空间中是连续的,但是在物理内存的层面,大概率是离散的,而如果想要在一个单独的I/O操作中搬运这些由应用层创建的物理非连续buffer,比较笨重的办法是申请与其大小相当的连续物理页面,并将数据先拷贝到这些物理页面上,然后再进行DMA操作。但是这样做的缺陷也很明显,一方面是增加了数据拷贝的过程,性能较差,另一方面是增加了对连续物理页面资源的要求,在长时间使用过后碎片化严重的物理内存中,申请一大块连续物理内存会非常困难。使用scatterlist,结合DMA的SG功能,能够非常有效的节省对大块连续物理内存的要求,也避免了不必要的额外拷贝过程

数据结构

刚接触scatterlist的朋友可能容易被这个结构体的名字误导,scatterlist本身并不是一种链表结构,而只是用于描述一个单独的内存块

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
};

page_link用于记录该内存块所在的页面号,page_link的bit[0]和bit[1]有特殊用途,因此页面必须是4字节对齐的。bit[0]为1表示该节点是一个铰链,为0表示一个普通内存节点,铰链是用于将多个链串成一个更大的链。bit[1]为1表示该节点是尾节点,为0表示一个普通节点。offset表示内存块在页面内的偏移,length表示内存块长度,dma_address表示物理地址

要使用scatterlist,开发者可以自己定义一个scatterlist的数组

struct scatterlist sgt[8];

也可以使用内核提供好的封装后的sg table结构

struct sg_table {
	struct scatterlist *sgl;	/* the list */
	unsigned int nents;		/* number of mapped entries */
	unsigned int orig_nents;	/* original size of list */
};

其中sgl是一个指向scatterlist数组的指针,nents是scatterlist数组的长度,orig_nents是scatterlist的原始长度

创建

创建一个scatterlist链有两种方式,一种是静态声明,并调用API进行初始化

struct scatterlist sgt[8];
sg_init_table(sgt, 8);

sg_init_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);
#ifdef CONFIG_DEBUG_SG
	{
		unsigned int i;
		for (i = 0; i < nents; i++)
			sgl[i].sg_magic = SG_MAGIC;
	}
#endif
	sg_mark_end(&sgl[nents - 1]);
}
EXPORT_SYMBOL(sg_init_table);

传入的参数sgl是scatterlist的数组地址,nents是数组长度,函数内部将数组内存初始化,然后调用了sg_mark_end将数组的最后一个元素的page_link标记为尾节点

另一种方法是动态创建

struct sg_table sgt;
sg_alloc_table(&sgt, 8, GFP_KERNEL);

 sg_alloc_table()函数定义如下

/**
 * sg_alloc_table - Allocate and initialize an sg table
 * @table:	The sg table header to use
 * @nents:	Number of entries in sg list
 * @gfp_mask:	GFP allocation mask
 *
 *  Description:
 *    Allocate and initialize an sg table. If @nents@ is larger than
 *    SG_MAX_SINGLE_ALLOC a chained sg table will be setup.
 *
 **/
int sg_alloc_table(struct sg_table *table, unsigned int nents, gfp_t gfp_mask)
{
	int ret;

	ret = __sg_alloc_table(table, nents, SG_MAX_SINGLE_ALLOC,
			       NULL, gfp_mask, sg_kmalloc);
	if (unlikely(ret))
		__sg_free_table(table, SG_MAX_SINGLE_ALLOC, false, sg_kfree);

	return ret;
}
EXPORT_SYMBOL(sg_alloc_table);

其内部通过__sg_alloc_table()来动添申请内存创建。这里的SG_MAX_SINGLE_ALLOC是一个由于各种原因的限制,即动态申请的连续内存的sg table必须在一个页面中,默认一个页面4096的话,scatterlist结构体大小20字节,那么一个页面最大能够创建大小为204的scatterlist链,而如果要创建更大数目的话,需要通过铰链的方式将多个分散在不同页面的scatterlist链串联起来,这在__sg_alloc_table()内部已经实现,用户不需要关心

使用

通常的应用场景是将应用程序分配的buffer映射到sg table上

void map_user_buf_to_sgl(struct sg_table *sgt, void __user *buf, int len)
{
    int page_nr = (((unsigned long)buf + len + PAGE_SIZE - 1) -
				 ((unsigned long)buf & PAGE_MASK))
				>> PAGE_SHIFT; 
    
    /* alloc sg table */
    sg_alloc_table(sgt, pages_nr, GFP_KERNEL);

    /* alloc page ptr */
    struct page **pages = kcalloc(pages_nr, sizeof(struct page *), GFP_KERNEL);
    
    /* buf to pages */
    get_user_pages_fast((unsigned long)buf, pages_nr, 1/* write */,
				pages);
    
    struct scatterlist *sg = sgt->sgl;
    for (i = 0; i < pages_nr; i++, sg = sg_next(sg)) {
        unsigned int offset = offset_in_page(buf);
        sg_set_page(sg, pages[i], len, offset);
    }
}

首先根据buf和len得到页面数量,然后根据页面数量创建对应长度的sg table,然后获取buf对应的页面,最后遍历sg table,通过sg_set_page()将页面设置到scatterlist上

其他的一些API

  • sg_alloc_table_from_pages:从一组页面创建sg table

  • sg_nents:获取sg的entity数量

  • sg_copy_from_buffer:从一个连续的buffer拷贝到sg list

  • sg_chain:将两个单独的sg list串联

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wei.Studio

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值