十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
映射一個(gè)設(shè)備意味著將用戶空間的一段內(nèi)存與設(shè)備內(nèi)存關(guān)聯(lián)起來。無論何時(shí)當(dāng)程序在分配的地址范圍內(nèi)讀寫時(shí),實(shí)際上訪問的就是設(shè)備。不是所有的設(shè)備都能進(jìn)行mmap抽象。比如像串口和其他面向流的設(shè)備就不能。mmap的另一個(gè)限制是:必須以PAGE_SIZE為單位進(jìn)行映射。
mmap方法是file_operation結(jié)構(gòu)的一部分,并且執(zhí)行mmap系統(tǒng)調(diào)用時(shí)將調(diào)用該方法。為了執(zhí)行mmap,驅(qū)動(dòng)程序只需要為該地址范圍建立合適的頁(yè)表,并將vma->vm_ops替換為一系列的新操作即可。
有兩種建立頁(yè)表的方法:使用remap_pfn_range函數(shù)一次全部建立,或者通過nopage方法每次建立一個(gè)頁(yè)。
2、執(zhí)行直接IO訪問
如果需要傳遞的數(shù)據(jù)量非常大,直接進(jìn)行數(shù)據(jù)傳輸,而不需要額外的從內(nèi)核空間拷貝數(shù)據(jù)操作的參與,這將會(huì)大大提高速度。使用直接IO并不在任何情況下都能提高性能。設(shè)置直接IO的開銷很大,而又沒有使用IO緩存的優(yōu)勢(shì)。比如,使用直接IO需要write系統(tǒng)調(diào)用同步執(zhí)行;否則應(yīng)用程序?qū)?huì)不知道什么時(shí)候能再次使用他的IO緩沖區(qū)。在每個(gè)寫操作完成之前不能停止應(yīng)用程序,這樣會(huì)導(dǎo)致關(guān)閉程序緩慢,這就是使用直接IO的應(yīng)用程序也使用異步IO的原因。
在字符設(shè)備中執(zhí)行直接IO是不行的,也是有害的。只有確定設(shè)置緩沖IO的開銷特別大,才使用直接IO。塊設(shè)備和網(wǎng)絡(luò)設(shè)備不擔(dān)心實(shí)現(xiàn)直接IO的問題,內(nèi)核高層代碼設(shè)置和使用了直接IO,而驅(qū)動(dòng)程序級(jí)的代碼甚至不需要知道已經(jīng)執(zhí)行了直接IO。
2.6內(nèi)核中,實(shí)現(xiàn)直接IO的關(guān)鍵名為get_user_pages函數(shù)。如果函數(shù)調(diào)用成功調(diào)用者就會(huì)擁有一個(gè)指向用戶空間緩沖區(qū)的頁(yè)數(shù)組,它將被鎖在內(nèi)存中。為了能直接操作緩沖區(qū),內(nèi)核空間代碼必須用kmap或kmap_atomic函數(shù)將每個(gè)page結(jié)構(gòu)指針轉(zhuǎn)換為內(nèi)核虛擬地址。使用直接IO的設(shè)備通常使用DMA操作,因此驅(qū)動(dòng)程序要從page結(jié)構(gòu)指針數(shù)組中創(chuàng)建一個(gè)分散/聚集鏈表。
一旦直接IO操作完成則需要釋放用戶內(nèi)存頁(yè)。在釋放前,如果改變了這些頁(yè)中的內(nèi)容,則必須通知內(nèi)核,否則內(nèi)核會(huì)認(rèn)為這些頁(yè)是干凈的,也就是說,內(nèi)核會(huì)認(rèn)為他們與交換設(shè)備中的拷貝是匹配的,因此,無需回存就能釋放他們。
3、異步IO
aio_read和aio_write函數(shù)的目的是初始化讀和寫操作,在這兩個(gè)函數(shù)完成時(shí),讀寫操作可能已經(jīng)完成。如果操作立刻完成,函數(shù)將返回常規(guī)狀態(tài):傳輸?shù)淖止?jié)數(shù)或者錯(cuò)誤碼。
如何允許異步操作:如果驅(qū)動(dòng)程序可以開始操作,必須記住與操作相關(guān)的所有信息,并且返回-EIOCBQUEUE給調(diào)用者。記住操作的信息包括了安排對(duì)用戶空間緩沖區(qū)的訪問;一旦返回,因?yàn)橐\(yùn)行在調(diào)用進(jìn)程的上下文中,所以將不能再訪問這個(gè)緩沖區(qū)。通常這意味著建立直接的內(nèi)核映射(get_user_pages)或者DMA映射。-EIOCBQUEUE表示操作沒有完成,它的最終狀態(tài)將在未來某個(gè)時(shí)刻公布。當(dāng)未來某個(gè)時(shí)刻到來時(shí),驅(qū)動(dòng)程序必須通知內(nèi)核操作已經(jīng)完成。使用aio_complete函數(shù)。
4、直接內(nèi)存訪問DMA
DMA是一種硬件機(jī)制,他允許外圍設(shè)備和主內(nèi)存之間直接傳輸他們的IO數(shù)據(jù),而不需要系統(tǒng)處理器的參與。使用這種機(jī)制可以提高與設(shè)備通信的吞吐量,因?yàn)槊獬舜罅康挠?jì)算開銷。
DMA需要設(shè)備驅(qū)動(dòng)程序分配一個(gè)或者多個(gè)適合執(zhí)行DMA的特殊緩沖區(qū)。
必須謹(jǐn)慎的為DMA操作分配正確的內(nèi)存類型,因?yàn)椴⒉皇撬袃?nèi)存區(qū)間都適合DMA操作。在實(shí)際操作中,一些設(shè)備和一些系統(tǒng)中的高端內(nèi)存不能用于DMA,這是因?yàn)橥鈬O(shè)備不能使用高端內(nèi)存的地址。在現(xiàn)代總線上的大多數(shù)設(shè)備能夠處理32位地址,這意味著常用的內(nèi)存分配機(jī)制能很好的工作。一些PCI設(shè)備沒能實(shí)現(xiàn)全部的PCI標(biāo)準(zhǔn),因此不能使用32位地址,而一些ISA設(shè)備還局限在使用24位地址的階段。
對(duì)于有這些限制的設(shè)備,應(yīng)使用GFP_DMA標(biāo)志調(diào)用kmalloc或者get_free_pages從DMA區(qū)間分配內(nèi)存。當(dāng)設(shè)置了該標(biāo)志時(shí),只有使用24位尋址方式的內(nèi)存才能被分配。另外韓可以使用通用DMA層來分配緩沖區(qū),這樣也恩那個(gè)滿足對(duì)設(shè)備限制的需要。
如果需要為DMA緩沖區(qū)分配一大塊內(nèi)存,最好考慮一下是否有替代方法。如果設(shè)備支持分散/聚合IO,則可以將緩沖區(qū)分配成多個(gè)小塊,設(shè)備會(huì)很好的處理它們。當(dāng)用戶空間中執(zhí)行直接IO的時(shí)候,也可以用分散/聚合IO。
基于DMA的硬件使用總線地址,而非物理地址。
int dma_set_mask(struct device *dev,u64 mask);//如果使用指定的mask時(shí)DMA能正常工作,則返回非零值。
void *dma_alloc_coherent(struct device *dev,size_t size,dma_addr_t *dma_handle,int flag);//分配一致性DMA緩沖區(qū);函數(shù)的返回值是緩沖區(qū)的內(nèi)核虛擬地址,可以被驅(qū)動(dòng)程序使用;而與其相關(guān)的總線地址,返回時(shí)保存在dma_handle中。
void dma_free_alloc_coherent(struct device *dev,size_t size,void *addr,dma_addr_t dma_handle);//一致性DMA緩沖區(qū)釋放
struct dma_pool *dma_pool_create(const char *name ,struct device *dev,size_t size,size_t align,size_t allocation);//創(chuàng)建DMA池
void dma_pool_destroy(struct dma_pool *pool);//銷毀DMA池
void *dma_pool_alloc(struct dma_pool *pool,int mem_flags,dma_addr_t *handle);//從DMA池中分配DMA緩沖區(qū)
void dma_pool_free(struct dma_pool *pool,void *vaddr,dma_addr_t addr);//釋放緩沖區(qū)到DMA池
dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction);
void dma_unmap_single(struct device *dev,dma_addr_t dma_addr,size_t size,enmu dma_data_direction direction);
流式DMA原則:
1、緩沖區(qū)的傳送方向必須匹配與映射時(shí)給定的方向值
2、一旦緩沖區(qū)被映射,他將屬于設(shè)備,而不是處理器。直到緩沖區(qū)被撤銷映射前,驅(qū)動(dòng)程序不能以任何方式訪問其中的內(nèi)容
3、在DMA處于活動(dòng)期間,不能撤銷對(duì)緩沖區(qū)的映射,否則會(huì)嚴(yán)重破壞系統(tǒng)的穩(wěn)定性。
有時(shí)候,驅(qū)動(dòng)程序需要不經(jīng)過撤銷映射就訪問流式DMA緩沖區(qū),內(nèi)核提供了一下方法:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr,size_t size,enmu dma_data_direction direction);//應(yīng)該在處理器訪問流式DMA緩沖區(qū)前調(diào)用該函數(shù)。一旦調(diào)用,則處理器將“擁有”DMA緩沖區(qū),并可根據(jù)需要對(duì)他進(jìn)行訪問。
void dam_sync_single_for_device(...)//在設(shè)備訪問緩沖區(qū)前,應(yīng)該調(diào)用該函數(shù)將緩沖區(qū)所有權(quán)還給設(shè)備。
dma_addr_t dma_map_page(struct device *dev,struct page *page,unsigned long offset,size_t size,enum dma_data_direction direction);//單頁(yè)流式映射
void dma_unmap_page(struct device *dev,dma_addr_t dma_address,size_t size,enmu dma_data_direction);//撤銷單頁(yè)映射
int dma_map_sg(struct device *dev,struct scatterlist *sg,int nents,enum dma_data_direction direction);//分散/聚集映射建立
dma_addr_t sg_dma_address(struct scatterlist *sg);//從該分散表的入口返回總線地址
unsigned int sg_dma_len(struct scatterlist *sg);//返回緩沖區(qū)的長(zhǎng)度