Linux的mmap内存映射机制解析
? ? ?VM是面向?qū)ο蟮姆椒ㄔO(shè)計(jì)的,這里的對象是指內(nèi)存對象:內(nèi)存對象是一個(gè)軟件抽象的概念,它描述內(nèi)存區(qū)與后備存儲之間的映射.系統(tǒng)可以使用多種類型的后備存儲,比如交換空間,本地或者遠(yuǎn)程文件以及幀緩存等等. VM系統(tǒng)對它們統(tǒng)一處理,采用同一操作集操作,比如讀取頁面或者回寫頁面等.每種不同的后備存儲都可以用不同的方法實(shí)現(xiàn)這些操作.這樣,系統(tǒng)定義了一套統(tǒng)一的接口,每種后備存儲給出自己的實(shí)現(xiàn)方法.這樣,進(jìn)程的地址空間就被視為一組映射到不同數(shù)據(jù)對象上的的映射組成.所有的有效地址就是那些映射到數(shù)據(jù)對象上的地址.這些對象為映射它的頁面提供了持久性的后備存儲.映射使得用戶可以直接尋址這些對象.
? ? 值得提出的是, VM體系結(jié)構(gòu)獨(dú)立于Unix系統(tǒng),所有的Unix系統(tǒng)語義,如正文,數(shù)據(jù)及堆棧區(qū)都可以建構(gòu)在基本VM系統(tǒng)之上.同時(shí), VM體系結(jié)構(gòu)也是獨(dú)立于存儲管理的,存儲管理是由操作系統(tǒng)實(shí)施的,如:究竟采取什么樣的對換和請求調(diào)頁算法,究竟是采取分段還是分頁機(jī)制進(jìn)行存儲管理,究竟是如何將虛擬地址轉(zhuǎn)換成為物理地址等等(Linux中是一種叫Three Level Page Table的機(jī)制),這些都與內(nèi)存對象的概念無關(guān).
一、Linux中VM的實(shí)現(xiàn).
? ? ??一個(gè)進(jìn)程應(yīng)該包括一個(gè)mm_struct(memory manage struct),?該結(jié)構(gòu)是進(jìn)程虛擬地址空間的抽象描述,里面包括了進(jìn)程虛擬空間的一些管理信息: start_code, end_code, start_data, end_data, start_brk, end_brk等等信息.另外,也有一個(gè)指向進(jìn)程虛存區(qū)表(vm_area_struct: virtual memory area)的指針,該鏈?zhǔn)前凑仗摂M地址的增長順序排列的.在Linux進(jìn)程的地址空間被分作許多區(qū)(vma),每個(gè)區(qū)(vma)都對應(yīng)虛擬地址空間上一段連續(xù)的區(qū)域, vma是可以被共享和保護(hù)的獨(dú)立實(shí)體,這里的vma就是前面提到的內(nèi)存對象.?
? 下面是vm_area_struct的結(jié)構(gòu),其中,前半部分是公共的,與類型無關(guān)的一些數(shù)據(jù)成員,如:指向mm_struct的指針,地址范圍等等,后半部分則是與類型相關(guān)的成員,其中最重要的是一個(gè)指向vm_operation_struct向量表的指針vm_ops, vm_pos向量表是一組虛函數(shù),定義了與vma類型無關(guān)的接口.每一個(gè)特定的子類,即每種vma類型都必須在向量表中實(shí)現(xiàn)這些操作.這里包括了: open, close, unmap, protect, sync, nopage, wppage, swapout這些操作.?
[cpp] view plain copyvm_ops: open, close, no_page, swapin, swapout……
二、驅(qū)動(dòng)中的mmap()函數(shù)解析
? ? ? ?設(shè)備驅(qū)動(dòng)的mmap實(shí)現(xiàn)主要是將一個(gè)物理設(shè)備的可操作區(qū)域(設(shè)備空間)映射到一個(gè)進(jìn)程的虛擬地址空間。這樣就可以直接采用指針的方式像訪問內(nèi)存的方式訪問設(shè)備。在驅(qū)動(dòng)中的mmap實(shí)現(xiàn)主要是完成一件事,就是實(shí)際物理設(shè)備的操作區(qū)域到進(jìn)程虛擬空間地址的映射過程。同時(shí)也需要保證這段映射的虛擬存儲器區(qū)域不會(huì)被進(jìn)程當(dāng)做一般的空間使用,因此需要添加一系列的保護(hù)方式。
[cpp] view plain copy具體的實(shí)現(xiàn)分析如下:
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
上面的兩個(gè)保護(hù)機(jī)制就說明了被映射的這段區(qū)域具有映射IO的相似性,同時(shí)保證這段區(qū)域不能隨便的換出。就是建立一個(gè)物理頁與虛擬頁之間的關(guān)聯(lián)性。具體原理是虛擬頁和物理頁之間是以頁表的方式關(guān)聯(lián)起來,虛擬內(nèi)存通常大于物理內(nèi)存,在使用過程中虛擬頁通過頁表關(guān)聯(lián)一切對應(yīng)的物理頁,當(dāng)物理頁不夠時(shí),會(huì)選擇性的犧牲一些頁,也就是將物理頁與虛擬頁之間切斷,重現(xiàn)關(guān)聯(lián)其他的虛擬頁,保證物理內(nèi)存夠用。在設(shè)備驅(qū)動(dòng)中應(yīng)該具體的虛擬頁和物理頁之間的關(guān)系應(yīng)該是長期的,應(yīng)該保護(hù)起來,不能隨便被別的虛擬頁所替換。具體也可參看關(guān)于虛擬存儲器的文章。
接下來就是建立物理頁與虛擬頁之間的關(guān)系,即采用函數(shù)remap_pfn_range(),具體的參數(shù)如下:
int remap_pfn_range(structvm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)
1、struct vm_area_struct是一個(gè)虛擬內(nèi)存區(qū)域結(jié)構(gòu)體,表示虛擬存儲器中的一個(gè)內(nèi)存區(qū)域。其中的元素vm_start是指虛擬存儲器中的起始地址。
2、addr也就是虛擬存儲器中的起始地址,通常可以選擇addr = vma->vm_start。
3、pfn是指物理存儲器的具體頁號,通常通過物理地址得到對應(yīng)的物理頁號,具體采用virt_to_phys(dev->data)>>PAGE_SHIFT.首先將虛擬內(nèi)存轉(zhuǎn)換到物理內(nèi)存,然后得到頁號。>>PAGE_SHIFT通常為12,這是因?yàn)槊恳豁摰拇笮偤檬?K,這樣右移12相當(dāng)于除以4096,得到頁號。
4、size區(qū)域大小
5、區(qū)域保護(hù)機(jī)制。
返回值,如果成功返回0,否則正數(shù)。
三、系統(tǒng)調(diào)用mmap函數(shù)解析
? ? ? ? 介紹完VM的基本概念后,我們可以講述mmap和munmap系統(tǒng)調(diào)用了.mmap調(diào)用實(shí)際上就是一個(gè)內(nèi)存對象vma的創(chuàng)建過程,
1、mmap函數(shù)
? ? ? ?Linux提供了內(nèi)存映射函數(shù)mmap,它把文件內(nèi)容映射到一段內(nèi)存上(準(zhǔn)確說是虛擬內(nèi)存上),通過對這段內(nèi)存的讀取和修改,實(shí)現(xiàn)對文件的讀取和修改?。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以向訪問普通內(nèi)存一樣對文件進(jìn)行訪問,不必再調(diào)用read(),write()等操作。
先來看一下mmap的函數(shù)聲明:
[cpp] view plain copymmap的作用是映射文件描述符fd指定文件的 [off,off + len]區(qū)域至調(diào)用進(jìn)程的[addr, addr + len]的內(nèi)存區(qū)域, 如下圖所示:
mmap系統(tǒng)調(diào)用的實(shí)現(xiàn)過程是
1.先通過文件系統(tǒng)定位要映射的文件;
2.權(quán)限檢查,映射的權(quán)限不會(huì)超過文件打開的方式,也就是說如果文件是以只讀方式打開,那么則不允許建立一個(gè)可寫映射;?
3.創(chuàng)建一個(gè)vma對象,并對之進(jìn)行初始化;?
4.調(diào)用映射文件的mmap函數(shù),其主要工作是給vm_ops向量表賦值;
5.把該vma鏈入該進(jìn)程的vma鏈表中,如果可以和前后的vma合并則合并;
6.如果是要求VM_LOCKED(映射區(qū)不被換出)方式映射,則發(fā)出缺頁請求,把映射頁面讀入內(nèi)存中.
2、munmap函數(shù)
? ? ? munmap(void * start, size_t length):
? ? ? 該調(diào)用可以看作是mmap的一個(gè)逆過程.它將進(jìn)程中從start開始length長度的一段區(qū)域的映射關(guān)閉,如果該區(qū)域不是恰好對應(yīng)一個(gè)vma,則有可能會(huì)分割幾個(gè)或幾個(gè)vma.
? ? ? msync(void * start, size_t length, int flags):
? ? ?把映射區(qū)域的修改回寫到后備存儲中.因?yàn)?/span>munmap時(shí)并不保證頁面回寫,如果不調(diào)用msync,那么有可能在munmap后丟失對映射區(qū)的修改.其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE, MS_SYNC要求回寫完成后才返回, MS_ASYNC發(fā)出回寫請求后立即返回, MS_INVALIDATE使用回寫的內(nèi)容更新該文件的其它映射.該系統(tǒng)調(diào)用是通過調(diào)用映射文件的sync函數(shù)來完成工作的.
? ? ?brk(void * end_data_segement):
將進(jìn)程的數(shù)據(jù)段擴(kuò)展到end_data_segement指定的地址,該系統(tǒng)調(diào)用和mmap的實(shí)現(xiàn)方式十分相似,同樣是產(chǎn)生一個(gè)vma,然后指定其屬性.不過在此之前需要做一些合法性檢查,比如該地址是否大于mm->end_code, end_data_segement和mm->brk之間是否還存在其它vma等等.通過brk產(chǎn)生的vma映射的文件為空,這和匿名映射產(chǎn)生的vma相似,關(guān)于匿名映射不做進(jìn)一步介紹.庫函數(shù)malloc就是通過brk實(shí)現(xiàn)的.
四、實(shí)例解析
? ? ? ?下面這個(gè)例子顯示了把文件映射到內(nèi)存的方法,源代碼是:
[cpp] view plain copy編譯運(yùn)行此程序:
gcc -Wall mmap.c
./a.out text_filename
上面的方法因?yàn)橛昧薖ROT_READ,所以只能讀取文件里的內(nèi)容,不能修改,如果換成PROT_WRITE就可以修改文件的內(nèi)容了。又由于 用了MAAP_PRIVATE所以只能此進(jìn)程使用此內(nèi)存區(qū)域,如果換成MAP_SHARED,則可以被其它進(jìn)程訪問,比如下面的
[cpp] view plain copy五、mmap和共享內(nèi)存對比
? ? ? 共享內(nèi)存允許兩個(gè)或多個(gè)進(jìn)程共享一給定的存儲區(qū),因?yàn)閿?shù)據(jù)不需要來回復(fù)制,所以是最快的一種進(jìn)程間通信機(jī)制。共享內(nèi)存可以通過mmap()映射普通文件(特殊情況下還可以采用匿名映射)機(jī)制實(shí)現(xiàn),也可以通過系統(tǒng)V共享內(nèi)存機(jī)制實(shí)現(xiàn)。應(yīng)用接口和原理很簡單,內(nèi)部機(jī)制復(fù)雜。為了實(shí)現(xiàn)更安全通信,往往還與信號燈等同步機(jī)制共同使用。
對比如下:
? ? ? mmap機(jī)制:就是在磁盤上建立一個(gè)文件,每個(gè)進(jìn)程存儲器里面,單獨(dú)開辟一個(gè)空間來進(jìn)行映射。如果多進(jìn)程的話,那么不會(huì)對實(shí)際的物理存儲器(主存)消耗太大。
? ? ? shm機(jī)制:每個(gè)進(jìn)程的共享內(nèi)存都直接映射到實(shí)際物理存儲器里面。
1、mmap保存到實(shí)際硬盤,實(shí)際存儲并沒有反映到主存上。優(yōu)點(diǎn):儲存量可以很大(多于主存);缺點(diǎn):進(jìn)程間讀取和寫入速度要比主存的要慢。
2、shm保存到物理存儲器(主存),實(shí)際的儲存量直接反映到主存上。優(yōu)點(diǎn),進(jìn)程間訪問速度(讀寫)比磁盤要快;缺點(diǎn),儲存量不能非常大(多于主存)
使用上看: 如果分配的存儲量不大,那么使用shm;如果存儲量大,那么使用mmap。總結(jié)
以上是生活随笔為你收集整理的Linux的mmap内存映射机制解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Hadoop 2.3上运行C++程序各
- 下一篇: Linux系统文件I/O编程(一)---