1 / 56

MMAP 與 DMA

Chapter 15. MMAP 與 DMA. 601430026 許名宏. 15.1 Linux 的記憶體管理. 主要是描述用於控管記憶體的各種 資料結構,相當冗長 。 有了 必要的基礎知識後,我們就可以開始使用這些 結構 。. 15.1.1 位址的分類 (1/4). 作業系統的分類 上, Linux 是一種虛擬記憶 系統。 虛擬記憶系統將邏輯世界 ( 軟體 ) 與現實世界 ( 硬體 ) 分隔開 來 , 最大 的好處是軟體可配置的空間超過 RAM 的實際 容量。 另一 項優點是核心可在執行 期間改變 行程的部分記憶 空間。

Download Presentation

MMAP 與 DMA

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Chapter 15 MMAP與DMA 601430026 許名宏

  2. 15.1 Linux的記憶體管理 • 主要是描述用於控管記憶體的各種資料結構,相當冗長。有了必要的基礎知識後,我們就可以開始使用這些結構。

  3. 15.1.1 位址的分類(1/4) • 作業系統的分類上,Linux是一種虛擬記憶系統。 • 虛擬記憶系統將邏輯世界(軟體)與現實世界(硬體)分隔開來,最大的好處是軟體可配置的空間超過RAM的實際容量。 • 另一項優點是核心可在執行期間改變行程的部分記憶空間。 • Linux系統上不只有兩種位址(虛擬、實體),而且每種位址都有其特殊用途。但核心原始程式裡沒有明確定義何種位址適用何種情況,所以必須相當謹慎小心。

  4. 15.1.1 位址的分類(2/4)

  5. 15.1.1 位址的分類(3/4) • 使用者虛擬位址(User Virtual Address) • 簡稱為虛擬位址,也就是 user-process 所見到的一般位址。虛擬位址寬度隨CPU架構而定。 • 實體位址(Physical Address) • 用於CPU與系統記憶體之間的位址。寬度依CPU而定,但不一定與CPU暫存器的寬度相符。 • 匯流排位址(Bus Address) • 用於周邊匯流排與記憶體的位址,具有高度的平台相依性。 • 核心邏輯位址(Kernel Logical Address) 這類位址構成核心的正常位址空間,他們對應到所有主記憶體,而且通常被當作實體位址來使用。邏輯位址與實體位址只差距一段固定偏移量,通常存放在unsigned long或void *型別變數上。kmalloc()所傳回的記憶體,就是以邏輯位址來定位。 • 核心虛擬位址(Kernel Virtual Address) • 核心虛擬位址跟邏輯位址不同之處,在於核心虛擬位址與實體位址不一定有直接對應關係,虛擬位址通常存放在指標變數中。vmalloc()配置而來得記憶體位址是以虛擬位址來表示。

  6. 15.1.1 位址的分類(4/4) • <asm/page.h>定義了兩個可換算位址的巨集。 • 如果你有一個邏輯位址, __pa()巨集可換算出其對應的實體位址。 • __va()可將實體位址換算回邏輯位址,但僅限於低記憶體的實體位址才有效,因為高記憶體沒有邏輯位址。

  7. 15.1.2 高低記憶體 • 核心邏輯位址與核心虛擬位址之間的差異,在配備超大量記憶體的32-bits系統上才凸顯出來。 • 低記憶體(Low memory) • 存在於kernel-space裡,具有邏輯位址的記憶體。 • 高記憶體(High memory) 沒有邏輯位址的記憶體,因為超過了核心的虛擬位址空間。 • 高低記憶體之間的分界線 核心在開機期間依據BIOS提供的資訊來決定的。在i386系統,分界通常位於1GB的位置。這是核心自己設下的限制,因為核心必須將32-bit位址空間劃分成kernel-space與user-space兩大部份。

  8. 15.1.3 記憶體對應表與struct page(1/2) • 由於高記憶體沒有邏輯位址,核心裡負責管理記憶體的函式,紛紛改用struct page來代替邏輯位址。 • page結構含有關於實體記憶體的一切資訊。系統上的每一個實體記憶頁,都有一個專屬的struct page,以下是page結構裡幾個比較重要的欄位。 • atomic_t count; • 此記憶頁的用量計次。當count值降為0時,記憶頁會被釋放回自由串列。 • void *virtual; • 本記憶頁對應的核心虛擬位址;若無對應的虛擬位址則指向NULL 。 • unsigned long flags; • 一組描述記憶頁狀態的位元旗標。如PG_locked(代表記憶頁是否已被鎖定)、PG_reserved(是否受記憶體管理系統的管轄) 。

  9. 15.1.3 記憶體對應表與struct page(2/2) • 為了方便在struct page指標與虛擬位址之間轉換,Linux定義了一組方便的函式與巨集: • struct page *virt_to_page(void *kaddr) ; • 將核心邏輯位址轉換成對應的struct page指標。 • void *page_address(struct page *page); • 傳回指定的page的核心虛擬位址。位於高記憶體的記憶頁,除非已事先映射到虛擬位址空間,否則沒有虛擬位址。 • #include <linux/highmem.h> • void *kmap(struct page *page) ; • kmap()可傳回系統上任何記憶頁的核心虛擬位址。 • Page在低記憶體→則傳回該記憶頁的邏輯位址。 • Page在高記憶體→kmap()主動將它映射到特殊的虛擬空間。 • 如果分頁表剛好沒有空位,kmap()有可能會休眠。 • void kunmap(struct page *page) ; • 將kmap()所建立的特殊對應解除。

  10. 15.1.4 虛擬記憶體分區(1/6) • 核心需要一個較高層級的機制,才能處理行程所見到的記憶體佈局。在Linux,這機制稱為虛擬記憶體分區(virtual memory areas),通常簡稱為分區或VMA。 • 用來管理使用者行程的虛擬位址空間的各個區域。 • 一個行程的虛擬位址空間,至少含有下列幾個VMA: • 一個存放程式碼(executable binary)的區域,通常稱為text。 • 多個資料分區,包括有初值的資料、沒初值的資料,以及程式堆疊。 • 每一個有效的記憶體對映(memory mapping),各有一個分區。

  11. 15.1.4 虛擬記憶體分區(2/6) • 例:以下是init行程VMA的分布情形。cat /proc/1/maps 08048000-0804e000 r-xp 00000000 03:01 64652 /sbin/init text 0804e000-0804f000 rw-p 00006000 03:01 64652 /sbin/init data 0804f000-08053000 rwxp 00000000 00:00 0 zero-mapped BSS 40000000-40015000 r-xp 00000000 03:01 96278 /lib/ld-2.3.2.so text 40015000-40016000 rw-p 00014000 03:01 96278 /lib/ld-2.3.2.so data 40016000-40017000 rw-p 00000000 00:00 0 BSS for ld.so 42000000-4212e000 r-xp 00000000 03:01 80290 /lib/tls/libc-2.3.2.so text 4212e000-42131000 rw-p 0012e000 03:01 80290 /lib/tls/libc-2.3.2.so data 42131000-42133000 rw-p 00000000 00:00 0 BSS for libc bffff000-c0000000 rwxp 00000000 00:00 0 Stack segment ffffe000-fffff000 ---p 00000000 00:00 0 vsyscallpage 各欄位的格式如下: start-end|perm|offset|major:minor|inode|imagename

  12. 15.1.4 虛擬記憶體分區(3/6) • 上面每一欄除了imagename之外,都分別對應到structvm_area_struct裡的欄位,這些欄位意義如下: • start、end VMA前後邊界的虛擬位址 • perm VMA的存取位元遮罩(r、w、x、p/s) • offset VMA所映射的檔案內容之起點 • major:minor 持有映射檔的裝置的主、次編號 • inode 被映射檔案的inode編號 • imagename 被映射檔案(通常是可執行檔)的名稱 • mmap() 是Unix 系統的一個重要的系統呼叫,其作用是將裝置記憶體映射到 user-space 行程的虛擬記憶空間。

  13. 15.1.4 虛擬記憶體分區(4/6) • Linux核心是以 structvm_area_struct 來表示VMA。 • 驅動程式不能任意建立新的VMA,否則會破壞整個組織,在核心內部,VMA是以串列與樹狀結構組織在一起(為了提升查詢VMA的效率) 。 • vm_area_struct的主要欄位: • unsigned long vm_start; • unsigned long vm_end; • VMA所涵蓋的虛擬位址範圍。 • struct file *vm_file; • 如果VMA的映射對象是檔案,則vm_file指向該檔案的struct file結構。

  14. 15.1.4 虛擬記憶體分區(5/6) • unsigned long vm_pgoff; • 此區域在檔案裡的相對位置(以page為單位)。當一個檔案或裝置被映射到記憶體,vm_pgoff就是映射到此區域的第一頁的檔案位置。 • unsigned long vm_flags; • 描述VMA性質的一組旗標。對裝置驅動程式而言,最可能用到的旗標是VM_IO 與VM_RESERVED。其中VM_IO表示該VMA是映射到硬體裝置上的I/O 位址區;VM_RESERVED要求記憶體管理系統不要將該VMA置換到磁碟上。 • structvm_operations_struct *vm_ops; • 一組可供核心用來操作此VMA的函式。這函式指標的存在,表示核心將VMA當成一種物件來看待。 • void *vm_private_data; • 供驅動程式用於儲存私有資訊的欄位。

  15. 15.1.4 虛擬記憶體分區(6/6) • void (*open)(structvm_area_struct*vma); • 核心會呼叫open作業方法,讓實作VMA的子系統有機會初始VMA 、調整用量計次...等等。 • void (*close)(structvm_area_struct*vma); • 當VMA被摧毀,核心會呼叫它的close作業方法。 • struct page *(*nopage)(structvm_area_struct *vma, unsigned long address, inttype); • 行程試圖讀取某個VMA記憶頁,但該記憶頁目前不在主記憶體裡,則會執行VMA的nopage作業方法。nopage作業方法通常會從磁碟上的交換區讀回記憶頁的內容,然後傳回一個指向實體記憶頁的structpage的指標。若果VMA沒定義它自己的nopage作業方法,則核心會配置一個空的記憶頁。

  16. remap_pfn_range() 與 nopage 作法圖示 ︰

  17. 15.2 mmap作業方法(1/2) • 就驅動程式的觀點而言,記憶體映射可用來提供直接存取裝置記憶體的能力給user-space應用程式。 • mmap()最經典的應用,就是 X Window System server 利用它來存取顯示卡的視訊記憶體。以下是 Xserver 行程的記憶體對應表: cat /proc/731/maps 000a0000-000c0000 rwxs 000a0000 03:01 282652 /dev/mem 000f0000-00100000 r-xs 000f0000 03:01 282652 /dev/mem 00400000-005c0000 r-xp 00000000 03:01 1366927 /usr/X11R6/bin/Xorg 006bf000-006f7000 rw-p 001bf000 03:01 1366927 /usr/X11R6/bin/Xorg 2a95828000-2a958a8000 rw-s fcc00000 03:01 282652 /dev/mem 2a958a8000-2a9d8a8000 rw-s e8000000 03:01 282652 /dev/mem • a0000:VGA卡的視訊記憶體的標準位置 • e8000000:位於系統記憶體的頂端,直接對應到顯卡上的視訊記憶體

  18. 15.2 mmap作業方法(2/2) • 由於X server時常需要傳輸大量資料到視訊記憶體,如果使用傳統的lseek()、write(),勢必引發相當頻繁的context switch,而傳輸效率當然就很差勁;然而如果將視訊記憶體直接映射到user-space,則應用程式可以直接填寫視訊記憶體,所以傳輸效率得以大幅提升。 • mmap作業方法屬於file_operations結構的一部份,由mmap()系統呼叫觸發。 • mmap (caddr_taddr,size_tlength,intprot,intflags,intfd,off_toffset); /此為mmap()系統呼叫的宣告形式/ • int (*mmap)(structfile *filp,structvm_area_struct *vma); /mmap作業方法宣告方式/ • 有兩種方法可以製作分頁表:全部交給remap_pfn_range()函式一次搞定。或者透過VMA的nopage作業方法,在VMA被存取時,才一次處理一頁。

  19. 15.2.1 使用remap_pfn_range() • 要將一段虛擬位址映射到一段實體位址,必須另外產生新的分頁表,這個任務就交給它來完成。 • intremap_pfn_range(structvm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_tprot); • 映射成功,則傳回0,否則傳回一個負值錯誤碼。 • vma 記憶頁所要映射到的VMA。 • virt_addr實體位址所要映射到的虛擬位址範圍之起點。 • pfn 虛擬位址所要映射到的實體位址的頁框編號。 • size 映射區的規模(以byte為計算單位) 。 • prot 新VMA的保護方式。驅動程式能使用 vma->vm_page_port所提供的值。

  20. 15.2.2 mmap實例 簡單、線性的映射作法,讓應用程式可透過user-space的某段虛擬位 址來存取裝置記憶體 • static intsimple_remap_mmap(struct file *filp, structvm_area_struct *vma) • { • if (remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff, • vma->vm_end - vma->vm_start, • vma->vm_page_prot)) • return -EAGAIN; • vma->vm_ops = &simple_remap_vm_ops; • simple_vma_open(vma); • return 0; • }

  21. 15.2.3 增添新的VMA作業方法 • void simple_vma_open(structvm_area_struct *vma) • { • printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n", • vma->vm_start, vma->vm_pgoff << PAGE_SHIFT); • } • void simple_vma_close(structvm_area_struct *vma) • { • printk(KERN_NOTICE "Simple VMA close.\n"); • } • static structvm_operations_structsimple_remap_vm_ops = { • .open = simple_vma_open, • .close = simple_vma_close, • };

  22. 15.2.4 使用nopage映射記憶體(1/3) • 雖然remap_page_range()已足以應付許多驅動程式的mmap,但偶爾會需要多一點彈性。對於這類情況,VMA的nopage作業方法或許是可以考慮的選擇。 • 適合使用nopage作業方法來映射位址空間的情況,是當驅動程式只需在VMA發生變化才會有處理動作時。應用程式可透過mremap()系統呼叫來改變VMA的邊界或大小。 • 發生mremap()系統呼叫時,核心不一定會通知驅動程式;假如改變的結果是VMA範圍縮減,核心可以默默清理掉多出來的記憶頁,而不必通知驅動程式;但如果是範圍擴張,核心才會呼叫該VMA的nopage作業方法來配置新的記憶頁。 • 之所以不讓驅動程式收到映射區擴張通知,是因為記憶體被實際應用之前,沒有處理的必要,而當真的有必要時,核心可觸發nopage來處理。

  23. 15.2.4 使用nopage映射記憶體(2/3) • struct page *simple_vma_nopage(structvm_area_struct *vma, unsigned long address, int*type) • { • structpage *pageptr; • unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; • unsigned long physaddr = address - vma->vm_start + offset; • unsigned long pageframe = physaddr >> PAGE_SHIFT; • if (!pfn_valid(pageframe)) • return NOPAGE_SIGBUS; • pageptr= pfn_to_page(pageframe); • get_page(pageptr); //遞增用量計次 • if (type) • *type = VM_FAULT_MINOR; • return pageptr; • }

  24. 15.2.4 使用nopage映射記憶體(3/3) static intsimple_nopage_mmap(struct file *filp, structvm_area_struct *vma) { unsigned long offset = vma ->vm_pgoff << PAGE_SHIFT; if (offset >= _ _ pa(high_memory) || (filp->f_flags & O_SYNC)) vma->vm_flags |= VM_IO; vma->vm_flags |= VM_RESERVED; vma->vm_ops = &simple_nopage_vm_ops; simple_vma_open(vma); return 0; }

  25. 15.2.5 重新映射特定I/O區(1/2) • 如果只想將整段位址中的一小段映射到user-space,驅動程式必須自己處理偏移位置(offset)。 • 例如,若要將實體位置simple_region_start開始的simple_region_size個位元組映射到user-space: • unsigned long off = vma->vm_pgoff << PAGE_SHIFT; • unsigned long physical = simple_region_start + off; • unsigned long vsize = vma->vm_end - vma->vm_start; • unsigned long psize = simple_region_size - off; • if (vsize > psize) • return -EINVAL; //跨越範圍太大 • remap_pfn_range(vma, vma->vm_start, physical, vsize, • vma->vm_page_prot);

  26. 15.2.5 重新映射特定I/O區(2/2) • 要避免映射範圍擴張,最簡單的辦法是實作一個簡單的nopage作業方法,讓它回覆一個SIGBUS信號給發生失誤的行程。例如: • struct page *simple_nopage(structvm_area_struct *vma, unsigned long address, int*type) • { • return NOPAGE_SIGBUS; /* 傳回一個 SIGBUS */ • }

  27. 15.2.6 重新映射 RAM • 如果要適度容許映射擴張,比較完善的做法,是檢查引發分頁失誤的位址,是否在有效的實體範圍內,如果是,才容許映射。 • remap_page_range() :只有保留頁,以及在實體記憶體(RAM)頂端之上的實體位址,它才有作用。保留頁被鎖在記憶體裡(不會被換出到磁碟上) ,所以可以安全地映射到user-space;這項限制式系統穩定度的基本要求。 • 由於remap_page_range()沒有處理RAM的能力,這表示類似scullp那樣的裝置將難以作出自己的mmap ,因為其裝置記憶體是一般的RAM而非I/O memory。幸好,還是可以使用nopage作業方法。

  28. 15.2.6.1 使用nopage重新映射RAM(1/3) • 先看看有哪些設計抉擇會影響scullp的mmap: • 在裝置被映射之後,scullp就不釋放其裝置記憶體,而且不能像scull或類似裝置那樣,在被開啟成write模式時,裝置長度就被截為0;要避免釋放已映射的裝置,驅動程式必須自己計算有效的映射次數,scullp_device結構中的vmas欄位,可當此用途來使用。 • 只有在scullp的order參數值為0 ,才容許映射記憶體。因為get_free_pages()和free_pages()只修改串列中第一個空頁計次值。 • 要遵循上述規則來映射RAM的程式,需要實作出open、close和nopage,而且還必須存取記憶對應表,調整記憶頁的用量計次。

  29. 15.2.6.1 使用nopage重新映射RAM(2/3) • intscullp_mmap(struct file *filp, structvm_area_struct *vma) • { • structinode *inode = filp->f_dentry->d_inode; • /* 如果order不等於0,則拒絕映射 */ • if (scullp_devices[iminor(inode)].order) • return -ENODEV; • /* 這裡不作任何事。交給“nopage”搞定 */ • vma->vm_ops = &scullp_vm_ops; • vma->vm_flags |= VM_RESERVED; • vma->vm_private_data = filp->private_data; • scullp_vma_open(vma); • return 0; • }

  30. 15.2.6.1 使用nopage重新映射RAM(3/3) • void scullp_vma_open(structvm_area_struct *vma) • { • structscullp_dev*dev = vma->vm_private_data; • dev->vmas++; • } • void scullp_vma_close(structvm_area_struct *vma) • { • structscullp_dev*dev = vma->vm_private_data; • dev->vmas--; • }

  31. 15.3 直接I/O • 大部份的I/O作業,透過kernel-space的緩衝區做緩衝,某種程度上隔離了user-space與實際裝置。好處:讓程式比較好寫,提升效能。 • 但在傳輸大量資料時,讓user-space直接與裝置I/O做溝通反而會比較好。

  32. 15.3.1直接I/O的關鍵函式 • 在2.6版核心實作直接I/O的關鍵函式是 get_user_pages() intget_user_pages(structtask_struct *tsk,structmm_struct *mm, unsigned long start,intlen,int write,int force,struct page **pages, structvm_area_struct **vmas); • structtask_struct *tsk :此指標指向要執行I/O的task_struct。 • structmm_struct *mm:此指標所指的mm_struct結構,是行程的虛擬位址空間的所有VMA。 • unsigned long start:user-space緩衝區的起始位址。 • intlen:該緩衝區的長度,以頁為計算單位。 • int write:若不是零,則要映射的記憶頁是供write存取之用。 • int force:要求 get_user_pages() 不理會指定記憶頁的保護旗標,對驅動程式應設為零。 • struct page **pages:page應該指向一個描述user-space緩衝區的struct page串列。 • structvm_area_struct **vmas :vmas應含有相關的VMA的指標。 • get_user_pages() 的回傳值:是實際映射的記憶頁數量,此數量有可能少於要求的數量,但 至少會大於零。

  33. 15.3.2釋放記憶頁之前要做的事情 • 完成直接I/O作業之後,必須釋放user-space記憶頁。 • 1.檢查記憶頁是否為記憶體對應表中的保留部份,因為保留頁絕對不會被置換出去,可使用PageReserved()來檢查特定記憶頁。 • 非絕對必要:user-space記憶體都不是保留頁。 • 2.若改變了記憶頁內容,則使用SetPageDirty(struct page *page);將記憶頁做標記,否則核心會直接釋放該記憶頁,而不寫回儲存裝置。 • 3.不管是否有改變記憶頁內容,都應將page從page cache釋放,這使用void page_cache_release(structpage *page);

  34. 15.3.3非同步I/O • 非同步 I/O 讓 user-space 可發動 I/O 作業要求,而不必等待 I/O 作業完成,也就是說在 I/O 進行過程中,應用程式仍可以做其它事。對於複雜的高效率應用程式,非同步 I/O 可讓它們同時進行多項工作。 • 核心沒強制要求驅動程式必須提供非同步 I/O 的能力,也只有非常少數的驅動程式設計者需要考慮這項能力,因為這通常無助於裝置的傳輸效率。

  35. 15.4 直接記憶體存取(DMA) • DMA是一種硬體機制,讓周邊元件可以直接與主記憶體交換I/O資料,而不必經過系統處理器。 • 由於DMA是“硬體”機制,其設定程序完全隨系統架構而定。 • DMA機制可大幅提昇周邊裝置的資料吞吐量,同時減輕CPU的運算負擔。

  36. 15.4.1 DMA資料傳輸的流程(1/3) • 有兩種機會可觸發DMA資料傳輸:軟體主動要求,或周邊硬體主動將資料推入系統。 • 第一種情況(軟體觸發)所涉及的步驟: 1.當行程發出一次read(),驅動程式的read作業方法就配置一塊DMA緩衝區,並指示周邊硬體開始傳輸資料。行程會進入休眠狀態。 2.周邊硬體將資料寫到DMA緩衝區,在完成傳輸之後,對CPU發出一次中斷訊號。 3.ISR(中斷服務程式)收下輸入資料、回應中斷、然後喚醒行程,讓行程讀走資料。

  37. 15.4.1 DMA資料傳輸的流程(2/3) • 第二種情況,發生在DMA被當成非同步傳輸機制使用 時的步驟: 1.周邊硬體觸發一次中斷,讓系統處理器知道新資料 已經到達。 2.ISR配置一個緩衝區,並將該緩衝區的位置告訴周邊 硬體,使其知道資料應該傳送到何處。 3.周邊硬體將資料寫入指定的緩衝區,在完成傳輸之 後,觸發另一次中斷。 4.ISR將新資料存放在適當位置,喚醒任何相關行程, 並處理一些例行工作。

  38. 15.4.1 DMA資料傳輸的流程(3/3) • 網路卡與CPU之間通常是透過主記憶體上的一塊環型緩衝區(稱為DMA ring buffer)互相交換資料。 • 當網路卡從外界收到一個封包,就將它放入環型緩衝區裡的下一個空位,然後發出中斷通知。 • 驅動程式將網路封包傳給核心裡的其它部門,並將一個新的DMA空位放回環型緩衝區。 • 大多數驅動程式在初始期就預先配置好所需的緩衝區,並全程使用同一塊緩衝區,直到關閉時才予以釋放。

  39. 15.4.2 配置DMA緩衝區 • 並非所有記憶體都可以用來當成DMA緩衝區,因此要配置一塊適合DMA的緩衝區,不是隨意配置一塊普通記憶體就了事了。 • DMA緩衝區必須是實體記憶體上的連續頁,因為周邊裝置使用ISA或PCI匯流排來傳輸資料,而兩種匯流排都使用實體位址。 • 自助配置法: • 需靠核心的mem=開機期參數配合,如原有256M的記憶體,而你需要1MB記憶體來當DMA緩衝區使用,則可使用mem=255M參數要求核心將最高的1MB保留給你。保留記憶體的模組程式為: • dmabuf=ioremap(0x1f00000/* 255M*/ ,0x100000 /* 1M */)。 • 積極配置法: • 使用 GFP_NOFAIL 積極配置足夠的DMA緩衝空間;不過這應該被視為最後手段,除非其它所有辦法都無效,因為積極配置會導致沉重的系統負載,甚至可能鎖死系統。

  40. 15.4.3 匯流排位址 • 具有DMA能力的周邊硬體,其實是使用匯流排位址,而非實體位址。在x86 PC上,ISA與PCI匯流排的位址確實等於x86 CPU的實體位址,但並非所有平台都這樣,有些平台的介面匯流排是透過橋接電路連接在一起,它們的I/O位址被映射到不同的實體位址。 • 在最底層,Linux核心提供一套通用的解決方案,以下兩個函式: • unsigned long virt_to_bus(volatile void * address); • void *bus_to_virt(unsigned long address); • 這兩個函式只是在虛擬位址與匯流排位址之間做轉換。通常不應該使用它們,因為它們僅適用於 I/O 架構非常簡單的平台,若是遇到有 I/OMMU 的平台就無能為力了。

  41. 15.4.4DMA抽象層 • 從驅動程式的觀點來看,DMA終究不過是配置一個緩衝區,並傳遞匯流排位置給裝置。 • 對於快取而言,如何保持快取的一致性,各家系統各有自己獨到的邏輯,若你的驅動程式沒能正確處理這方面的問題,可能會造成系統記憶錯亂。 • 並非所有系統的每一塊記憶體都可以配合DMA作業。例如:x86平台的高記憶體就不能當成DMA緩衝區。 • 核心提供了一個DMA抽象層,可跨越各種不同平台與匯流排之間的差異。

  42. 15.4.4.1 排除不支援的硬體 • 嘗試進行DMA作業之前,第一個要回答的問題是當時的平台是否支援目標裝置所需的DMA能力。 • 原則上核心假設裝置可在任何32-bit位址執行DMA作業,如果裝置不合這項假設,可以下列方式通知核心: intdma_set_mask (struct device *dev, u64 mask); 其中mask代表目標裝置的定址空間的位元數。 假如驅動的是只有24-bit定址能力的ISA裝置: if (dma_set_mask (dev, 0xffffff)) card->use_dma=1; else { card->use_dma=0; printk (KERN_WARN, “mydev: DMA not supported\n”); }

  43. 15.4.4.2DMA 對應 • 配置一個DMA緩衝區,並為該緩衝區產生一個可供裝置存取的位址,這兩個動作的組合就稱為 DMA 對應。 • 在PCI匯流排上的DMA對應被分成兩種類型,主要差別在於 DMA緩衝區的存活時間長短。這兩種對應模式如下: • 常態性DMA對應(coherent DMA mapping) • 若DMA緩衝區的生命期與驅動程式一樣長,就稱為常態性DMA對應。DMA緩衝區必須能夠同時被CPU與周邊使用,甚至應該被排除在快取機制之外,以免一方看不見另一方的更新。 • 臨時性DMA對應(streaming DMA mapping) • 為了單次DMA作業而臨時設置的DMA對應;這比較有彈性但必須遵守一些限制;建議盡可能使用臨時性對應基於兩項理由。首先,在配置對應暫存器的系統上,每組DMA對應都需要用掉一或多個暫存器,常態性對應會長期佔用這些暫存器;其次,有不少硬體平台特地針對臨時性對應做了最佳化,這些最佳化措施不能運行在常態性對應。

  44. 15.4.4.3 設定常態性DMA對應 • 驅動程式可呼叫dma_alloc_coherent() 來設定一組常態性的DMA對應,此函式包辦了緩衝區的配置與對應工作。 void *dma_alloc_coherent(struct device *dev, size_tsize, dma_addr_t *dma_handle, int flag); • dma_alloc_coherent() 內部使用 get_free_pages() 來配置指定的記憶空間,以便得到可以配置 DMA 作業的緩衝區;flag通常是設定為 GFP_KERNEL(可能會休眠)或 GFP_ATOMIC(絕不會休眠)。 • 當不再需要緩衝區時(通常是在卸載模組時,就應該盡快使用dma_free_coherent() 將緩衝區還給系統,此函式需同時提供CPU位址與匯流排位址。 void dma_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_tdma_handle);

  45. 15.4.4.4 設定臨時性DMA對應(1/3) • 臨時性 DMA 對應的軟體介面稍微複雜些,因為驅動程式自己得事先配置好緩衝區,並處理它們所沒選擇的位址。在某些平台上,臨時性對應甚至接受多個不連續的記憶頁。 • 設定臨性對應時,必須讓核心知道資料的移動方向,方向是以 enumdma_data_direction 型別的符號來描述,如下: • DMA_TO_DEVICE 如果是要將資料傳送到裝置上(為了回應 write() 系統呼叫)。 • DMA_FROM_DEVICE 如果是要將資料傳送到裝置上(為了回應 read() 系統呼叫)。 • DMA_BIDIRECTIONAL 表示如果容許資料雙向移動。 • DMA_NONE 供除錯用途。 • 當你只有一個緩衝區要傳輸,可使用 dma_map_single() 來將該緩衝區映射到裝置定址空間。 • dma_addr_tdma_map_single(struct device *dev, void *buffer, size_tsize, enumdma_data_direction direction); • 傳回值是可以傳給目標裝置的匯流排位址。如果映射失敗,則會傳回 NULL。 • 完成傳輸之後,應該立刻使用 pci_unmap_single() 來解除對應。 • void pci_unmap_single(structpci_dev *pdev,dma_addr_tbus_addr,size_tsize,intdirection); • 這裡的 size 和 direction 引數,必須符合當初映射緩衝區時所用的引數值。

  46. 15.4.4.4 設定臨時性DMA對應(2/3) • 臨時性 DMA 對應必須遵守三點重要法則: • 緩衝區的使用,必須符合映射時所設定的傳輸方向。 • 在緩衝區映射到匯流排位址之後,就屬於裝置,而非處理器。在解除對應之前,驅動程式不能以任何方式接觸緩衝區。只有在呼叫 dma_unmap_single() 之後,驅動程式才能安全存取緩衝區的內容。這意味著你必須先將要寫入裝置的資料放在緩衝區,然後才能映射它。 • 在DMA動作期間,不能解除對應,否則保證系統一定會嚴重錯亂。 • 為何驅動程式不能接觸已被對應的緩衝區?有兩項原因,第一,如果要輸出資料到裝置上,核心必須確保要放在DMA緩衝區的資料,已經確實全數寫入記憶體;第二,如果被映射的緩衝區位於周邊裝置無法存取的區域時,某些平台會直接讓DMA作業失敗,而其它平台則可能會建立一個轉進緩衝區(bounce buffer),轉進緩衝區只是另一塊裝置可以存取的記憶區。

  47. 15.4.4.4 設定臨時性DMA對應(3/3) • 偶爾,驅動程式需要在解除對應之前,先存取臨時 DMA 緩衝區的內容,可用以下函式: • void dma_sync_single_for_cpu(struct device *dev, dma_handle_tbus_addr, size_tsize, enumdma_data_direction direction); • 此函式的呼叫時機,必須在處理器存取 DMA_FROM_DEVICE 緩衝區之前。一旦此函式 返回,CPU就全權擁有 DMA 緩衝區了。 • 在裝置存取 DMA 緩衝區之前,必須將所有權轉移回去: void dma_sync_single_for_device(structdevice *dev, dma_handle_tbus_addr, size_t size, enumdma_data_direction direction); 呼叫了 void dma_sync_single_for_device() 函式之後,CPU就不應該碰觸 DMA 緩衝區了。

  48. 15.4.4.5 臨時性的單頁對應 • 偶爾,可能會想要將 DMA 緩衝區映射到一個已有其 struct page 指標的記憶頁;比如當想要讓裝置直接將資料傳輸到一個用 get_user_page () 所取得的 user-space 暫存區時,就會有這樣的需要。 • 要讓 DMA 緩衝區暫時映射到 struct page 指標所指的記憶頁,可用下列函式: dma_addr_tdma_map_page(structdevice *dev, struct page *page, unsigned long offset, size_t size, enumdma_data_direction direction); • 下列函式可解除對應關係: void dma_unmap_page(structdevice *dev, dma_addr_tdma_address, size_t size, enumdma_data_direction direction); • offset 與 size 引數可用來縮限映射範圍,不過應該盡量避免不滿一頁的映射。當映射範圍不滿一個記憶頁時,而配置範圍只包含部份的快取線,就有可能導致快取失調的問題,進而導致記憶錯亂。

  49. Demo(1/8)

  50. Demo(2/8)

More Related