Linux內核scatterlist API介紹


1. 前言

我們在那些需要和用戶空間交互大量數據的子系統(例如MMC[1]、Video、Audio等)中,經常看到scatterlist的影子。對我們這些“非英語母語”的人來說,初見這個詞匯,腦袋瞬間就蒙圈了。scatter可翻譯成“散開、分散”,list是“列表”的意思,因而scatterlist可翻譯為“散列表”。“散列表”又是什么?太抽象了!

之所以抽象,是因為這個詞省略了主語----物理內存(Physical memory),加上后,就好理解了多了,既:物理內存的散列表。再通俗一些,就是把一些分散的物理內存,以列表的形式組織起來。那么,也許你會問,有什么用處呢?

當然有用,具體可參考本文后續的介紹。

2. scatterlist產生的背景

我沒有去考究scatterlist API是在哪個kernel版本中引入的(年代太久遠了),憑猜測,我覺得應該和MMU有關。因為在引入MMU之后,linux系統中的軟件將不得不面對一個困擾(下文將以圖片1中所示的系統架構為例進行說明):

假設在一個系統中(參考下面圖片1)有三個模塊可以訪問memory:CPU、DMA控制器和某個外設。CPU通過MMU以虛擬地址(VA)的形式訪問memory;DMA直接以物理地址(PA)的形式訪問memory;Device通過自己的IOMMU以設備地址(DA)的形式訪問memory。

然后,某個“軟件實體”分配並使用了一片存儲空間(參考下面圖片2)。該存儲空間在CPU視角上(虛擬空間)是連續的,起始地址是va1(實際上,它映射到了3塊不連續的物理內存上,我們以pa1,pa2,pa3表示)。

那么,如果該軟件單純的以CPU視角訪問這塊空間(操作va1),則完全沒有問題,因為MMU實現了連續VA到非連續PA的映射。

不過,如果軟件經過一系列操作后,要把該存儲空間交給DMA控制器,最終由DMA控制器將其中的數據搬移給某個外設的時候,由於DMA控制器只能訪問物理地址,只能以“不連續的物理內存塊”為單位遞交(而不是我們所熟悉的虛擬地址)。

此時,scatterlist就誕生了:為了方便,我們需要使用一個數據結構來描述這一個個“不連續的物理內存塊”(起始地址、長度等信息),這個數據結構就是scatterlist(具體可參考下面第3章的說明)。而多個scatterlist組合在一起形成一個表(可以是一個struct scatterlist類型的數組,也可以是kernel幫忙抽象出來的struct sg_table),就可以完整的描述這個虛擬地址了。

最后,從本質上說:scatterlist(數組)是各種不同地址映射空間(PA、VA、DA、等等)的媒介(因為物理地址是真實的、實在的存在,因而可以作為通用語言),借助它,這些映射空間才能相互轉換(例如從VA轉到DA)。

cpu_dma_device_memory

圖片1 cpu_dma_device_memory

    

cpu_view_memory

圖片2 cpu_view_memory

3. scatterlist API介紹

3.1 struct scatterlist

struct scatterlist用於描述一個在物理地址上連續的內存塊(以page為單位),它的定義位於“include/linux/scatterlist.h”中,如下:

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,指示該內存塊所在的頁面。bit0和bit1有特殊用途(可參考后面的介紹),因此要求page最低4字節對齊。
offset,指示該內存塊在頁面中的偏移(起始位置)。
length,該內存塊的長度。

dma_address,該內存塊實際的起始地址(PA,相比page更接近我們人類的語言)。
dma_length,相應的長度信息。

3.2 struct sg_table

在實際的應用場景中,單個的scatterlist是沒有多少意義的,我們需要多個scatterlist組成一個數組,以表示在物理上不連續的虛擬地址空間。通常情況下,使用scatterlist功能的模塊,會自行維護這個數組(指針和長度),例如[2]中所提到的struct mmc_data:

struct mmc_data {
    …

        unsigned int            sg_len;         /* size of scatter list */     
        struct scatterlist      *sg;            /* I/O scatter list */         
        s32                     host_cookie;    /* host private data */        
};

 

不過呢,為了使用者可以偷懶,kernel抽象出來了一個簡單的數據結構:struct sg_table,幫忙保存scatterlist的數組指針和長度:

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

其中sgl是內存塊數組的首地址,orig_nents是內存塊數組的size,nents是有效的內存塊個數(可能會小於orig_nents)。

以上心思都比較直接,不過有一點,我們要仔細理解:

scatterlist數組中到底有多少有效內存塊呢?這不是一個很直觀的事情,主要有如下2個規則決定:

1)如果scatterlist數組中某個scatterlist的page_link的bit0為1,表示該scatterlist不是一個有效的內存塊,而是一個chain(鉸鏈),指向另一個scatterlist數組。通過這種機制,可以將不同的scatterlist數組鏈在一起,因為scatterlist也稱作chain scatterlist。

2)如果scatterlist數組中某個scatterlist的page_link的bit1為1,表示該scatterlist是scatterlist數組中最后一個有效內存塊(后面的就忽略不計了)。

3.3 API介紹

理解了scatterlist的含義之后,再去看“include/linux/scatterlist.h”中的API,就容易多了,例如(簡單介紹一下,不再詳細分析):

#define sg_dma_address(sg)      ((sg)->dma_address)

#ifdef CONFIG_NEED_SG_DMA_LENGTH
#define sg_dma_len(sg)          ((sg)->dma_length)
#else
#define sg_dma_len(sg)          ((sg)->length)
#endif

sg_dma_address、sg_dma_len,獲取某一個scatterlist的物理地址和長度。

#define sg_is_chain(sg)         ((sg)->page_link & 0x01)
#define sg_is_last(sg)          ((sg)->page_link & 0x02)
#define sg_chain_ptr(sg)        \
        ((struct scatterlist *) ((sg)->page_link & ~0x03))

sg_is_chain可用來判斷某個scatterlist是否為一個chain,sg_is_last可用來判斷某個scatterlist是否是sg_table中最后一個scatterlist。

sg_chain_ptr可獲取chain scatterlist指向的那個scatterlist。

static inline void sg_assign_page(struct scatterlist *sg, struct page *page)
static inline void sg_set_page(struct scatterlist *sg, struct page *page,
                               unsigned int len, unsigned int offset)
static inline struct page *sg_page(struct scatterlist *sg)
static inline void sg_set_buf(struct scatterlist *sg, const void *buf,
                               unsigned int buflen)

#define for_each_sg(sglist, sg, nr, __i)        \
        for (__i = 0, sg = (sglist); __i < (nr); __i++, sg = sg_next(sg))

static inline void sg_chain(struct scatterlist *prv, unsigned int prv_nents,
                             struct scatterlist *sgl)

static inline void sg_mark_end(struct scatterlist *sg)
static inline void sg_unmark_end(struct scatterlist *sg)

static inline dma_addr_t sg_phys(struct scatterlist *sg)
static inline void *sg_virt(struct scatterlist *sg)

sg_assign_page,將page賦給指定的scatterlist(設置page_link字段)。
sg_set_page,將page中指定offset、指定長度的內存賦給指定的scatterlist(設置page_link、offset、len字段)。
sg_page,獲取scatterlist所對應的page指針。
sg_set_buf,將指定長度的buffer賦給scatterlist(從虛擬地址中獲得page指針、在page中的offset之后,再調用sg_set_page)。

for_each_sg,遍歷一個scatterlist數組(sglist)中所有的有效scatterlist(考慮sg_is_chain和sg_is_last的情況)。

sg_chain,將兩個scatterlist 數組捆綁在一起。

sg_mark_end、sg_unmark_end,將某個scatterlist 標記(或者不標記)為the last one。

sg_phys、sg_virt,獲取某個scatterlist的物理或者虛擬地址。

等等(不再羅列了,感興趣的同學直接去看代碼就行了)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM