1. 程式人生 > >linux 記憶體對映 PCI記憶體對映 DMA對映

linux 記憶體對映 PCI記憶體對映 DMA對映

記憶體對映, 就是指把外設的記憶體對映到使用者空間訪問。系統呼叫為:

       #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 

其中fd 可以為開啟的普通檔案或裝置檔案。返回的地址為使用者地址(即vma的地址, vm_area_struct, 跟描述vmalloc返回核心虛擬地址的vm_struct 不一樣。)

驅動程式中的mmap原型為:        

        int (*mmap) (struct file *filp, struct vm_area_struct *vma);

 在sysmmap系統呼叫到f->ops->mmap前,linux OS 已做了大量工作,包含分配vma (或合併原vma)。到此時,vma已經填好,只要將分配實體記憶體和建立對應的頁表即可。如果有實體記憶體,可以通過remap_pfn_range 一次性建立全部頁表,或者在vma的nopage(現新核心改成fault)中每次對映一個頁面。

使用者程序訪問使用者空間地址的時候直接通過頁表訪問,跟vma沒任何關係。vma是核心管理使用者程序的使用者態記憶體區域的。如果訪問地址失敗(或者缺頁表,或者缺實體記憶體),都將將引發缺頁異常。缺頁異常會判定異常的原因,如果是對映的file原因,則呼叫vma的nopage函式,返回page指標,然後據此計算頁幀號並寫入對應的頁表像pte。這樣下次就可以訪問了。

nopage函式需要返回一個page指標(page為核心描述實體記憶體的結構)。如果是vmalloc分配的物理頁(單個頁面不划算,要分配頁表),則呼叫vmalloc_to_page 返回頁面指標;如果是get_free_pages(或者kmalloc)返回的記憶體,則呼叫virt_to_page返回page指標。(其實這兩個函式實現一樣,都是 mem_map [ (kaddr -3G)>>12]. 

remap不允許對映常規記憶體,除非需要mem_map_reserve 先設定成保留,因常規記憶體由OS自己管理。

PCI裝置有兩次對映,第一次是將外設儲存對映到統一的實體地址,在系統啟動的時候已經由bios做好了,分配的實體地址記錄在PCI外設配置空間裡面。在啟動後,呼叫ioremap(pci_resource_start()) 返回核心虛擬地址(跟vmalloc的類似),原型為:

void *  ioremap (unsigned long phys_addr, unsigned long size) 然後可以用專用的IO埠操作函式操作 ioread/iowrite. 

DMA對映則相反,DMA對映是分配的緩衝區(核心邏輯地址,不管是dma_alloc_coherent, kmalloc等等,本質都是get_free_pages(DMA))跟一組裝置可訪問的實體地址的組合。dma_alloc_coherent 內部呼叫get_free_pages(DMA),返回該地址,同時設定對應的實體地址(匯流排地址返回)。  核心使用邏輯地址設定資料,然後將對應的實體地址寫入DMA暫存器(即寫上面的ioremap返回的地址),讓DMA完成操作。