輸入輸出記憶體管理單元(IOMMU)
/ I-O-M-M-U, 'eye-oh-em-em-you' /
一般的記憶體管理單元(MMU)位於 CPU 與記憶體之間,把 CPU 使用的虛擬位址轉譯成真正的實體位址,並阻擋對行程不該碰的記憶體的存取。但做 DMA 的裝置會直接寫記憶體,繞過 CPU 的 MMU——那是什麼在保護記憶體不被行為不當的裝置破壞?IOMMU 就是給裝置用的 MMU:一個位於裝置與記憶體之間的硬體單元,轉譯裝置使用的位址,並拒絕超出其被允許範圍的存取。
具體而言,當裝置位於 IOMMU 之後時,你給它的位址(一個 dma_addr_t,有時稱為 I/O 虛擬位址)不是原始的實體位址——它會經由 IOMMU 的分頁表轉譯,就像 CPU 的虛擬位址經由分頁表轉譯一樣。核心對 IOMMU 編程,使某個裝置只能抵達驅動程式為它映射的特定緩衝區;任何對未映射位址的 DMA 都會被阻擋並引發錯誤。這做了兩件大事:把每個裝置侷限在它自己的記憶體裡(讓有臭蟲或惡意的裝置無法塗寫核心或另一裝置的資料),並讓一個只能定址例如 32 位元記憶體的裝置,透過重新映射抵達放在實體記憶體高處的緩衝區。
IOMMU 對安全與虛擬化最為重要:它正是讓安全的裝置直通(passthrough)到虛擬機成為可能的關鍵,因為客體可以驅動真實硬體,而該硬體無法 DMA 進主機或其他客體。誠實的提醒:位址轉譯增加額外負擔,還多了一個 IOMMU TLB 要管理,所以有真實的效能代價;而保護的好壞只取決於映射本身——若驅動程式把緩衝區映射得太寬鬆,或留下過時的映射,IOMMU 會忠實地正好允許那樣。它守的是你劃下的邊界,而非它自己發明的邊界。
沒有 IOMMU: 驅動程式給裝置一個原始實體位址 位址錯了 -> 裝置可塗寫任何地方 有 IOMMU: 驅動程式映射 buf -> 取得一個 I/O 虛擬位址 裝置用任何其他位址 -> IOMMU 引發錯誤 // 讓把網卡安全 VFIO 直通給客體 VM 成為可能
IOMMU 轉譯並侷限裝置的 DMA,使裝置只能碰被明確為它映射的緩衝區。
IOMMU 只強制執行核心所編程的映射;過寬或過時的映射會被忠實地允許。它增加轉譯負擔,而其保護的緊密程度正好等於驅動程式把它設得多緊。