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 heredma_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 操作中添加更多的處理邏輯。