Linux Kernel Scatterlist 使用指南
1. 简介
scatterlist
结构在 Linux 内核中主要用于 DMA(直接内存访问)操作中的内存管理。它允许将不连续的物理内存片段表示为一个逻辑上的连续块,从而使 DMA 操作可以高效地处理这些不连续的内存片段。
2. 设计思想
在 DMA 操作中,数据的源或目标可能分散在物理内存的不同位置。scatterlist
提供了一种机制,将这些分散的内存片段组合在一起,使 DMA 控制器能够处理这些数据,从而提高内存操作的效率和灵活性。
3. scatterlist
结构
scatterlist
结构体定义在 <linux/scatterlist.h>
头文件中,主要成员包括:
struct scatterlist {
unsigned long page_link;
unsigned int offset;
dma_addr_t dma_address;
unsigned int length;
};
page_link
:指向内存页的指针及一些标志。offset
:内存页内的偏移量。dma_address
:DMA 设备使用的地址。length
:此段内存的长度。
4. 使用步骤
使用 scatterlist
主要包括以下几个步骤:
4.1 初始化 scatterlist
在使用 scatterlist
之前,需要先分配并初始化它。
struct scatterlist *sg;
int nents = 10; // Scatterlist 条目的数量
sg = kmalloc_array(nents, sizeof(*sg), GFP_KERNEL);
if (!sg)
return -ENOMEM;
sg_init_table(sg, nents);
4.2 填充 scatterlist
将内存区域填充到 scatterlist
中。
for (i = 0; i < nents; i++) {
sg_set_page(&sg[i], page, PAGE_SIZE, 0);
}
4.3 映射 scatterlist
到 DMA 地址空间
在进行 DMA 传输之前,需要将 scatterlist
映射到 DMA 地址空间。
dma_addr_t dma_handle;
dma_handle = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
4.4 传输数据
使用映射后的 scatterlist 进行 DMA 数据传输。此步骤取决于你的具体 DMA 控制器和驱动程序。以下是一个简单的示例,假设你的 DMA 控制器支持 dmaengine 框架:
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
struct dma_chan *chan = /* your DMA channel */;
tx = dmaengine_prep_slave_sg(chan, sg, dma_nents, DMA_MEM_TO_DEV, flags);
if (!tx) {
pr_err("Failed to prepare DMA transfer\n");
goto unmap;
}
cookie = tx->tx_submit(tx);
if (dma_submit_error(cookie)) {
pr_err("Failed to submit DMA transfer\n");
goto unmap;
}
dma_async_issue_pending(chan);
// 等待DMA传输完成(可以是中断或轮询)
4.5 解除映射
传输完成后,需要解除 scatterlist
的 DMA 映射。
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);
5. 注意事项
- 内存分配:确保分配的内存足够大,可以容纳所有的
scatterlist
条目。 - 映射和解除映射:确保在使用前正确映射
scatterlist
,传输完成后及时解除映射,以防止内存泄漏或数据损坏。 - 内存对齐:确保内存地址和长度满足 DMA 控制器的对齐要求。
- 错误处理:处理好内存分配失败和 DMA 操作失败的情况。
6. 示例代码
以下是一个完整的示例,展示了如何使用 scatterlist
进行 DMA 操作:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
static int __init my_module_init(void)
{
struct scatterlist *sg;
int nents = 10;
int i;
dma_addr_t dma_handle;
struct device *dev = /* your device */;
struct page *page;
sg = kmalloc_array(nents, sizeof(*sg), GFP_KERNEL);
if (!sg)
return -ENOMEM;
sg_init_table(sg, nents);
for (i = 0; i < nents; i++) {
page = alloc_page(GFP_KERNEL);
if (!page) {
pr_err("Failed to allocate page
");
goto out;
}
sg_set_page(&sg[i], page, PAGE_SIZE, 0);
}
dma_handle = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
if (!dma_handle) {
pr_err("Failed to map scatterlist
");
goto out;
}
// Perform DMA operation here
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);
out:
for (i = 0; i < nents; i++) {
if (sg[i].page_link)
__free_page(sg_page(&sg[i]));
}
kfree(sg);
return 0;
}
static void __exit my_module_exit(void)
{
// Cleanup code here
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Scatterlist Example");
这个示例展示了如何分配、初始化、填充、映射和解除映射 scatterlist
进行 DMA 操作。根据具体需求,你可以在 DMA 操作中添加更多的处理逻辑。