1. 程式人生 > 實用技巧 >宋寶華:關於linux記憶體管理中DMA ZONE和dma_alloc_coherent若干誤解的澄清【轉】

宋寶華:關於linux記憶體管理中DMA ZONE和dma_alloc_coherent若干誤解的澄清【轉】

轉自:https://blog.csdn.net/21cnbao/article/details/79133658

本文已首先在Linuxer公眾號(ID: LinuxDev)發表,先轉回我的blog也發表。轉載請註明出處。

1.DMA ZONE的大小是16MB?

這個答案在32位X86計算機的條件下是成立的,但是在其他的絕大多數情況下都不成立。

首先我們要理解DMA ZONE產生的歷史原因是什麼。DMA可以直接在記憶體和外設之間進行資料搬移,對於記憶體的存取來講,它和CPU一樣,是一個訪問master,可以直接訪問記憶體。

DMA ZONE產生的本質原因是:不一定所有的DMA都可以訪問到所有的記憶體,這本質上是硬體的設計限制。

在32位X86計算機的條件下,ISA實際只可以訪問16MB以下的記憶體。那麼ISA上面假設有個網絡卡,要DMA,超過16MB以上的記憶體,它根本就訪問不到。所以Linux核心乾脆簡單一點,把16MB砍一刀,這一刀以下的記憶體單獨管理。如果ISA的驅動要申請DMA buffer,你帶一個GFP_DMA標記來表明你想從這個區域申請,我保證申請的記憶體你是可以訪問的。

DMA ZONE的大小,以及DMA ZONE要不要存在,都取決於你實際的硬體是什麼。比如我在CSR工作的時候,CSR的primaII晶片,儘管除SD MMC控制器以外的所有的DMA都可以訪問整個4GB記憶體,但MMC控制器的DMA只能訪問256MB,我們就把primaII對應Linux的DMA ZONE設為了256MB,詳見核心:arch/arm/mach-prima2/common.c

#ifdef CONFIG_ARCH_PRIMA2

static const char *const prima2_dt_match[] __initconst = {

"sirf,prima2",

NULL

};

DT_MACHINE_START(PRIMA2_DT, "Generic PRIMA2 (Flattened Device Tree)")

/* Maintainer: Barry Song <[email protected]> */

.l2c_aux_val = 0,

.l2c_aux_mask = ~0,

.dma_zone_size = SZ_256M,

.init_late = sirfsoc_init_late,

.dt_compat = prima2_dt_match,

MACHINE_END

#endif

不過CSR這個公司由於早前已經被Q記收購,已經不再存在,一起幻滅的,還有當年掛在汽車前窗上的導航儀。這不禁讓我想起我們當年在ADI arch/blackfin裡面寫的程式碼,也漸漸快幾乎沒有人用了一樣。

一代人的芳華已逝,面目全非,重逢雖然談笑如故,可不難看出歲月給每個人帶來的改變。原諒我不願讓你們看到我們老去的樣子,就讓程式碼,留住我們芬芳的年華吧........

下面我們架空歷史,假設有一個如下的晶片,裡面有5個DMA,A、B、C都可以訪問所有記憶體,D只能訪問32MB,而E只能訪問64MB,你覺得Linux的設計者會把DMA ZONE設定為多大?當然是32MB,因為如果設定為64MB,D從DMA ZONE申請的記憶體就可能位於32MB-64MB之間,申請了它也訪問不了。

由於現如今絕大多少的SoC都很牛逼,似乎DMA都沒有什麼缺陷了,根本就不太可能給我們機會指定DMA ZONE大小裝逼了,那個這個ZONE就不太需要存在了。反正任何DMA在任何地方申請的記憶體,這個DMA都可以存取到。

2.DMA ZONE的記憶體只能做DMA嗎?

DMA ZONE的記憶體做什麼都可以。DMA ZONE的作用是讓有缺陷的DMA對應的外設驅動申請DMA buffer的時候從這個區域申請而已,但是它不是專有的。其他所有人的記憶體(包括應用程式和核心)也可以來自這個區域。

3.dma_alloc_coherent()申請的記憶體來自DMA ZONE?

dma_alloc_coherent()申請的記憶體來自於哪裡,不是因為它的名字前面帶了個dma_就來自DMA ZONE的,本質上取決於對應的DMA硬體是誰。看程式碼:

  1. static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
  2. gfp_t gfp, pgprot_t prot, bool is_coherent, const void *caller)
  3. {
  4. u64 mask = get_coherent_dma_mask(dev);
  5. if (mask < 0xffffffffULL)
  6. gfp |= GFP_DMA;
  7. }

對於primaII而言,絕大多少的外設的dma_coherent_mask都設定為0XffffffffULL(4GB記憶體全覆蓋),但是SD那個則設定為256MB-1對應的數字。這樣當primaII的SD驅動呼叫dma_alloc_coherent()的時候,GFP_DMA標記被設定,以指揮核心從DMA ZONE申請記憶體。但是,其他的外設,mask覆蓋了整個4GB,呼叫dma_alloc_coherent()獲得的記憶體就不需要一定是來自DMA ZONE。

4.dma_alloc_coherent()申請的記憶體是非cache的嗎?

要解答這個問題,首先要理解什麼叫cache coherent。還是繼續看這個DMA的圖,我們假設MEM裡面有一塊紅色的區域,並且CPU讀過它,於是紅色區域也進CACHE:



但是,假設現在DMA把外設的一個白色搬移到了記憶體原本紅色的位置:

這個時候,記憶體雖然白了,CPU讀到的卻還是紅色,因為CACHE命中了,這就出現了cache的不coherent。
當然,如果是CPU寫資料到記憶體,它也只是先寫進cache(不一定進了記憶體),這個時候如果做一個記憶體到外設的DMA操作,外設可能就得到錯誤的記憶體裡面的老資料。
所以cache coherent的最簡單方法,自然是讓CPU訪問DMA buffer的時候也不帶cache。事實上,預設情況下,dma_alloc_coherent()申請的記憶體預設是進行uncache配置的。
但是,由於現代SoC特別強,這樣有一些SoC裡面可以用硬體做CPU和外設的cache coherence,如圖中的cache coherent interconnect:

這些SoC的廠商就可以把核心的通用實現overwrite掉,變成dma_alloc_coherent()申請的記憶體也是可以帶cache的。這部分還是讓大牛Arnd Bergmann童鞋來解釋:

來自:https://www.spinics.net/lists/arm-kernel/msg322447.html

Arnd Bergmann:

dma_alloc_coherent() is a wrapper around a device-specific allocator,

based on the dma_map_ops implementation. The default allocator

from arm_dma_ops gives you uncached, buffered memory. It is expected

that the driver uses a barrier (which is implied by readl/writel

but not __raw_readl/__raw_writel or readl_relaxed/writel_relaxed)

to ensure the write buffers are flushed.

If the machine sets arm_coherent_dma_ops rather than arm_dma_ops,

the memory will be cacheable, as it's assumed that the hardware

is set up for cache-coherent DMAs.

當我grep核心原始碼的時候,我發現部分SoC確實是這樣實現的:

baohua@baohua-VirtualBox:~/develop/linux/arch/arm$ git grep arm_coherent_dma_ops

include/asm/dma-mapping.h:extern struct dma_map_ops arm_coherent_dma_ops;

mach-highbank/highbank.c:set_dma_ops(dev, &arm_coherent_dma_ops);

mach-mvebu/coherency.c:set_dma_ops(dev, &arm_coherent_dma_ops);

5.dma_alloc_coherent()申請的記憶體一定是物理連續的嗎?

絕大多數的SoC目前都支援和使用CMA技術,並且多數情況下,DMA coherent APIs以CMA區域為申請的後端,這個時候,dma alloc coherent本質上用__alloc_from_contiguous()從CMA區域獲取記憶體,申請出來的記憶體顯然是物理連續的。這一點,在裝置樹dts裡面就可以輕鬆配置,要麼配置一個自己特定的cma區域,要麼從“linux,cma-default”指定的預設的CMA池子裡面取記憶體:

  reserved-memory {
                #address-cells = <1>;
                #size-cells = <1>;
                ranges;

                /* global autoconfigured region for contiguous allocations */
                linux,cma {
                        compatible = "shared-dma-pool";
                        reusable;
                        size = <0x4000000>;
                        alignment = <0x2000>;
                        linux,cma-default;
                };

                display_reserved: framebuffer@78000000 {
                        reg = <0x78000000 0x800000>;
                };

                multimedia_reserved: multimedia@77000000 {
                        compatible = "acme,multimedia-memory";
                        reg = <0x77000000 0x4000000>;
                };
        };

但是,如果IOMMU存在(ARM裡面叫SMMU)的話,DMA完全可以訪問非連續的記憶體,並且把物理上不連續的記憶體,用IOMMU進行重新對映為I/O virtual address (IOVA):

所以dma_alloc_coherent()這個API只是一個前端的介面,它的記憶體究竟從哪裡來,究竟要不要連續,帶不帶cache,都完全是因人而異的。

最後總結一句,千萬不要被教科書和各種網上的資料懵逼了雙眼,你一定要真正自己探索和搞清楚事情的本源。

今天看了《芳華》這部電影,感慨良多,遂作此文。

更多精華文章請掃描下方二維碼關注Linux閱碼場