Download presentation
Presentation is loading. Please wait.
1
Chapter 15 MMAP與DMA
2
15.1 Linux的記憶體管理 主要是描述用於控管記憶體的各種資料結構,相當冗長.有了必要的基礎知識後,我們就可以開始使用這些結構.
3
15.1.1 位址的分類(1/4) 作業系統的分類上,Linux是一種虛擬記憶系統.
虛擬記憶系統將邏輯世界(軟體)與現實世界(硬體)分隔開來,最大的好處是軟體可配置的空間超過RAM的實際容量. 另一項優點是核心可在執行期改變行程的部分記憶空間. Linux系統上不只有兩種位址(虛擬、實體),而且每種位址都有其特殊用途. 但核心原始程式裡沒有明確定義何種位址適用何種情況,所以必須相當謹慎小心.
4
15.1.1 位址的分類(3/4) 使用者虛擬位址(User Virtual Address) 簡稱為虛擬位址,位址寬度隨CPU架構而定
實體位址(Physical Address) CPU與記憶體間的位址,寬度依CPU而定,但不一定與CPU暫存器相符 匯流排位址(Bus Address) 週邊匯流排與記憶體的位址,具有高度的平台相依性 核心邏輯位址(Kernel Logical Address) 構成核心的正常位址空間,他們對應到所有主記憶體,而且通常被當作實體位址來使用。邏輯位址與實體位址只差距一段固定偏移量,通常存放在unsigned long或void *型別變數上。kmalloc()所傳回的記憶體,就是以邏輯位址來定位。 核心虛擬位址(Kernel Virtual Address) 核心虛擬位址跟邏輯位址不同之處,在於核心虛擬位址與實體位址不一定有直接對應關係,虛擬位址通常存放在指標變數中。vmalloc()配置而來的記憶體位址是以虛擬位址來表示。
5
15.1.1 位址的分類(4/4) <asm/page.h>定義了兩個可換算位址的巨集.
__va()可將實體位址換算回邏輯位址,但僅限於低記憶體的實體位址才有效,因為高記憶體沒有邏輯位址
6
15.1.2 高低記憶體 核心邏輯位址與核心虛擬位址之間的差異,再配備超大量記憶體的32-bits系統上才凸顯出來.
低記憶體(Low memory) 存在kernel-space裡,可用邏輯位址來定位的記憶體 高記憶體(High memory) 沒有邏輯位址的記憶體,因為系統上安裝超過32-bits定址範圍的實體記憶體. 高低記憶體之間的分界線 核心在開機期間依據BIOS提供的資訊來決定的.在i386系統,分界通常位於1GB以下.這是核心自己設下的限制,因為核心必須將32-bit位址空間劃分成kernel-space與user-space兩大部份.
7
15.1.3 記憶體對應表與struct page(1/2)
page結構紀錄了關於實體記憶頁的一切資訊.系統上的每一頁記憶體,都有一個專屬的struct page,幾個重要欄位如下. atomic_t count; 此記憶頁的用量計次.當count降為0時,記憶頁會被釋放回自由串列. void *virtual; 本記憶頁對應的核心虛擬位址;若無對應的虛擬位址則指向NULL. unsigned long flags; 一組描述記憶頁狀態的位元旗標.如PG_locked、PG_reserved. /usr/src/kernels/linux /include/linux/mm.h
8
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在高記憶體→將他映射到特殊的虛擬空間 void kunmap(struct page *page); 將kmap()所建立的特殊對應解除 /usr/src/kernels/linux /include/linux/highmem.h
9
15.1.4 虛擬記憶區(Virtual Memory Areas)(1/6)
核心需要一個較高層級的機制,才能處理行程所見到的記憶體佈局.在Linux,這機制稱為虛擬記憶區(virtual memory areas),通常簡稱為區域或VMA. 用來管理使用者行程的虛擬位址空間的各個區域 行程空間的虛擬位址空間,由下列區域構成: 一個存放程式碼(executable binary)的區域,通常稱為text 多個存放資料的區域 每一個有效的記憶體對映(memory mapping),各有一個區域.
10
15.1.4 虛擬記憶區(Virtual Memory Areas)(2/6)
例:以下是init行程VMA的分布情形。cat /proc/1/maps e000 r-xp : /sbin/init text 0804e f000 rw-p : /sbin/init data 0804f rwxp : zero-mapped BSS r-xp : /lib/ld so text rw-p : /lib/ld so data rw-p : BSS for ld.so e000 r-xp : /lib/tls/libc so text 4212e rw-p 0012e000 03: /lib/tls/libc so data rw-p : BSS for libc bffff000-c rwxp : Stack segment ffffe000-fffff p : vsyscall page 各欄位的格式如下: start-end|perm|offset|major:minor|inode|imagename
11
15.1.4 虛擬記憶區(Virtual Memory Areas)(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作業方法的驅動程式,必須填寫一個VMA結構,放在要求映射裝置的行程的位址空間裡
12
15.1.4 虛擬記憶區(Virtual Memory Areas)(4/6)
我們看看struct vm_area_struct(定義在<linux/mm.h>)裡幾個最重要的欄位(很相似/proc/*/maps),因為驅動程式的mmap作業方法可能會需要用到這些欄位. 驅動程式不能任意建立新的VMA,否則會破壞整個組織(串列與樹狀). unsigned long vm_start; unsigned long vm_end; 此VMA涵蓋的虛擬位址範圍. struct file *vm_file; 如果VMA的映射對象是檔案,則vm_file指向該檔案的struct file結構.
13
14.1.4 虛擬記憶區(Virtual Memory Areas)(5/6)
unsigned long vm_pgoff; 此區域在檔案的相對位置(以page為單位). unsigned long vm_flags; 一組描述VMA屬性的旗標.VM_IO表示此VMA映射到I/O region, VM_RESERVED要求記憶體管理系統不要將此VMA交換到磁碟上. struct vm_operations_struct *vm_ops; 一組可供核心用來操作VMA的函式. void *vm_private_data; 供驅動程式用於儲存私有資訊的欄位.
14
15.1.4 虛擬記憶區(Virtual Memory Areas)(6/6)
vm_operations_struct它紀錄了處理行程記憶體所需的三項作業方法:open、close與nopage如下所述. void (*open)(struct vm_area_struct *area); 核心會呼叫open作業方法,讓實作VMA的子系統有機會初始VMA 調整用量計次...等. void (*close)(struct vm_area_struct *area); 當VMA被摧毀,核心會呼叫它的close作業方法. struct page *(*nopage)(struct vm_area_struct *vma,unsigned long address,int type); 行程試圖讀取某個VMA記憶頁,但記憶頁不在主記憶體裡,則VMA的nopage作業方法會被呼叫。nopage從磁碟上的交換區讀回記憶頁內容,然後傳回一個指向實體記憶頁的struct page指標.若VMA沒定義自己的nopage方法,則核心會配置一個空的記憶頁
15
15.2 mmap作業方法(1/2) 就驅動程式的觀點而言,記憶映射可用來提供直接存取裝置記憶體的能力給user-space應用程式.
觀察X Window System server的VMA如何映射到/dev/mem,有助於理解mmap()系統呼叫的典型用法. cat /proc/731/maps 000a c0000 rwxs 000a : /dev/mem 000f r-xs 000f : /dev/mem c0000 r-xp : /usr/X11R6/bin/Xorg 006bf f7000 rw-p 001bf000 03: /usr/X11R6/bin/Xorg 2a a958a8000 rw-s fcc : /dev/mem 2a958a8000-2a9d8a8000 rw-s e : /dev/mem a0000:VGA卡的視訊記憶體的標準位置 e :位於系統記憶體的頂端,直接對應到顯卡上的視訊記憶體 從/proc/iomem也可看出顯示卡上的各段I/O記憶體
16
15.2 mmap作業方法(2/2) 由於X server時常需要傳輸大量資料到視訊記憶體,如果將視訊記憶體直接映射到user-space,則應用程式可以直接填寫視訊記憶體,所以傳輸效率得以大幅提升. mmap作業方法屬於file_operations結構的一部分,由mmap()系統呼叫觸發. void *mmap(void *start,size_t length,int port,int flags,int fd,off_t offset); int (*mmap)(struct filp *filp,struct vm_area_struct *vma); 要支援mmap()系統呼叫的驅動程式,只需對該段虛擬位址範圍建立適當的分頁表 製作分頁表:全部交給remap_page_ranfe()函式一次搞定.
17
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); 映射成功傳回0,失敗傳回錯誤碼 vma 記憶頁所要映射到的VMA virt_addr 要被重新映射的虛擬位址 size 映射區規模(byte為單位) prot 新VMA的保護方式.驅程能使用 vma->vm_page_port找到的值.
18
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; }
19
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, };
20
15.3直接I/O 大部分的I/O作業,透過kernel-space的緩衝區做緩衝,隔離了user-space與實際裝置。好處:讓程式比較好寫,提升效能。 但在傳輸大量資料時,讓user-space直接與裝置I/O做溝通反而會比較好。
21
15.3.1實做直接I/O的關鍵函式 宣告於<linux/mm.h>
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:不理會指定記憶頁的保護旗標,對驅動程式應設為零 struct page **pages:指向user-space緩衝區的struct page串列 此函式的回傳值:實際映射的記憶頁數量。
22
15.3.2釋放記憶頁 完成直接I/O作業→釋放user-space記憶頁 1.檢查記憶頁是否為保留頁:PageReserved();
2.若改變了記憶頁內容→使用SetPageDirty(struct page *page)將記憶頁做標記,否則核心會直接釋放該記憶頁,而不寫回儲存裝置 3.將page從page cache釋放→page_cache_release(struct page *page); 無論是否有改變記憶頁內容,最後都應做釋放的動作! <linux/page-flags.h>:SetPageDirty(struct page *page)
23
15.3.3非同步I/O 非同步I/O讓user-space可不必等到I/O作業完成,就發起其他I/O作業。
Similar presentations