1.DMA通道
DMA(Direct Memory Access)通道建立在設備和RAM之間,DMAC(DMA Controler)與設備I/O控制器相互作用共同實現(xiàn)數(shù)據(jù)傳送。
在PC中,DMA控制器位于主板上負責管理I/O總線的南橋上。典型的PC架構的數(shù)據(jù)通道示意圖如下:

DMAC一旦被CPU激活,就可以自行傳送數(shù)據(jù)。在實現(xiàn)DMA傳輸時,由DMA控制器直接掌管總線,因此,存在著一個總線控制權轉移問題。在DMA傳輸前,CPU要把總線控制權交給DMA控制器。在DMA傳輸后,DMAC發(fā)出一個中斷請求,將總線控制權再交回給CPU。DMA控制器獲得總線控制權后,CPU即刻掛起或只執(zhí)行內部操作,由DMA控制器輸出讀寫命令,直接控制RAM與I/O接口進行DMA傳輸。
需要明確的是,I/O設備內部一般自帶緩存,即通常所說的設備內存。從數(shù)據(jù)傳輸?shù)?strong>源宿角度分析,DMA通道的兩端分別是RAM和設備內存。設備內存一般選用快速低功耗的SRAM材質,例如AR9331交換芯片PCU單元中有4KB的Tx
FIFO和2KB的Rx FIFO,“The GE0 and GE1 support 2K transmit FIFO and 2K receive FIFO.”
使用DMAC最多的是磁盤驅動器和其他需要一次傳送大量字節(jié)的慢速設備,例如PCI網(wǎng)卡(NIC)。
2.Linux中的DMA層
DMA操作的核心是DMA內存映射,包括一致性DMA映射、流式DMA映射和分散/聚集映射。以下是Linux內核DMA層的大體框架圖:

從圖中可以看出,Linux內核中的DMA層為設備驅動程序提供標準的DMA映射接口,例如一致性映射類型的dma_alloc_coherent和流式映射類型的dma_map_single。這些接口屏蔽了不同平臺之間的差異,為設備驅動程序提供了良好的可移植性。
3.DMA描述符
SoC datasheet通常會提供DMA rx/tx的descriptor address和trigger control寄存器。在嵌入式軟件開發(fā)中,DMA描述符數(shù)組是個很重要的概念。
DMA描述符數(shù)組(DMA Descriptor Array/Ring/Chain)是一個形如unsigned long* hw_desc[DESC_NUM]的指針數(shù)組,每個指針(hw_desc[i])指向一個描述符。這個描述符是由硬件定義的,其數(shù)據(jù)結構一般由datasheet或sdk定義。
3.1 硬件描述符(h/w descriptor)
硬件描述符通常包含以下五個部分:
<1>控制位(empty flag/own bit):descriptor empty/owned by DMA or not,該位域描述的是descriptor對CPU/DMA的有效性。empty_flag和own_bit是站在不同角度對控制狀態(tài)的同功描述,empty表示descriptor上尚無數(shù)據(jù)(包)。對于RX,descriptor
empty(owned by DMA)表示亟待DMA搬運數(shù)據(jù)進來掛到該descriptor的DMA緩存上;對于TX,descriptor empty(not owned by DMA)表示亟待CPU往descriptor上掛載待發(fā)送的數(shù)據(jù)包。下文主要基于DMAown_bit分析控制位,讀者可自行做等價轉換。
<2>數(shù)據(jù)包地址(data buffer address):該指針指向DMA傳輸時的源端或目的端內存區(qū),有的地方稱之為DMA緩存,也即上文提到的設備內存。DMA緩存是數(shù)據(jù)包的終極歸宿,即cluster的數(shù)據(jù)區(qū)(mBlk::mBlkHdr.mData inVxWorks, sk_buff.data in Linux)。
<3>數(shù)據(jù)包長度(packet length):rx/tx數(shù)據(jù)包的有效長度。
<4>環(huán)尾標(wrap bit):wrap bit of the last descriptor,該位域標記最后一個描述符,用于判斷溢出(rx overflow)。站在“環(huán)”的角度分析,也可以理解為按照數(shù)組索引的回環(huán)點。
<5>環(huán)鏈結(next pointer):該指針指向下一個描述符。盡管分配的DMA描述符數(shù)組已經(jīng)是線性存儲,但是硬件總是習慣按照地址來查找下一個描述符。軟件則更習慣在RX ISR中使用數(shù)組索引來遍歷描述符環(huán)上待收割(reap)的數(shù)據(jù)包。
有的地方稱之為BD(Buffer Descriptor),由于是hardware
specific,故開發(fā)者一般無需修改h/w descriptor數(shù)據(jù)結構。
DMA描述符數(shù)組hw_desc[]的地址是DMA映射的虛擬地址,它是描述符環(huán)的基地址,需要配置到SoC芯片的相關寄存器中,例如AR9331Datasheet中的DMARXDESCR(Pointer
to Rx Descrpitor)和DMATXDESCR_Q0(Descriptor Address for Queue 0 Tx)。很顯然,這個數(shù)組需要分配或轉換到非緩存的區(qū)段(例如MIPS中的kseg1段)。但每個描述符所指向的DMA緩存(desc buffer)通常是分配在緩存的區(qū)段(例如MIPS中的kseg0段),使用cache主要是出于訪存性能考慮。

3.2 軟件描述符(s/w descriptor)
硬件描述符(h/w descriptor)更多的關注分組(例如以太網(wǎng)幀)的傳輸,而缺乏對數(shù)據(jù)包或數(shù)據(jù)鏈的軟件組織層次關懷。數(shù)據(jù)包在網(wǎng)絡協(xié)議棧各層之間流動時,軟件層面需要維系完整的數(shù)據(jù)鏈信息,包括橫向的包內分片(fragment)和縱向的多包鏈化(chain),以便進行鏈接跟蹤(conntrack)。以網(wǎng)卡為例,NIC的ISR中首先會創(chuàng)建一個buf(mBlk/mbuf
in VxWorksor or sk_buff in Linux),分組的內容將被封裝到這個buf結構體中,進而調用相應函數(shù)(END_RCV_RTN_CALL(END_OBJ*,M_BLK_ID)in VxWorks or netif_rx(sk_buff*)in
Linux)將數(shù)據(jù)包推送到網(wǎng)絡子系統(tǒng)(TCP/IP協(xié)議棧)中的高層代碼中。
數(shù)據(jù)包以mBlk或sk_buff的形式在協(xié)議棧之間流動,因此在軟件層面,往往根據(jù)信息組織的完整性需要對h/w descriptor進行適當?shù)?strong>擴展。通常,s/w descriptor是對h/w descriptor的container。除了數(shù)據(jù)包的組織擴展以外,也可以根據(jù)需要增加一些描述信息和維護信息,例如可添加用于跟蹤tx timeout的timestamp。
4.DMA流程
4.1 DMA掛環(huán)及啟動
我們知道讀寫操作存在差異性,rx是不定期發(fā)生的,故rx descriptor ring上需要預掛cluster隨時待命;tx往往是由我們主動發(fā)起的,驅動的send調用中將數(shù)據(jù)包cluster掛到tx descriptor ring上。
設備驅動程序在初始時,需要將RX環(huán)上的每個descriptor的控制位手工置1(owned by DMA,empty and not available for CPU to handle),意即當前對DMA有效(available
for DMA),CPU虛位以待DMA從設備傳送數(shù)據(jù)進來。初始化TX環(huán)上的每個descriptor的控制位手工置0(not owned by DMA,empty
and available for CPU to fill),意即當前對CPU有效(available for CPU),DMA虛位以待CPU將待發(fā)數(shù)據(jù)包掛載到TX環(huán)上。
一般來說,網(wǎng)絡設備(MAC)驅動層配置就緒之后,就可以通過配置DMA RX trigger control寄存器啟用DMA RX通道。對于TX流程,往往在數(shù)據(jù)包準備好且掛接到TX環(huán)上后配置DMA TX trigger control寄存器啟動DMA TX。
4.2 DMA環(huán)的大小
rx環(huán)上的每個描述符(descriptor)指向一個緩存實體(buffer),因此環(huán)的大小在一定程度上決定了收包的能力。rx環(huán)太小會造成頻繁的rx overflow,勢必會影響吞吐量性能。tx環(huán)的size將決定我們能往其上掛多少個待發(fā)數(shù)據(jù)包(packet buffer to be sent),tx環(huán)太小也會影響吞吐量性能。但是rx/tx環(huán)并不是越大越好,需要結合內存余量來配置,而且要和CPU的處理能力適配,綜合權衡才能獲得最佳的性能(performance
balance)。Windows PC的網(wǎng)絡適配器高級選項里面的接收緩沖區(qū)和發(fā)送緩沖區(qū)(傳輸緩沖區(qū))的數(shù)量一般就是指RX/TX環(huán)的大小。
4.3 DMA過程控制
(1)在向內傳輸(rx)時,設備收到數(shù)據(jù)包后(或設備RX FIFO滿時)將通知DMAC,DMA開始將數(shù)據(jù)從設備內存?zhèn)魉偷筋A先掛在RX環(huán)上的DMA緩沖區(qū)上。此時,一方面,DMAC將對應描述符的控制位自動置0(not owned by DMA,not empty and
available for CPU to handle),意即等待CPU進行消化處理;另一方面,DMAC將向CPU發(fā)起中斷請求。在RX ISR中,對RX環(huán)進行掃描,其中控制位為0的描述符上的DMA緩沖區(qū)對應的數(shù)據(jù)包將被封裝成mBlk/sk_buff,并將由END_OBJ/net_device ISR推送到TCP/IP協(xié)議棧隊列,等待網(wǎng)絡子系統(tǒng)的一系列處理。

通常,在RX ISR中需要從netPool/skbPool中重新分配一個新的mBlk/sk_buff,并將其補掛(參考下文的流式映射)到剛才buffer被采摘走的descriptor上(需要將控制位手工置1),如此則可以保證RX描述符環(huán)總是虛位以待。被采摘走的mBlk/sk_buff的生命周期將由網(wǎng)絡子系統(tǒng)控制,釋放或用來構造tx包。
當協(xié)議棧處理包流程(中斷處理下半部)過長時,將會導致netPool/skbPool吃緊;另一方面,中斷響應(中斷上半部)不及時將會引發(fā)rx overflow。在某些SoC芯片中,rx overflow可能會將DMA暫停一段時間,以避免頻繁中斷。以下表述摘自AR9331
Datasheet:
This bit(DMARXSTATUS::RXOVERFLOW) is set when the DMA controller reads a set empty flag(not available for DMA) in the descriptor it is processing.The DMA controller clears this bit(DMARXCTRL::RXENABLE) when
it encounters an RX overflow or bus error state.
(2)本地協(xié)議棧對rx進來的數(shù)據(jù)包進行反饋或轉發(fā)時,需要將數(shù)據(jù)包掛載到tx環(huán)上空閑的描述符上,空閑的標準是控制位為0(not owned by DMA)。此時,我們需要將控制位手工置1(owned by DMA,not empty and available forDMA to xmit),表示TX環(huán)上的這個描述符上掛載著待發(fā)的數(shù)據(jù)包。緊接著,啟動DMA
TX trigger,DMAC將在搶到總線后,將控制位為1的描述符上的數(shù)據(jù)包傳送(拷貝)到設備內存(TX FIFO)中。

我們總是假設DMAC和設備I/O控制器之間能夠很好地同步工作,故通常較少提及TX OVERFLOW事件。DMAC將成功傳送到設備內存的數(shù)據(jù)包對應的描述符的控制位自動置0(not owned by DMA,empty
and available for CPU to reuse/refill),并向CPU發(fā)出中斷請求。設備驅動程序一般在TX ISR或下一次的send中,掃描TX環(huán)上控制位為0的描述符環(huán),并釋放回收(reap)掛在上面的數(shù)據(jù)包緩存。
5.DMA與cache的一致性問題
5.1 cache一致性問題
下圖展示了CPU、cache、RAM和Device之間的數(shù)據(jù)流動。

中間CPU和內存直接交互的為在kseg1地址空間的操作;上下經(jīng)過Cache的為kseg0地址空間的操作(指針對不適用MMU的情況)。
由于數(shù)據(jù)區(qū)域對于進程來說是可讀寫的,而指令數(shù)據(jù)對于進程來說是只讀的,故程序源碼編譯后分為“程序指令”和“程序數(shù)據(jù)”兩種段,這種分區(qū)虛存有利于權限管理。當系統(tǒng)中運行著多個進程的實例副本時,由于指令是一樣的,所以內存中只需要保存一份程序的指令部分。現(xiàn)代處理器的L1
Cache設計時基本上采用與程序分段思想呼應的哈佛結構,即將指令和數(shù)據(jù)分離,分別叫做I-Cache、D-Cache,各自有獨立的讀寫端口(I-Cache只讀,不需要寫端口)。哈佛結構有利于提高程序的局部性,具體來說能夠提高CPU對緩存的命中率。
如果RAM(Memory)和Device之間的一次數(shù)據(jù)交換改變了RAM中DMA緩存區(qū)的內容,假設在這個案例里恰好cache中緩存了DMA緩沖區(qū)對應RAM中一段內存塊,如果沒有一種機制確保cache中的內容被新的DMA緩沖區(qū)數(shù)據(jù)更新(或者無效),則cache和它對應的RAM中的一段內存塊在內容上出現(xiàn)了不一致性。若此時CPU試圖去獲取Device傳到RAM的DMA緩沖區(qū)中的數(shù)據(jù),它將直接從cache獲得數(shù)據(jù),這些數(shù)據(jù)顯然不是所期望的,因為cache對應的RAM中的數(shù)據(jù)已經(jīng)被更新了。
單就cache一致性問題,不同體系架構有不同策略。有些在硬件層面予以保證(比如x86平臺);有些則沒有硬件支持而需要軟件的參與(比如MIPS、ARM平臺),此時設備驅動程序員需要在軟件層面解決cache與DMA不一致性問題。
5.2 一致性DMA映射
前面提到過,出于訪存性能考慮,在內核協(xié)議棧中流竄的數(shù)據(jù)包對應的DMA緩存區(qū)段最好開啟cache,通常分配在緩存的區(qū)段(例如MIPS中的kseg0段)。
在x86平臺上,硬件處理cache的一致性,所以一致性的DMA映射的建立只需要保證能獲得一組所需大小的連續(xù)的物理內存頁幀即可。在MIPS或ARM品臺上,dma_alloc_coherent()首先分配一組連續(xù)的物理頁用作后續(xù)DMA操作的緩沖區(qū),然后在軟件層面將該段物理地址空間重新映射到非緩存的虛擬地址空間,具體來說在頁目錄和頁表項中關閉了這段映射區(qū)間上的cache功能,使得cache的一致性問題不再成為問題。因為關閉了cache,失去了高速緩存功能,所以一致性映射在性能上打了折扣。
DMA描述符本身很適合使用一致性映射,一般映射到非緩存的區(qū)段(例如MIPS中的kseg1段)。
在實際驅動程序中,一致性映射的緩沖區(qū)都是由驅動程序自身在初始化階段分配,其生命周期可以一直延續(xù)到該驅動程序模塊從內系統(tǒng)中移除。但在某些情況下,一致性映射也會遇到無法克服的困難,這主要是指驅動程序中使用的DMA緩沖區(qū)并非由驅動程序分配,而是來自其他模塊(典型的如網(wǎng)絡設備驅動程序中數(shù)據(jù)包傳輸?shù)膕kb->data所指向的緩沖區(qū)),此時需要使用另一種DMA映射方式:流式DMA映射。
5.3 流式DMA映射
在流式DMA映射場合,DMA傳輸通道所使用的緩沖區(qū)往往不是由當前驅動程序自身分配的,而且往往每次DMA傳輸都會重新建立一個流式映射的緩沖區(qū)。此外,由于無法確定外部模塊傳入的DMA緩沖區(qū)的映射情況,所以設備驅動程序必須小心地處理可能會出現(xiàn)的cache一致性問題。
(1)在向內傳輸(rx)時,DMA設備將數(shù)據(jù)寫入內存后,DMAC將向CPU發(fā)出中斷請求,在RX ISR中使用該內存之前,需要先InvalidateD-Cache(sync_single_for_cpu)使cache無效重填(refill),此時CPU通過高速緩存cache獲得的才是最新的數(shù)據(jù)。
(2)在向外傳輸(tx)時,一種可能的情形是CPU構造的本地協(xié)議棧反饋包還在D-Cache中,故在send調用中需要先Flush D-Cache(sync_single_for_device)將數(shù)據(jù)寫回(write back)到內存,使DMA緩存更新為最新鮮的待發(fā)送數(shù)據(jù)再啟動DMA TX
trigger。
需要注意的是,在某些平臺上,比如ARM,CPU的讀/寫用的是不同的cache(讀用的是cache,寫則用的是write buffer),所以建立流式DMA映射需要指明數(shù)據(jù)在DMA通道中的流向,以便由內核決定是操作cache還是write buffer。
5.4 分散/聚集DMA映射(scatter/gather map)
到目前為止,對DMA操作時緩沖區(qū)的映射問題的討論僅限于單個緩沖區(qū),接下來將討論另一種類型的DMA映射——分散/聚集映射。
分散/聚集映射通過將虛擬地址上分散的多個DMA緩沖區(qū)通過一個類型為struct scatterlist的數(shù)組或鏈表組織起來,然后通過一次的DMA傳輸操作在主存RAM和設備之間傳輸數(shù)據(jù)??梢灶惐萕inSock中WSA系列API提供的Scatter/GatherI/O特性。

上圖顯示了主存中三個分散的物理頁面與設備之間進行的一次DMA傳輸時分散/聚集映射示意。其中單個物理頁面與設備之間可以看做是一個單一的流式映射,每個這樣的單一映射在內核中有數(shù)據(jù)結構strcut scatterlist來表示。
如果從CPU的角度來看這種分散/聚集映射,它對應的需求時三塊數(shù)據(jù)(分別存放在三段分散的虛擬地址空間中)需要和設備進行交互(發(fā)送或者接收),通過建立struct scatterlist類型的數(shù)組/鏈表在一次DMA傳輸中完成所有的數(shù)據(jù)傳輸。這樣,通過減少重復的DMA傳送請求來提高效率。
通過上面的討論可知,分散/聚集映射本質上是通過一次DMA操作把主存中分散的數(shù)據(jù)塊在主存與設備之間進行傳輸,對于其中的每個數(shù)據(jù)塊內核都會建立對應的一個流式DMA映射。但是對于MIPS、ARM平臺而言,還是需要通過軟件來保證cache的一致性問題。
參考:
《MIPS體系結構透視》
10.3<可見緩存的問題>
《深入Linux設備驅動程序內核機制》
10<內存映射和DMA>
12.3.3<數(shù)據(jù)包的發(fā)送>
12.3.6<數(shù)據(jù)包的接收>
《精通Linux設備驅動程序開發(fā)》
10.4 DMA
10.5.2 數(shù)據(jù)傳輸
15 網(wǎng)絡接口卡
《Linux設備驅動》
15 內存映射和 DMA
《深入分析Linux內核源碼》
11.2.6驅動DMA工作
《Blackfin DMA》《ADI blackfin DMA Descriptor》
《計算機cpu和外設的平等性--DMA簡述》
《嵌入式中的DMA介紹》《對各種DMA的理解困惑》
《ARM DMA Channel Number Register》
《通用設備的動態(tài)DMA映射》
《linux內核學習之網(wǎng)絡篇——接收分組》
《Linux內核分析 - 網(wǎng)絡[二]:B44網(wǎng)卡驅動接收報文》
《UDP數(shù)據(jù)報從網(wǎng)卡驅動到用戶空間流程總結》
《mx27 DMA 驅動》《8139d網(wǎng)卡工作原理》
《e1000中DMA傳輸?shù)膯栴}》《網(wǎng)絡數(shù)據(jù)包收發(fā)流程(三):e1000網(wǎng)卡和DMA》
《網(wǎng)卡利用DMA機制接收和發(fā)送數(shù)據(jù)包的具體流程》
《Linux內核開發(fā)之內存與I/O訪問》《Linux驅動修煉之道-DMA框架源碼分析》
《Linux DMA驅動開發(fā)(S3C2410)》《Linux DMA驅動分析(S3C6410)》《Linux
DMA驅動構架分析(S3C2440)》
《Intel 82571零拷貝的設計與實現(xiàn)》《Linux網(wǎng)絡處理“零拷貝”技術&mmap()》
《基于DMA實現(xiàn)高速數(shù)據(jù)包收發(fā)》《高速網(wǎng)絡接口卡DMA引擎的設計與實現(xiàn)》
|