mmap 與 dma

56
MMAP 與 DMA 601430026 許許許 Chapter 15

Upload: rooney-lucas

Post on 01-Jan-2016

66 views

Category:

Documents


4 download

DESCRIPTION

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

TRANSCRIPT

Page 1: MMAP 與 DMA

MMAP 與 DMA

601430026 許名宏

Chapter 15

Page 2: MMAP 與 DMA

2

15.1 Linux 的記憶體管理 主要是描述用於控管記憶體的各種資料結構,相

當冗長。有了必要的基礎知識後,我們就可以開始使用這些結構。

Page 3: MMAP 與 DMA

3

15.1.1 位址的分類 (1/4) 作業系統的分類上, Linux 是一種虛擬記憶系統。 虛擬記憶系統將邏輯世界 ( 軟體 ) 與現實世界 ( 硬體 ) 分隔

開來,最大的好處是軟體可配置的空間超過 RAM 的實際容量。

另一項優點是核心可在執行期間改變行程的部分記憶空間。 Linux 系統上不只有兩種位址 ( 虛擬、實體 ) ,而且每種位

址都有其特殊用途。但核心原始程式裡沒有明確定義何種位址適用何種情況,所以必須相當謹慎小心。

Page 4: MMAP 與 DMA

4

15.1.1 位址的分類 (2/4)

Page 5: MMAP 與 DMA

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() 配置而來得記憶體位址是以虛擬位址來表示。

Page 6: MMAP 與 DMA

6

15.1.1 位址的分類 (4/4) <asm/page.h> 定義了兩個可換算位址的巨集。 如果你有一個邏輯位址, _ _pa() 巨集可換算出其對應的

實體位址。 _ _va() 可將實體位址換算回邏輯位址,但僅限於低記憶體

的實體位址才有效,因為高記憶體沒有邏輯位址。

Page 7: MMAP 與 DMA

7

15.1.2 高低記憶體 核心邏輯位址與核心虛擬位址之間的差異,在配備超大量

記憶體的 32-bits 系統上才凸顯出來。 低記憶體 (Low memory) 存在於 kernel-space 裡,具有邏輯位址的記憶體。 高記憶體 (High memory)

沒有邏輯位址的記憶體,因為超過了核心的虛擬位址空間。 高低記憶體之間的分界線

核心在開機期間依據 BIOS 提供的資訊來決定的。在 i386系統,分界通常位於 1GB 的位置。這是核心自己設下的限制,因為核心必須將 32-bit 位址空間劃分成 kernel-space 與user-space 兩大部份。

Page 8: MMAP 與 DMA

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( 是否受記憶體管理系統的管轄 ) 。

Page 9: MMAP 與 DMA

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() 所建立的特殊對應解除。

Page 10: MMAP 與 DMA

10

15.1.4 虛擬記憶體分區 (1/6) 核心需要一個較高層級的機制,才能處理行程所見到的

記憶體佈局。在 Linux ,這機制稱為虛擬記憶體分區(virtual memory areas) ,通常簡稱為分區或VMA 。

用來管理使用者行程的虛擬位址空間的各個區域。 一個行程的虛擬位址空間,至少含有下列幾個 VMA:• 一個存放程式碼 (executable binary) 的區域,

通常稱為 text 。• 多個資料分區,包括有初值的資料、沒初值的資料,

以及程式堆疊。• 每一個有效的記憶體對映 (memory mapping) ,

各有一個分區。

Page 11: MMAP 與 DMA

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 vsyscall page

各欄位的格式如下:start-end|perm|offset|major:minor|inode|imagename

Page 12: MMAP 與 DMA

12

15.1.4 虛擬記憶體分區 (3/6) 上面每一欄除了 imagename 之外,都分別對應到 struct

vm_area_struct 裡的欄位,這些欄位意義如下 :• start 、 end VMA前後邊界的虛擬位址• perm VMA 的存取位元遮罩 (r 、 w 、 x 、 p/s)• offset VMA 所映射的檔案內容之起點• major:minor 持有映射檔的裝置的主、次編號• inode 被映射檔案的 inode編號• imagename 被映射檔案 ( 通常是可執行檔 ) 的名稱

mmap() 是 Unix 系統的一個重要的系統呼叫,其作用是將裝置記憶體映射到 user-space 行程的虛擬記憶空間。

Page 13: MMAP 與 DMA

13

15.1.4 虛擬記憶體分區 (4/6) Linux 核心是以 struct vm_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 結構。

Page 14: MMAP 與 DMA

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 置換到磁碟上。

• struct vm_operations_struct *vm_ops; 一組可供核心用來操作此 VMA 的函式。這函式指標的存在,表

示核心將 VMA 當成一種物件來看待。• void *vm_private_data; 供驅動程式用於儲存私有資訊的欄位。

Page 15: MMAP 與 DMA

15

15.1.4 虛擬記憶體分區 (6/6)• void (*open)(struct vm_area_struct *vma) ;• 核心會呼叫 open 作業方法,讓實作 VMA 的子系統有機會初

始 VMA 、調整用量計次 ...等等。• void (*close)(struct vm_area_struct *vma) ;• 當 VMA 被摧毀,核心會呼叫它的 close 作業方法。• struct page *(*nopage)(struct vm_area_struct *vma, unsigned

long address, int type) ;• 行程試圖讀取某個 VMA 記憶頁,但該記憶頁目前不在主記憶

體裡,則會執行 VMA 的 nopage 作業方法。 nopage 作業方法通常會從磁碟上的交換區讀回記憶頁的內容,然後傳回一個指向實體記憶頁的 struct page 的指標。若果 VMA 沒定義它自己的 nopage 作業方法,則核心會配置一個空的記憶頁。

Page 16: MMAP 與 DMA

16

remap_pfn_range() 與 nopage 作法圖示 ︰

Page 17: MMAP 與 DMA

17

15.2 mmap 作業方法 (1/2) 就驅動程式的觀點而言,記憶體映射可用來提供直接存取裝置記憶體的能力給 user-space 應用程式。

mmap() 最經典的應用,就是 X Window System server 利用它來存取顯示卡的視訊記憶體。以下是 X server 行程的記憶體對應表 :

cat /proc/731/maps000a0000-000c0000 rwxs 000a0000 03:01 282652 /dev/mem000f0000-00100000 r-xs 000f0000 03:01 282652 /dev/mem00400000-005c0000 r-xp 00000000 03:01 1366927 /usr/X11R6/bin/Xorg006bf000-006f7000 rw-p 001bf000 03:01 1366927 /usr/X11R6/bin/Xorg2a95828000-2a958a8000 rw-s fcc00000 03:01 282652 /dev/mem2a958a8000-2a9d8a8000 rw-s e8000000 03:01 282652 /dev/mem

a0000:VGA卡的視訊記憶體的標準位置 e8000000:位於系統記憶體的頂端,直接對應到顯卡上的視訊記憶體

Page 18: MMAP 與 DMA

18

15.2 mmap 作業方法 (2/2) 由於 X server 時常需要傳輸大量資料到視訊記憶體,如果使用傳統的

lseek() 、 write() ,勢必引發相當頻繁的 context switch ,而傳輸效率當然就很差勁;然而如果將視訊記憶體直接映射到 user-space ,則應用程式可以直接填寫視訊記憶體,所以傳輸效率得以大幅提升。

mmap 作業方法屬於 file_operations 結構的一部份,由 mmap() 系統呼叫觸發。• mmap (caddr_t addr, size_t length, int prot, int flags, int fd, off_t

offset) ; / 此為 mmap() 系統呼叫的宣告形式 /

• int (*mmap)(struct file *filp, struct vm_area_struct *vma) ; /mmap 作業方法宣告方式 /

有兩種方法可以製作分頁表 :全部交給 remap_pfn_range() 函式一次搞定。或者透過 VMA 的 nopage 作業方法,在 VMA 被存取時,才一次處理一頁。

Page 19: MMAP 與 DMA

19

15.2.1 使用 remap_pfn_range() 要將一段虛擬位址映射到一段實體位址,必須另外產生新的

分頁表,這個任務就交給它來完成。 int remap_pfn_range(struct vm_area_struct *vma,

unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot) ;

映射成功,則傳回 0 ,否則傳回一個負值錯誤碼。• vma 記憶頁所要映射到的 VMA 。• virt_addr 實體位址所要映射到的虛擬位址範圍之起點。• pfn 虛擬位址所要映射到的實體位址的頁框編號。• size 映射區的規模 ( 以 byte 為計算單位 ) 。• prot 新 VMA 的保護方式。驅動程式能使用 vma->vm_page_port 所提供的值。

Page 20: MMAP 與 DMA

20

15.2.2 mmap 實例簡單、線性的映射作法,讓應用程式可透過 user-space 的某段虛擬位址來存取裝置記憶體 static int simple_remap_mmap(struct file *filp, struct vm_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; }

Page 21: MMAP 與 DMA

21

15.2.3 增添新的 VMA 作業方法 void simple_vma_open(struct vm_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(struct vm_area_struct *vma)

{

printk(KERN_NOTICE "Simple VMA close.\n");

}

static struct vm_operations_struct simple_remap_vm_ops = {

.open = simple_vma_open,

.close = simple_vma_close,

};

Page 22: MMAP 與 DMA

22

15.2.4 使用 nopage映射記憶體 (1/3) 雖然 remap_page_range()已足以應付許多驅動程式的 mmap ,但偶爾會需要多一點彈性。對於這類情況, VMA 的 nopage 作業方法或許是可以考慮的選擇。

適合使用 nopage 作業方法來映射位址空間的情況,是當驅動程式只需在 VMA發生變化才會有處理動作時。應用程式可透過mremap() 系統呼叫來改變 VMA 的邊界或大小。

發生mremap() 系統呼叫時,核心不一定會通知驅動程式;假如改變的結果是 VMA範圍縮減,核心可以默默清理掉多出來的記憶頁,而不必通知驅動程式;但如果是範圍擴張,核心才會呼叫該VMA 的 nopage 作業方法來配置新的記憶頁。

之所以不讓驅動程式收到映射區擴張通知,是因為記憶體被實際應用之前,沒有處理的必要,而當真的有必要時,核心可觸發 nopage 來處理。

Page 23: MMAP 與 DMA

23

15.2.4 使用 nopage映射記憶體 (2/3)struct page *simple_vma_nopage(struct vm_area_struct *vma, unsigned long address,

int *type){ struct page *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; }

Page 24: MMAP 與 DMA

24

15.2.4 使用 nopage映射記憶體 (3/3)static int simple_nopage_mmap(struct file *filp, struct vm_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;

}

Page 25: MMAP 與 DMA

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);

Page 26: MMAP 與 DMA

26

15.2.5 重新映射特定 I/O區 ( 2/2) 要避免映射範圍擴張,最簡單的辦法是實作一個

簡單的 nopage 作業方法,讓它回覆一個 SIGBUS信號給發生失誤的行程。例如 :

struct page *simple_nopage(struct vm_area_struct *vma, unsigned long address, int *type)

{ return NOPAGE_SIGBUS; /* 傳回一個 SIGBUS

*/ }

Page 27: MMAP 與 DMA

27

15.2.6 重新映射 RAM 如果要適度容許映射擴張,比較完善的做法,是檢查引發

分頁失誤的位址,是否在有效的實體範圍內,如果是,才容許映射。

remap_page_range() : 只有保留頁,以及在實體記憶體(RAM)頂端之上的實體位址,它才有作用。保留頁被鎖在記憶體裡 ( 不會被換出到磁碟上 ) ,所以可以安全地映射到 user-space; 這項限制式系統穩定度的基本要求。

由於 remap_page_range() 沒有處理 RAM 的能力,這表示類似 scullp那樣的裝置將難以作出自己的 mmap ,因為其裝置記憶體是一般的 RAM 而非 I/O memory 。幸好,還是可以使用 nopage 作業方法。

Page 28: MMAP 與 DMA

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 ,而且還必須存取記憶對應表,調整記憶頁的用量計次。

Page 29: MMAP 與 DMA

29

15.2.6.1 使用 nopage 重新映射 RAM(2/3) int scullp_mmap(struct file *filp, struct vm_area_struct *vma) { struct inode *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; }

Page 30: MMAP 與 DMA

30

15.2.6.1 使用 nopage 重新映射 RAM(3/3) void scullp_vma_open(struct vm_area_struct *vma)

{

struct scullp_dev *dev = vma->vm_private_data;

dev->vmas++;

}

void scullp_vma_close(struct vm_area_struct *vma)

{

struct scullp_dev *dev = vma->vm_private_data;

dev->vmas--;

}

Page 31: MMAP 與 DMA

31

15.3 直接 I/O 大部份的 I/O 作業,透過 kernel-space 的緩衝區做緩衝,某種程度上隔離了 user-space 與實際裝置。好處:讓程式比較好寫,提升效能。

但在傳輸大量資料時,讓 user-space 直接與裝置I/O做溝通反而會比較好。

Page 32: MMAP 與 DMA

32

15.3.1 直接 I/O 的關鍵函式 在2.6版核心實作直接 I/O的關鍵函式是 get_user_pages()

int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,

unsigned long start, int len, int write, int force, struct page **pages,

struct vm_area_struct **vmas); struct task_struct *tsk :此指標指向要執行 I/O的 task_struct。 struct mm_struct *mm:此指標所指的mm_struct結構,是行程的虛擬位址空間的所有

VMA。 unsigned long start:user-space緩衝區的起始位址。 int len:該緩衝區的長度,以頁為計算單位。 int write:若不是零,則要映射的記憶頁是供write存取之用。 int force:要求 get_user_pages() 不理會指定記憶頁的保護旗標,對驅動程式應設為零。 struct page **pages:page應該指向一個描述user-space緩衝區的struct page串列。 struct vm_area_struct **vmas :vmas應含有相關的VMA的指標。 get_user_pages() 的回傳值:是實際映射的記憶頁數量,此數量有可能少於要求的數量,但 至少會大於零。

Page 33: MMAP 與 DMA

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(struct page *page) ;

Page 34: MMAP 與 DMA

34

15.3.3非同步 I/O 非同步 I/O 讓 user-space 可發動 I/O 作業要求,而

不必等待 I/O 作業完成,也就是說在 I/O 進行過程中,應用程式仍可以做其它事。對於複雜的高效率應用程式,非同步 I/O 可讓它們同時進行多項工作。

核心沒強制要求驅動程式必須提供非同步 I/O 的能力,也只有非常少數的驅動程式設計者需要考慮這項能力,因為這通常無助於裝置的傳輸效率。

Page 35: MMAP 與 DMA

35

15.4 直接記憶體存取 (DMA) DMA 是一種硬體機制,讓周邊元件可以直接與主

記憶體交換 I/O 資料,而不必經過系統處理器。 由於 DMA 是“硬體”機制,其設定程序完全隨系

統架構而定。 DMA 機制可大幅提昇周邊裝置的資料吞吐量,同

時減輕 CPU 的運算負擔。

Page 36: MMAP 與 DMA

36

15.4.1 DMA 資料傳輸的流程 (1/3) 有兩種機會可觸發 DMA 資料傳輸 : 軟體主動要求,或周邊硬體主動將資料推入系統。

第一種情況(軟體觸發)所涉及的步驟 : 1. 當行程發出一次 read() ,驅動程式的 read

作業方法就配置一塊 DMA緩衝區,並指示周邊硬體開始傳輸資料。行程會進入休眠狀態。

2. 周邊硬體將資料寫到 DMA緩衝區,在完成傳輸之後,對 CPU發出一次中斷訊號。

3.ISR( 中斷服務程式 )收下輸入資料、回應中斷、然後喚醒行程,讓行程讀走資料。

Page 37: MMAP 與 DMA

37

15.4.1 DMA 資料傳輸的流程 (2/3) 第二種情況,發生在 DMA 被當成非同步傳輸機制使用 時的步驟 :

1. 周邊硬體觸發一次中斷,讓系統處理器知道新資料 已經到達。 2.ISR 配置一個緩衝區,並將該緩衝區的位置告訴周邊 硬體,使其知道資料應該傳送到何處。 3. 周邊硬體將資料寫入指定的緩衝區,在完成傳輸之 後,觸發另一次中斷。 4.ISR 將新資料存放在適當位置,喚醒任何相關行程, 並處理一些例行工作。

Page 38: MMAP 與 DMA

38

15.4.1 DMA 資料傳輸的流程 (3/3)網路卡與 CPU 之間通常是透過主記憶體上的一塊環型緩衝區 ( 稱為 DMA ring buffer)互相交換資料。

當網路卡從外界收到一個封包,就將它放入環型緩衝區裡的下一個空位,然後發出中斷通知。

驅動程式將網路封包傳給核心裡的其它部門,並將一個新的 DMA 空位放回環型緩衝區。

大多數驅動程式在初始期就預先配置好所需的緩衝區,並全程使用同一塊緩衝區,直到關閉時才予以釋放。

Page 39: MMAP 與 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緩衝空間;不過這應該被視為最後手段,除非其它所有辦法都無效,因為積極配置會導致沉重的系統負載,甚至可能鎖死系統。

Page 40: MMAP 與 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/O MMU 的平台就無能為力了。

Page 41: MMAP 與 DMA

41

15.4.4 DMA抽象層 從驅動程式的觀點來看, DMA終究不過是配置一個緩衝區,並傳遞匯流排位置給裝置。

對於快取而言,如何保持快取的一致性,各家系統各有自己獨到的邏輯,若你的驅動程式沒能正確處理這方面的問題,可能會造成系統記憶錯亂。

並非所有系統的每一塊記憶體都可以配合 DMA 作業。例如 : x86 平台的高記憶體就不能當成 DMA緩衝區。

核心提供了一個 DMA抽象層,可跨越各種不同平台與匯流排之間的差異。

Page 42: MMAP 與 DMA

42

15.4.4.1 排除不支援的硬體 嘗試進行 DMA 作業之前,第一個要回答的問題是當時的平台是否支援目標裝置所需的 DMA能力。

原則上核心假設裝置可在任何 32-bit 位址執行 DMA 作業,如果裝置不合這項假設,可以下列方式通知核心 :

int dma_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”) ; }

Page 43: MMAP 與 DMA

43

15.4.4.2 DMA 對應 配置一個 DMA緩衝區,並為該緩衝區產生一個可供裝置存取的位址,這兩個動作的組合就稱為 DMA 對應。

在 PCI 匯流排上的 DMA 對應被分成兩種類型,主要差別在於 DMA緩衝區的存活時間長短。這兩種對應模式如下 :

常態性 DMA 對應 (coherent DMA mapping)

若 DMA緩衝區的生命期與驅動程式一樣長,就稱為常態性 DMA 對應。 DMA緩衝區必須能夠同時被 CPU 與周邊使用,甚至應該被排除在快取機制之外,以免一方看不見另一方的更新。

臨時性 DMA 對應 (streaming DMA mapping)

為了單次 DMA 作業而臨時設置的 DMA 對應;這比較有彈性但必須遵守一些限制;建議盡可能使用臨時性對應基於兩項理由。首先,在配置對應暫存器的系統上,每組 DMA 對應都需要用掉一或多個暫存器,常態性對應會長期佔用這些暫存器;其次,有不少硬體平台特地針對臨時性對應做了最佳化,這些最佳化措施不能運行在常態性對應。

Page 44: MMAP 與 DMA

44

15.4.4.3 設定常態性 DMA 對應 驅動程式可呼叫 dma_alloc_coherent() 來設定一組常態性的 DMA 對應,

此函式包辦了緩衝區的配置與對應工作。void *dma_alloc_coherent(struct device *dev, size_t size, 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_t dma_handle) ;

Page 45: MMAP 與 DMA

45

15.4.4.4 設定臨時性 DMA 對應 (1/3) 臨時性 DMA 對應的軟體介面稍微複雜些,因為驅動程式自己得事先配置好緩衝區,並處

理它們所沒選擇的位址。在某些平台上,臨時性對應甚至接受多個不連續的記憶頁。 設定臨性對應時,必須讓核心知道資料的移動方向,方向是以 enum dma_data_direction 型

別的符號來描述,如下 :

DMA_TO_DEVICE 如果是要將資料傳送到裝置上 (為了回應 write() 系統呼叫 )。 DMA_FROM_DEVICE 如果是要將資料傳送到裝置上 (為了回應 read() 系統呼叫 )。 DMA_BIDIRECTIONAL 表示如果容許資料雙向移動。 DMA_NONE 供除錯用途。 當你只有一個緩衝區要傳輸,可使用 dma_map_single() 來將該緩衝區映射到裝置定址空間。 dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum

dma_data_direction direction) ; 傳回值是可以傳給目標裝置的匯流排位址。如果映射失敗,則會傳回 NULL。 完成傳輸之後,應該立刻使用 pci_unmap_single() 來解除對應。 void pci_unmap_single(struct pci_dev *pdev, dma_addr_t bus_addr, size_t size, int direction) ; 這裡的 size 和 direction 引數,必須符合當初映射緩衝區時所用的引數值。

Page 46: MMAP 與 DMA

46

15.4.4.4 設定臨時性 DMA 對應 (2/3) 臨時性 DMA 對應必須遵守三點重要法則 :

緩衝區的使用,必須符合映射時所設定的傳輸方向。 在緩衝區映射到匯流排位址之後,就屬於裝置,而非處理器。在解除對應之前,驅動程式不能以任何方式接觸緩衝區。只有在呼叫 dma_unmap_single() 之後,驅動程式才能安全存取緩衝區的內容。這意味著你必須先將要寫入裝置的資料放在緩衝區,然後才能映射它。

在 DMA動作期間,不能解除對應,否則保證系統一定會嚴重錯亂。 為何驅動程式不能接觸已被對應的緩衝區?有兩項原因,第一,如

果要輸出資料到裝置上,核心必須確保要放在 DMA緩衝區的資料,已經確實全數寫入記憶體;第二,如果被映射的緩衝區位於周邊裝置無法存取的區域時,某些平台會直接讓DMA 作業失敗,而其它平台則可能會建立一個轉進緩衝區 (bounce buffer) ,轉進緩衝區只是另一塊裝置可以存取的記憶區。

Page 47: MMAP 與 DMA

47

15.4.4.4 設定臨時性 DMA 對應 (3/3) 偶爾,驅動程式需要在解除對應之前,先存取臨時 DMA 緩衝區的內容,可用以下函式 :

void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction) ;

此函式的呼叫時機,必須在處理器存取 DMA_FROM_DEVICE 緩衝區之前。一旦此函式 返回, CPU 就全權擁有 DMA 緩衝區了。 在裝置存取 DMA 緩衝區之前,必須將所有權轉移回去 :

void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction) ;呼叫了 void dma_sync_single_for_device() 函式之後, CPU 就不應該碰觸 DMA 緩衝區了。

Page 48: MMAP 與 DMA

48

15.4.4.5 臨時性的單頁對應 偶爾,可能會想要將 DMA 緩衝區映射到一個已有其 struct page 指標

的記憶頁;比如當想要讓裝置直接將資料傳輸到一個用 get_user_page () 所取得的 user-space 暫存區時,就會有這樣的需要。

要讓 DMA 緩衝區暫時映射到 struct page 指標所指的記憶頁,可用下列函式 :

dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction direction) ;

下列函式可解除對應關係 :

void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction) ;

offset 與 size 引數可用來縮限映射範圍,不過應該盡量避免不滿一頁的映射。當映射範圍不滿一個記憶頁時,而配置範圍只包含部份的快取線,就有可能導致快取失調的問題,進而導致記憶錯亂。

Page 49: MMAP 與 DMA

49

Demo(1/8)

Page 50: MMAP 與 DMA

50

Demo(2/8)

Page 51: MMAP 與 DMA

51

Demo(3/8)

Page 52: MMAP 與 DMA

52

Demo(4/8)

Page 53: MMAP 與 DMA

53

Demo(5/8)

Page 54: MMAP 與 DMA

54

Demo(6/8)

Page 55: MMAP 與 DMA

55

Demo(7/8)

Page 56: MMAP 與 DMA

56

Demo(8/8)