直接記憶體存取(direct memory access)
/ DMA, 'dee-em-ay' /
把 CPU 想成一位經理,他可以親手一次一個地搬一千個箱子,但會卡住所有其他工作。若 CPU 自己把每個位元組搬進搬出裝置,發生的就是這種事。直接記憶體存取讓裝置替 CPU 搬箱子:裝置自己的引擎直接讀寫主記憶體,所以 CPU 只要說「把這份資料搬到這裡」就能去做別的事,等傳輸完成時收到一個中斷。
具體而言,驅動程式配置一塊記憶體、把實體位址與長度告訴裝置,然後啟動傳輸;裝置(作為匯流排主控者,bus master)便經由匯流排直接讀寫那塊記憶體,CPU 不必碰每個位元組。映射有兩種風味。DMA 一致性(coherent / consistent)映射是 CPU 與裝置隨時都看到一致內容的記憶體,不需手動管理快取——適合像描述符環那樣小而長壽的控制結構。串流(streaming)映射則是為單一方向的單次傳輸而設(前用 dma_map_single、後用 dma_unmap_single),往往較快,但要求你把所有權交給裝置,並在解除映射前不去碰那個緩衝區。一個關鍵微妙處:裝置使用的是匯流排/實體位址,而非 CPU 的虛擬位址,所以你必須映射一個緩衝區以取得一個 dma_addr_t 交給硬體。
DMA 之所以重要,是因為它是唯一能以全速搬移大量資料(磁碟區塊、網路封包、視訊影格)而不綁住 CPU 的方式。誠實的提醒:DMA 繞過 CPU 快取,所以在某些系統上你必須清空(flush)或使無效(invalidate)快取(或用一致性映射),否則裝置與 CPU 會看到過時資料;而由於裝置寫的是真實記憶體位址,一個被給了錯誤位址的有臭蟲或惡意裝置可以破壞任何東西——這正是 IOMMU 存在以管制 DMA 的原因。
// 為一次 DMA 讀入 'buf' 做串流映射: dma_addr_t da = dma_map_single(dev, buf, len, DMA_FROM_DEVICE); start_device_dma(dev, da, len); // 給的是匯流排位址 // ... 完成時中斷觸發 ... dma_unmap_single(dev, da, len, DMA_FROM_DEVICE); // 直到此刻 CPU 才安全地讀取 buf
一次串流映射:把匯流排位址交給裝置、讓它搬資料,並在解除映射前不去碰緩衝區。
裝置使用匯流排/實體位址,而非虛擬位址,所以緩衝區必須先做 DMA 映射。DMA 可能繞過 CPU 快取並寫入真實記憶體,所以一致性與位址驗證(IOMMU)不是可有可無的細節。