Linux記憶體管理:DMA

發表於2015-09-25

說起DMA我們並不陌生,但是實際程式設計中去用的人不多吧,最多就是網路卡驅動裡的環形buffer,再有就是裝置的dma,下面我們就分析分析.
DMA用來在裝置記憶體和記憶體之間直接資料互動。而無需cpu干預

核心為了方便驅動的開發,已經提供了幾個dma 函式介面。

dma跟硬體架構相關,所以linux關於硬體部分已經給遮蔽了,有興趣的可以深入跟蹤學習.

按照linux核心對dma層的架構設計,各平臺dma緩衝區對映之間的差異由核心定義的一個dma操作集

include/linux/dma-mapping.h:

來統一遮蔽實現的差異.
不同差異主要來來自cache的問題
Cache與dma同步問題,這裡不深入討論.

另外一個常用的函式是Dma_set_mask, 為了通知核心裝置能夠定址的範圍,很多時候裝置能夠定址的範圍有限。

Dma對映可以分為三類:

1. 一致性dma對映 dma_alloc_coherent (問題:驅動使用的buffer不是自身申請的,而是其他模組)
當驅動模組主動分配一個Dma緩衝區並且dma生存期和模組一樣時

引數說明:

(1)這個函式的返回值是緩衝的一個核心虛擬地址, 它可被驅動使用
(2)第三個引數dma_handle:
其間相關的實體地址在 dma_handle 中返回

2. 流式dma對映 dma_map_single
通常用於把核心一段buffer對映,返回實體地址.
如果驅動模組需要使用從別的模組傳進來的虛擬地址空間作為dma緩衝區,保證地址的線性 cache一致性
一致性api介面:sync_single_for_cpu

3.分散/聚集對映(scatter/gather map) Dma_map_sgs

有時候我們還需要
1. 回彈緩衝區 bounce buffer:當cpu側實體地址不適合裝置的dma操作的時候

2.
DmA記憶體池:一般dma對映都是單個page的整數倍,如果驅動程式需要更小的一致性對映的dma緩衝區,可以使用。類似於slab機制,
Dma_pool_create

下面我們就那網路卡驅動的例子說說dma的具體應用,參考linux kernel e1000網路卡
drivers/net/ethernet/intel/e1000/*
Ring buffer

Dma不能為高階記憶體,一般為32,預設低端記憶體,由於裝置能夠訪問的地址範圍有限。
裝置使用實體地址,而程式碼使用虛擬地址。

就看看如何傳送資料包:e1000_main.c:

e1000_xmit_frame: 關於幀的傳送流程這裡不多說.

經過上次,鄰居子系統後,資料幀已經到達驅動,資料放在skb指定的記憶體裡.
看程式碼
tx_ring = adapter->tx_ring; // 獲取傳送的ring buffer

接著我們看關鍵程式碼:
count = e1000_tx_map(adapter, tx_ring, skb, first, max_per_txd, nr_frags, mss);

它做了什麼呢?

預設資料包文沒有分片或者碎片什麼的。
那麼進入第一個while(len)

獲取buffer_info = &tx_ring->buffer_info[i];
然後:呼叫dma_map_single進行流式對映. 即把skb->data(虛擬地址) 和buffer_info->dma(實體地址)對應起來.操作兩個地址等於操作同一片區域。

回到主傳送函式:

呼叫e1000_tx_queue把資料傳送出去:

我們看到它把剛才dma_map_singe裡的對映賦值了:
tx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
說明傳送的時候是根據傳送描述符來傳送的。

然後操作暫存器:
writel(i, hw->hw_addr + tx_ring->tdt);
那麼網路卡就會自動讀取tx desc 然後把資料傳送出去。

總結下流程:
1. linux os會呼叫網路卡的start_xmit()函式。在e1000裡,對應的函式是 e1000_xmit_frame,
2. e1000_xmit_frame又會呼叫e1000_tx_queue(adapter, tx_ring, tx_flags, count)。
這裡的tx_queue指的是傳送Descriptor的queue。
3. e1000_tx_queue 在檢查了一些引數後,最終呼叫 writel(i, hw->hw_addr + tx_ring->tdt)。
這裡的tx_ring->tdt中的tdt全寫為 tx_descriptor_tail。從網路卡的開發手冊中可以查到,如果寫了descriptor tail,那麼網路卡就會自動讀取 descriptor,然後把包傳送出去。

descroptor的主要內容是addr pointer和length。前者是要傳送的包的起始實體地址。後者是包的長度。有了這些,硬體就可以通過dma來讀取包併發出去了。其他網路卡也基本會用descriptor的結構。

雖然流程明白了,但是還有幾個點,
1. tx_ring在哪初始化?
2. 網路卡到底是如何操作對映的dma地址的,把資料傳送出去的?

tx ring 在e1000_open 的時候:
呼叫:

我們看:它建立了一致性dma對映.

desc是結構指標:它的結構跟網路卡暫存器結構有關,e1000_hw.h

我們稍微屢一下,

那麼網路卡又是如何和dma地址關聯的呢?

很明顯它把dma地址寫入了網路卡dma暫存器。所以dma還需要網路卡硬體的支援才行.

當然e1000這個網路卡驅動還是相當的複雜,不過它把一致性對映和流式對映都用上了。

相關文章