指定行銷區域

直接記憶體存取 (DMA) 這項功能可讓硬體在不需要 CPU 介入的情況下存取記憶體。最高等級,系統會提供要移轉的記憶體區域來源和目的地 (及其大小),並告知要複製資料。有些硬體週邊裝置甚至支援執行多項「散佈 / 收集」樣式作業,因此可以依序執行多項複製作業,不必進行額外的 CPU 介入措施。

指定行銷區域注意事項

為了確保能徹底感謝問題,請務必留意下列事項:

  • 每項程序都會在虛擬位址空間中運作
  • MMU 可以將連續的虛擬位址範圍對應到多個不連續的實體位址範圍 (反之亦然),
  • 每項程序都有一個有限的實際位址
  • 部分週邊裝置會透過輸入 / 輸出記憶體管理單元 (IOMMU) 支援他們擁有的虛擬位址。

接著就來逐一討論每個環節。

虛擬、實體和裝置實際位址

程序可存取的位址為虛擬,也就是由 CPU 記憶體管理單元 (MMU) 建立的插圖。MMU 會將虛擬地址對應至實際地址。對應精細程度是以名為「頁面大小」的參數為基礎,該參數至少為 4,000 個位元組,但新型處理器可提供較大的尺寸。

圖:虛擬與實際地址之間的關係

在上圖中,我們顯示了具有多個虛擬位址的特定程序 (程序 12)。MMU 負責將藍色虛擬位址對應至 CPU 實體公車位址 (紅色)。每個程序都有各自的對應,因此即使程序 12 具有虛擬位址 300,其他程序可能也有虛擬位址 300。該程序的虛擬位址 300 (如有) 會對應至程序 12 中的實際地址。

請注意,我們使用小小數數字做為「地址」,以求簡潔扼要。 實際上,上方顯示的每個正方形都代表一個記憶體 (4K 以上) 的頁面,並且根據 32 或 64 位元值 (視平台而定) 識別。

圖中的重點如下:

  1. 虛擬位址可以分配給多個群組 (其中三個是 300-303420-421,以及 770-771);
  2. 虛擬運算 (例如300-303) 不一定是連續的。
  3. 部分虛擬地址無法對應 (例如沒有虛擬地址 304)
  4. 部分程序可能無法取得所有實際地址 (例如,程序 12 無法存取實際地址 120)。

視平台的可用硬體而定,裝置的位址空間不一定會採用類似的翻譯。如果沒有 IOMMU,週邊裝置使用的位址就會與 CPU 使用的實體位址相同:

圖:未使用 IOMMU 的裝置

在上圖中,裝置的位址空間 (例如影格緩衝區或控制暫存器) 會直接顯示在 CPU 的實體位址範圍內。也就是說,裝置會透過 122125 (含首尾) 佔用實際地址。

為了存取裝置的記憶體,系統需要建立從某些虛擬位址與 122125 的實際地址的 MMU 對應。詳細步驟請見下方說明。

然而,使用 IOMMU 時,週邊裝置看到的位址可能與 CPU 的實體位址不同:

圖:使用 IOMMU 的裝置

在本例中,裝置有自身已知的「裝置實體」地址,也就是 03 的位址 (含首尾)。IOMMU 可分別將裝置實體位址 03 對應至 CPU 實體位址 109110101119

在這種情況下,為使用裝置記憶體的程序,必須安排兩項對應:

  • 一組標記,也就是從虛擬位址空間設定的300303) 至 CPU 實體位址空間 (分別為 109110101119),透過 MMU 和
  • 一組透過 IOMMU 範圍,從 CPU 實體位址空間 (位址 109110101119) 到裝置實體位址 (03)。

雖然這看似複雜,但 Zircon 提供了抽象層,可以消除複雜度。

此外,如下圖所示,取得 IOMMU 數量的原因和提供的好處,與使用 MMU 取得的好處類似。

記憶體連續性

當您分配大量記憶體 (例如使用 calloc()) 時,程序當然會遇到大型連續的虛擬位址範圍。MMU 可能會在虛擬定址層級建立連續記憶體的錯覺,即使 MMU 可能會選擇回到該記憶體區域,並在實體位址層級提供實體不連續的記憶體,也會造成這種情況。

此外,當程序分配及釋放記憶體時,實體記憶體與虛擬位址空間的對應作業往往更加複雜,從而鼓勵出現更多「Swiss 起司」孔 (也就是對應中出現更多不連續情形)。

因此,請務必留意,連續虛擬地址不一定是連續的實體位址,而且會隨著時間的推移而變得更加寶貴。

存取權控管

MMU 的另一個好處是,程序受限於實體記憶體可視 (基於安全性和可靠性)。不過,這會影響驅動程式,而程序必須明確要求從虛擬位址空間對應至實際位址空間,並擁有執行此作業的必要權限。

伊曼

一般建議使用連續的實體記憶體。執行一次傳輸 (使用一個來源位址和一個目的地地址) 會比設定及管理多個個別傳輸作業更有效率 (每次傳輸作業之間可能需要 CPU 介入才能設定下一個傳輸作業)。

IOMMU (如果有的話) 可對週邊裝置執行相同操作 (即 CPU 的 MMU 進行程序) 來緩解此問題,透過將多個不連續的區塊對應至虛擬連續空間,對週邊裝置就知道它處理了連續位址空間的錯覺。限制對應區域後,IOMMU 也會透過與 MMU 相同的方式,防止週邊裝置存取不在目前作業「範圍內」的記憶體,藉此提供安全性。

融會貫通並靈活運用

因此,當您寫驅動程式庫時,可能會需要擔心虛擬、實體和裝置實體的地址空間。但事實並非如此。

指定行銷區域和你的驅動程式庫

Zircon 提供一組函式,可讓您明確處理上述所有項目。以下運作方式如下:

  • 公車交易發起者 (BTI),以及
  • 虛擬記憶體物件 (VMO)。

BTI 核心物件提供模型的抽象化機制,以及用於處理與 VMO 相關聯的實體 (或裝置實體) 位址的 API。

在驅動程式庫的初始化作業中,呼叫 Pci::GetBti() 以取得 BTI 控制代碼:

#include <lib/device-protocol/pci.h>

zx_status_t Pci::GetBti(uint32_t index,
                        zx::bti* out_bti);

GetBti() 函式位於 ddk::Pci 類別 (與上述所有其他 PCI 函式相同) 中,且接受 index 參數 (保留供日後使用,請使用 0)。它會透過 out_bti 指標引數傳回 BTI 物件。

接下來,您需要 VMO。簡單來說,您可以將 VMO 視為指向記憶體區塊的指標,但這不只是,這是代表一組虛擬頁面的核心物件,代表一組虛擬頁面 (不一定有實體頁面),且可對應到驅動程式庫程式程序的虛擬位址空間。(還不只如此,但我們會針對其他章節進行討論)。

最終,這些網頁是 DMA 轉移的來源或目的地。

zx_vmo_create()zx_vmo_create_continueiguous() 這兩個函式會分配記憶體,並將其繫結至 VMO

zx_status_t zx_vmo_create(uint64_t size,
                          uint32_t options,
                          zx_handle_t* out);

zx_status_t zx_vmo_create_contiguous(zx_handle_t bti,
                                     size_t size,
                                     uint32_t alignment_log2,
                                     zx_handle_t* out);

如您所見,兩者都會使用指出所需位元組數的 size 參數,而且兩者都會傳回 VMO (透過 out)。兩者都會針對特定大小分配虛擬連續頁面。

請注意,這與標準 C 程式庫記憶體配置函式 (例如 malloc()) 不同,後者會分配虛擬連續記憶體,但不會受到頁面邊界的限制。舉例來說,資料列中的兩個小型 malloc() 呼叫可能會從「同一個」頁面分配兩個記憶體區域,而 VMO 建立函式一律會分配記憶體,以從頁面開始。

zx_vmo_create_continueiguous() 函式會執行 zx_vmo_create(),「並」確保頁面適合整理,以搭配指定的 BTI 使用 (這也是為什麼需要 BTI 控制代碼)。此元素還包含 alignment_log2 參數,可用來指定最小對齊需求。顧名思義,此值必須是 2 的整數功率 (其中 0 值表示頁面對齊)。

此時,分配的記憶體有兩種「檢視畫面」:

  • 一個連續的虛擬位址空間 代表從驅動程式庫的視角代表記憶體
  • 一組 (可能是連續、可能承諾) 的實體網頁,以供週邊裝置使用。

使用這些網頁之前,您必須確保這些網頁出現在記憶體中 (亦即「已修訂」:您的程序可存取實體頁面),且週邊裝置可透過 IOMMU (如有) 存取這些頁面。此外,您也需要網頁位址 (從裝置的角度),以便在裝置上編寫 DMA 控制器來存取這些網頁。

zx_bti_pin() 函式可用來執行以下所有操作:

#include <zircon/syscalls.h>

zx_status_t zx_bti_pin(zx_handle_t bti, uint32_t options,
                       zx_handle_t vmo, uint64_t offset, uint64_t size,
                       zx_paddr_t* addrs, size_t addrs_count,
                       zx_handle_t* pmt);

這個函式有 8 個參數:

參數 目的
bti 這個週邊裝置的 BTI
options 選項 (請見下方)
vmo 此記憶體區域的 VMO
offset VMO 開頭的偏移量
size VMO 中的位元組總數
addrs 退貨地址清單
addrs_count addrs 中的元素數量
pmt 傳回 PMT (請見下方說明)

addrs 參數是指向您提供的 zx_paddr_t 陣列的指標。系統會在這個位置傳回各網頁的周邊裝置地址。陣列是 addrs_count 元素,且必須符合 zx_bti_pin() 預期的元素數量。

寫入 addrs 的值適合用來編寫週邊裝置的 DMA 控制器,也就是說,這些值會將 IOMMU 執行的任何轉譯 (如果有的話) 納入考量。

就技術層面而言,zx_bti_pin() 的另一項影響是,核心會確保在固定時不會停用這些網頁 (也就是移動或重複使用)。

options 引數實際上是選項的點陣圖:

選項 目的
ZX_BTI_PERM_READ 週邊裝置 (由驅動程式庫寫入) 皆可讀取
ZX_BTI_PERM_WRITE 網頁都可以由周邊裝置寫入 (由驅動程式庫讀取)
ZX_BTI_COMPRESS (請見下方的「最小連續屬性」)

例如,請參閱上方顯示「裝置 3」的圖表。如果有 IOMMU,則 addrs 會包含 0123 (即裝置實體地址)。如果沒有 IOMMU,則 addrs 會包含 109110101119 (亦即實際地址)。

權限

請記住,權限是從週邊裝置的角度來看,而非驅動程式庫。舉例來說,在區塊裝置的 write 作業中,裝置會從記憶體「讀取」,因此驅動程式庫會指定 ZX_BTI_PERM_READ,反之亦然。

最小連續屬性

根據預設,透過 addrs 傳回的每個地址都是一頁的內容。 如要要求更大的區塊,您可以在 options 引數中設定 ZX_BTI_COMPRESS 選項。在這種情況下,每個傳回的項目的長度都會對應至「最小連續性」屬性。雖然您無法設定這個屬性,但可以使用 zx_object_get_info() 加以讀取。實際上,最低連續屬性可確保 zx_bti_pin() 一律可傳回至少這麼多位元組的連續位址。

舉例來說,如果屬性的值值為 1 MB,則呼叫 zx_bti_pin() 時,要求的大小為 2MB,最多會傳回兩個連續執行作業。如果要求的大小為 2.5 MB,則最多會傳回三個實體連續執行作業,以此類推。

釘選的記憶體權杖 (PMT)

zx_bti_pin() 會在 pmt 引數成功時,傳回固定的記憶體權杖 (PMT)。裝置完成記憶體交易後,驅動程式必須呼叫 zx_pmt_unpin(),才能取消固定並撤銷裝置對記憶體頁面的存取權。

進階主題

快取一致性

在完全 DMA 一致性架構中,硬體可確保 CPU 快取中的資料與主要記憶體中的資料相同,且無需任何軟體介入。並非所有架構都具有 DMA 一致。在這些系統中執行 DMA 作業之前,驅動程式庫必須針對記憶體範圍叫用適當的快取作業,確保 CPU 已一致,這樣就不會存取過時的資料。

如要在 VMO 所代表的記憶體上叫用快取作業,請使用 zx_vmo_op_range() syscall。在周邊裝置讀取 (驅動程式庫寫入) 作業之前,請使用 ZX_VMO_OP_CACHE_CLEAN 清理快取,以便將骯髒資料寫入主要記憶體。在周邊裝置寫入 (驅動程式庫-read) 之前,請使用 ZX_VMO_OP_CACHE_CLEAN_INVALIDATE 清理快取行並將其標示為無效,確保系統會在下次存取時從主記憶體擷取資料。