230 likes | 381 Views
Chapter 15. MMAP 與 DMA. 15.1 Linux 的記憶體管理. 主要是描述用於控管記憶體的各種資料結構 , 相當冗長 . 有了必要的基礎知識後 , 我們就可以開始使用這些結構. 15.1.1 位址的分類 (1/4). 作業系統的分類上 ,Linux 是一種虛擬記憶系統 . 虛擬記憶系統將 邏輯世界 ( 軟體 ) 與 現實世界 ( 硬體 ) 分隔開來 , 最大的好處是軟體可配置的空間超過 RAM 的實際容量 . 另一項優點是核心可在執行期改變行程的部分記憶空間 .
E N D
Chapter 15 MMAP與DMA
15.1 Linux的記憶體管理 • 主要是描述用於控管記憶體的各種資料結構,相當冗長.有了必要的基礎知識後,我們就可以開始使用這些結構.
15.1.1 位址的分類(1/4) • 作業系統的分類上,Linux是一種虛擬記憶系統. • 虛擬記憶系統將邏輯世界(軟體)與現實世界(硬體)分隔開來,最大的好處是軟體可配置的空間超過RAM的實際容量. • 另一項優點是核心可在執行期改變行程的部分記憶空間. • Linux系統上不只有兩種位址(虛擬、實體),而且每種位址都有其特殊用途. 但核心原始程式裡沒有明確定義何種位址適用何種情況,所以必須相當謹慎小心.
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()配置而來的記憶體位址是以虛擬位址來表示。
15.1.1 位址的分類(4/4) • <asm/page.h>定義了兩個可換算位址的巨集. • 如果你有一個邏輯位址,__pa()巨集可換算出其對應的實體位址 • __va()可將實體位址換算回邏輯位址,但僅限於低記憶體的實體位址才有效,因為高記憶體沒有邏輯位址
15.1.2 高低記憶體 • 核心邏輯位址與核心虛擬位址之間的差異,再配備超大量記憶體的32-bits系統上才凸顯出來. • 低記憶體(Low memory) • 存在kernel-space裡,可用邏輯位址來定位的記憶體 • 高記憶體(High memory) • 沒有邏輯位址的記憶體,因為系統上安裝超過32-bits定址範圍的實體記憶體. • 高低記憶體之間的分界線 核心在開機期間依據BIOS提供的資訊來決定的.在i386系統,分界通常位於1GB以下.這是核心自己設下的限制,因為核心必須將32-bit位址空間劃分成kernel-space與user-space兩大部份.
15.1.3 記憶體對應表與structpage(1/2) • 由於高記憶體沒有邏輯位址,處理記憶體的核心函式,紛紛改用struct page來代替邏輯位址. • page結構紀錄了關於實體記憶頁的一切資訊.系統上的每一頁記憶體,都有一個專屬的struct page,幾個重要欄位如下. • atomic_t count; • 此記憶頁的用量計次.當count降為0時,記憶頁會被釋放回自由串列. • void *virtual; • 本記憶頁對應的核心虛擬位址;若無對應的虛擬位址則指向NULL. • unsigned long flags; • 一組描述記憶頁狀態的位元旗標.如PG_locked、PG_reserved.
15.1.3 記憶體對應表與structpage(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()所建立的特殊對應解除
15.1.4 虛擬記憶區(Virtual Memory Areas)(1/6) • 核心需要一個較高層級的機制,才能處理行程所見到的記憶體佈局.在Linux,這機制稱為虛擬記憶區(virtual memory areas),通常簡稱為區域或VMA. • 用來管理使用者行程的虛擬位址空間的各個區域 • 行程空間的虛擬位址空間,由下列區域構成: • 一個存放程式碼(executable binary)的區域,通常稱為text • 多個存放資料的區域 • 每一個有效的記憶體對映(memory mapping),各有一個區域.
15.1.4 虛擬記憶區(Virtual Memory Areas)(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
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結構,放在要求映射裝置的行程的位址空間裡
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結構.
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; • 供驅動程式用於儲存私有資訊的欄位.
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.2 mmap作業方法(1/2) • 就驅動程式的觀點而言,記憶映射可用來提供直接存取裝置記憶體的能力給user-space應用程式. • 觀察X Window System server的VMA如何映射到/dev/mem,有助於理解mmap()系統呼叫的典型用法. • 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:位於系統記憶體的頂端,直接對應到顯卡上的視訊記憶體 • 從/proc/iomem也可看出顯示卡上的各段I/O記憶體
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()函式一次搞定.
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找到的值.
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; • }
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, • };
15.3直接I/O • 大部分的I/O作業,透過kernel-space的緩衝區做緩衝,隔離了user-space與實際裝置。好處:讓程式比較好寫,提升效能。 • 但在傳輸大量資料時,讓user-space直接與裝置I/O做溝通反而會比較好。
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串列 • 此函式的回傳值:實際映射的記憶頁數量。
15.3.2釋放記憶頁 • 完成直接I/O作業→釋放user-space記憶頁 • 1.檢查記憶頁是否為保留頁:PageReserved(); • 非絕對必要:user-space記憶體都不是保留頁 • 2.若改變了記憶頁內容→使用SetPageDirty(struct page *page)將記憶頁做標記,否則核心會直接釋放該記憶頁,而不寫回儲存裝置 • 3.將page從page cache釋放→page_cache_release(struct page *page); • 無論是否有改變記憶頁內容,最後都應做釋放的動作!
15.3.3非同步I/O • 非同步I/O讓user-space可不必等到I/O作業完成,就發起其他I/O作業。