Linux内存管理之mmap详解 【转】
轉(zhuǎn)自:http://blog.chinaunix.net/uid-26669729-id-3077015.html
一. mmap系統(tǒng)調(diào)用
1. mmap系統(tǒng)調(diào)用
mmap將一個(gè)文件或者其它對(duì)象映射進(jìn)內(nèi)存。文件被映射到多個(gè)頁(yè)上,如果文件的大小不是所有頁(yè)的大小之和,最后一個(gè)頁(yè)不被使用的空間將會(huì)清零。munmap執(zhí)行相反的操作,刪除特定地址區(qū)域的對(duì)象映射。
當(dāng)使用mmap映射文件到進(jìn)程后,就可以直接操作這段虛擬地址進(jìn)行文件的讀寫(xiě)等操作,不必再調(diào)用read,write等系統(tǒng)調(diào)用.但需注意,直接對(duì)該段內(nèi)存寫(xiě)時(shí)不會(huì)寫(xiě)入超過(guò)當(dāng)前文件大小的內(nèi)容.
采用共享內(nèi)存通信的一個(gè)顯而易見(jiàn)的好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫(xiě)內(nèi)存,而不需要任何數(shù)據(jù)的拷貝。對(duì)于像管道和消息隊(duì)列等通信方式,則需要在內(nèi)核和用戶(hù)空間進(jìn)行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則只拷貝兩次數(shù)據(jù):一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。實(shí)際上,進(jìn)程之間在共享內(nèi)存時(shí),并不總是讀寫(xiě)少量數(shù)據(jù)后就解除映射,有新的通信時(shí),再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直到通信完畢為止,這樣,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并沒(méi)有寫(xiě)回文件。共享內(nèi)存中的內(nèi)容往往是在解除映射時(shí)才寫(xiě)回文件的。因此,采用共享內(nèi)存的通信方式效率是非常高的。
基于文件的映射,在mmap和munmap執(zhí)行過(guò)程的任何時(shí)刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情況下沒(méi)有得到更新,首次對(duì)映射區(qū)的第一個(gè)頁(yè)索引時(shí)會(huì)更新該字段的值。用PROT_WRITE 和 MAP_SHARED標(biāo)志建立起來(lái)的文件映射,其st_ctime 和 st_mtime在對(duì)映射區(qū)寫(xiě)入之后,但在msync()通過(guò)MS_SYNC 和 MS_ASYNC兩個(gè)標(biāo)志調(diào)用之前會(huì)被更新。
用法:
#include
void *mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *start, size_t length);
返回說(shuō)明:
成功執(zhí)行時(shí),mmap()返回被映射區(qū)的指針,munmap()返回0。失敗時(shí),mmap()返回MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設(shè)為以下的某個(gè)值
EACCES:訪(fǎng)問(wèn)出錯(cuò)
EAGAIN:文件已被鎖定,或者太多的內(nèi)存已被鎖定
EBADF:fd不是有效的文件描述詞
EINVAL:一個(gè)或者多個(gè)參數(shù)無(wú)效
ENFILE:已達(dá)到系統(tǒng)對(duì)打開(kāi)文件的限制
ENODEV:指定文件所在的文件系統(tǒng)不支持內(nèi)存映射
ENOMEM:內(nèi)存不足,或者進(jìn)程已超出最大內(nèi)存映射數(shù)量
EPERM:權(quán)能不足,操作不允許
ETXTBSY:已寫(xiě)的方式打開(kāi)文件,同時(shí)指定MAP_DENYWRITE標(biāo)志
SIGSEGV:試著向只讀區(qū)寫(xiě)入
SIGBUS:試著訪(fǎng)問(wèn)不屬于進(jìn)程的內(nèi)存區(qū)
參數(shù):
start:映射區(qū)的開(kāi)始地址。
length:映射區(qū)的長(zhǎng)度。
prot:期望的內(nèi)存保護(hù)標(biāo)志,不能與文件的打開(kāi)模式?jīng)_突。是以下的某個(gè)值,可以通過(guò)or運(yùn)算合理地組合在一起
PROT_EXEC //頁(yè)內(nèi)容可以被執(zhí)行
PROT_READ //頁(yè)內(nèi)容可以被讀取
PROT_WRITE //頁(yè)可以被寫(xiě)入
PROT_NONE //頁(yè)不可訪(fǎng)問(wèn)
flags:指定映射對(duì)象的類(lèi)型,映射選項(xiàng)和映射頁(yè)是否可以共享。它的值可以是一個(gè)或者多個(gè)以下位的組合體
MAP_FIXED //使用指定的映射起始地址,如果由start和len參數(shù)指定的內(nèi)存區(qū)重疊于現(xiàn)存的映射空間,重疊部分將會(huì)被丟棄。如果指定的起始地址不可用,操作將會(huì)失敗。并且起始地址必須落在頁(yè)的邊界上。
MAP_SHARED //與其它所有映射這個(gè)對(duì)象的進(jìn)程共享映射空間。對(duì)共享區(qū)的寫(xiě)入,相當(dāng)于輸出到文件。直到msync()或者munmap()被調(diào)用,文件實(shí)際上不會(huì)被更新。
MAP_PRIVATE //建立一個(gè)寫(xiě)入時(shí)拷貝的私有映射。內(nèi)存區(qū)域的寫(xiě)入不會(huì)影響到原文件。這個(gè)標(biāo)志和以上標(biāo)志是互斥的,只能使用其中一個(gè)。
MAP_DENYWRITE //這個(gè)標(biāo)志被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要為這個(gè)映射保留交換空間。當(dāng)交換空間被保留,對(duì)映射區(qū)修改的可能會(huì)得到保證。當(dāng)交換空間不被保留,同時(shí)內(nèi)存不足,對(duì)映射區(qū)的修改會(huì)引起段違例信號(hào)。
MAP_LOCKED //鎖定映射區(qū)的頁(yè)面,從而防止頁(yè)面被交換出內(nèi)存。
MAP_GROWSDOWN //用于堆棧,告訴內(nèi)核VM系統(tǒng),映射區(qū)可以向下擴(kuò)展。
MAP_ANONYMOUS //匿名映射,映射區(qū)不與任何文件關(guān)聯(lián)。
MAP_ANON //MAP_ANONYMOUS的別稱(chēng),不再被使用。
MAP_FILE //兼容標(biāo)志,被忽略。
MAP_32BIT //將映射區(qū)放在進(jìn)程地址空間的低2GB,MAP_FIXED指定時(shí)會(huì)被忽略。當(dāng)前這個(gè)標(biāo)志只在x86-64平臺(tái)上得到支持。
MAP_POPULATE //為文件映射通過(guò)預(yù)讀的方式準(zhǔn)備好頁(yè)表。隨后對(duì)映射區(qū)的訪(fǎng)問(wèn)不會(huì)被頁(yè)違例阻塞。
MAP_NONBLOCK //僅和MAP_POPULATE一起使用時(shí)才有意義。不執(zhí)行預(yù)讀,只為已存在于內(nèi)存中的頁(yè)面建立頁(yè)表入口。
fd:有效的文件描述詞。如果MAP_ANONYMOUS被設(shè)定,為了兼容問(wèn)題,其值應(yīng)為-1。
offset:被映射對(duì)象內(nèi)容的起點(diǎn)。
2. 系統(tǒng)調(diào)用munmap()
#include
int munmap( void * addr, size_t len )
該調(diào)用在進(jìn)程地址空間中解除一個(gè)映射關(guān)系,addr是調(diào)用mmap()時(shí)返回的地址,len是映射區(qū)的大小。當(dāng)映射關(guān)系解除后,對(duì)原來(lái)映射地址的訪(fǎng)問(wèn)將導(dǎo)致段錯(cuò)誤發(fā)生。
3. 系統(tǒng)調(diào)用msync()
#include
int msync ( void * addr , size_t len, int flags)
一般說(shuō)來(lái),進(jìn)程在映射空間的對(duì)共享內(nèi)容的改變并不直接寫(xiě)回到磁盤(pán)文件中,往往在調(diào)用munmap()后才執(zhí)行該操作。可以通過(guò)調(diào)用msync()實(shí)現(xiàn)磁盤(pán)上文件內(nèi)容與共享內(nèi)存區(qū)的內(nèi)容一致。
二. 系統(tǒng)調(diào)用mmap()用于共享內(nèi)存的兩種方式:
(1)使用普通文件提供的內(nèi)存映射:適用于任何進(jìn)程之間;此時(shí),需要打開(kāi)或創(chuàng)建一個(gè)文件,然后再調(diào)用mmap();典型調(diào)用代碼如下:
- fd=open(name, flag, mode);
- if(fd<0)
- ...
- ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
通過(guò)mmap()實(shí)現(xiàn)共享內(nèi)存的通信方式有許多特點(diǎn)和要注意的地方
(2)使用特殊文件提供匿名內(nèi)存映射:適用于具有親緣關(guān)系的進(jìn)程之間;由于父子進(jìn)程特殊的親緣關(guān)系,在父進(jìn)程中先調(diào)用mmap(),然后調(diào)用fork()。那么在調(diào)用fork()之后,子進(jìn)程繼承父進(jìn)程匿名映射后的地址空間,同樣也繼承mmap()返回的地址,這樣,父子進(jìn)程就可以通過(guò)映射區(qū)域進(jìn)行通信了。注意,這里不是一般的繼承關(guān)系。一般來(lái)說(shuō),子進(jìn)程單獨(dú)維護(hù)從父進(jìn)程繼承下來(lái)的一些變量。而mmap()返回的地址,卻由父子進(jìn)程共同維護(hù)。
對(duì)于具有親緣關(guān)系的進(jìn)程實(shí)現(xiàn)共享內(nèi)存最好的方式應(yīng)該是采用匿名內(nèi)存映射的方式。此時(shí),不必指定具體的文件,只要設(shè)置相應(yīng)的標(biāo)志即可.
三. mmap進(jìn)行內(nèi)存映射的原理
mmap系統(tǒng)調(diào)用的最終目的是將,設(shè)備或文件映射到用戶(hù)進(jìn)程的虛擬地址空間,實(shí)現(xiàn)用戶(hù)進(jìn)程對(duì)文件的直接讀寫(xiě),這個(gè)任務(wù)可以分為以下三步:
1.在用戶(hù)虛擬地址空間中尋找空閑的滿(mǎn)足要求的一段連續(xù)的虛擬地址空間,為映射做準(zhǔn)備(由內(nèi)核mmap系統(tǒng)調(diào)用完成)
每個(gè)進(jìn)程擁有3G字節(jié)的用戶(hù)虛存空間。但是,這并不意味著用戶(hù)進(jìn)程在這3G的范圍內(nèi)可以任意使用,因?yàn)樘摯婵臻g最終得映射到某個(gè)物理存儲(chǔ)空間(內(nèi)存或磁盤(pán)空間),才真正可以使用。
那么,內(nèi)核怎樣管理每個(gè)進(jìn)程3G的虛存空間呢?概括地說(shuō),用戶(hù)進(jìn)程經(jīng)過(guò)編譯、鏈接后形成的映象文件有一個(gè)代碼段和數(shù)據(jù)段(包括data段和bss段),其中代碼段在下,數(shù)據(jù)段在上。數(shù)據(jù)段中包括了所有靜態(tài)分配的數(shù)據(jù)空間,即全局變量和所有申明為static的局部變量,這些空間是進(jìn)程所必需的基本要求,這些空間是在建立一個(gè)進(jìn)程的運(yùn)行映像時(shí)就分配好的。除此之外,堆棧使用的空間也屬于基本要求,所以也是在建立進(jìn)程時(shí)就分配好的,如圖3.1所示:
圖3.1 進(jìn)程虛擬空間的劃分
在內(nèi)核中,這樣每個(gè)區(qū)域用一個(gè)結(jié)構(gòu)struct vm_area_struct 來(lái)表示.它描述的是一段連續(xù)的、具有相同訪(fǎng)問(wèn)屬性的虛存空間,該虛存空間的大小為物理內(nèi)存頁(yè)面的整數(shù)倍。可以使用 cat /proc//maps來(lái)查看一個(gè)進(jìn)程的內(nèi)存使用情況,pid是進(jìn)程號(hào).其中顯示的每一行對(duì)應(yīng)進(jìn)程的一個(gè)vm_area_struct結(jié)構(gòu).
下面是struct vm_area_struct結(jié)構(gòu)體的定義:
- #include <linux/mm_types.h>
- /* This struct defines a memory VMM memory area. */
- struct vm_area_struct {
- struct mm_struct * vm_mm; /* VM area parameters */
- unsigned long vm_start;
- unsigned long vm_end;
- /* linked list of VM areas per task, sorted by address */
- struct vm_area_struct *vm_next;
- pgprot_t vm_page_prot;
- unsigned long vm_flags;
- /* AVL tree of VM areas per task, sorted by address */
- short vm_avl_height;
- struct vm_area_struct * vm_avl_left;
- struct vm_area_struct * vm_avl_right;
- /* For areas with an address space and backing store,
- vm_area_struct *vm_next_share;
- struct vm_area_struct **vm_pprev_share;
- struct vm_operations_struct * vm_ops;
- unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
- struct file * vm_file;
- unsigned long vm_raend;
- void * vm_private_data; /* was vm_pte (shared mem) */
- };
通常,進(jìn)程所使用到的虛存空間不連續(xù),且各部分虛存空間的訪(fǎng)問(wèn)屬性也可能不同。所以一個(gè)進(jìn)程的虛存空間需要多個(gè)vm_area_struct結(jié)構(gòu)來(lái)描述。在vm_area_struct結(jié)構(gòu)的數(shù)目較少的時(shí)候,各個(gè)vm_area_struct按照升序排序,以單鏈表的形式組織數(shù)據(jù)(通過(guò)vm_next指針指向下一個(gè)vm_area_struct結(jié)構(gòu))。但是當(dāng)vm_area_struct結(jié)構(gòu)的數(shù)據(jù)較多的時(shí)候,仍然采用鏈表組織的化,勢(shì)必會(huì)影響到它的搜索速度。針對(duì)這個(gè)問(wèn)題,vm_area_struct還添加了vm_avl_hight(樹(shù)高)、vm_avl_left(左子節(jié)點(diǎn))、vm_avl_right(右子節(jié)點(diǎn))三個(gè)成員來(lái)實(shí)現(xiàn)AVL樹(shù),以提高vm_area_struct的搜索速度。
假如該vm_area_struct描述的是一個(gè)文件映射的虛存空間,成員vm_file便指向被映射的文件的file結(jié)構(gòu),vm_pgoff是該虛存空間起始地址在vm_file文件里面的文件偏移,單位為物理頁(yè)面。
圖3.2 進(jìn)程虛擬地址示意圖
因此,mmap系統(tǒng)調(diào)用所完成的工作就是準(zhǔn)備這樣一段虛存空間,并建立vm_area_struct結(jié)構(gòu)體,將其傳給具體的設(shè)備驅(qū)動(dòng)程序.
2. 建立虛擬地址空間和文件或設(shè)備的物理地址之間的映射(設(shè)備驅(qū)動(dòng)完成)
建立文件映射的第二步就是建立虛擬地址和具體的物理地址之間的映射,這是通過(guò)修改進(jìn)程頁(yè)表來(lái)實(shí)現(xiàn)的.mmap方法是file_opeartions結(jié)構(gòu)的成員:
int (*mmap)(struct file *,struct vm_area_struct *);
linux有2個(gè)方法建立頁(yè)表:
(1) 使用remap_pfn_range一次建立所有頁(yè)表.
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
返回值:
成功返回 0, 失敗返回一個(gè)負(fù)的錯(cuò)誤值
參數(shù)說(shuō)明:
vma 用戶(hù)進(jìn)程創(chuàng)建一個(gè)vma區(qū)域
virt_addr 重新映射應(yīng)當(dāng)開(kāi)始的用戶(hù)虛擬地址. 這個(gè)函數(shù)建立頁(yè)表為這個(gè)虛擬地址范圍從 virt_addr 到 virt_addr_size.
pfn 頁(yè)幀號(hào), 對(duì)應(yīng)虛擬地址應(yīng)當(dāng)被映射的物理地址. 這個(gè)頁(yè)幀號(hào)簡(jiǎn)單地是物理地址右移 PAGE_SHIFT 位. 對(duì)大部分使用, VMA 結(jié)構(gòu)的 vm_paoff 成員正好包含你需要的值. 這個(gè)函數(shù)影響物理地址從 (pfn<<page_shift) 到 (pfn<<page_shift)+size.< span="">
size 正在被重新映射的區(qū)的大小, 以字節(jié).
prot 給新 VMA 要求的"protection". 驅(qū)動(dòng)可(并且應(yīng)當(dāng))使用在vma->vm_page_prot 中找到的值.
(2) 使用nopage VMA方法每次建立一個(gè)頁(yè)表項(xiàng).
struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);
返回值:
成功則返回一個(gè)有效映射頁(yè),失敗返回NULL.
參數(shù)說(shuō)明:
address 代表從用戶(hù)空間傳過(guò)來(lái)的用戶(hù)空間虛擬地址.
返回一個(gè)有效映射頁(yè).
(3) 使用方面的限制:
remap_pfn_range不能映射常規(guī)內(nèi)存,只存取保留頁(yè)和在物理內(nèi)存頂之上的物理地址。因?yàn)楸A繇?yè)和在物理內(nèi)存頂之上的物理地址內(nèi)存管理系統(tǒng)的各個(gè)子模塊管理不到。640 KB 和 1MB 是保留頁(yè)可能映射,設(shè)備I/O內(nèi)存也可以映射。如果想把kmalloc()申請(qǐng)的內(nèi)存映射到用戶(hù)空間,則可以通過(guò)mem_map_reserve()把相應(yīng)的內(nèi)存設(shè)置為保留后就可以。
3. 當(dāng)實(shí)際訪(fǎng)問(wèn)新映射的頁(yè)面時(shí)的操作(由缺頁(yè)中斷完成)
(3) 進(jìn)程調(diào)用mmap()時(shí),只是在進(jìn)程空間內(nèi)新增了一塊相應(yīng)大小的緩沖區(qū),并設(shè)置了相應(yīng)的訪(fǎng)問(wèn)標(biāo)識(shí),但并沒(méi)有建立進(jìn)程空間到物理頁(yè)面的映射。因此,第一次訪(fǎng)問(wèn)該空間時(shí),會(huì)引發(fā)一個(gè)缺頁(yè)異常。
(4) 對(duì)于共享內(nèi)存映射情況,缺頁(yè)異常處理程序首先在swap cache中尋找目標(biāo)頁(yè)(符合address_space以及偏移量的物理頁(yè)),如果找到,則直接返回地址;如果沒(méi)有找到,則判斷該頁(yè)是否在交換區(qū) (swap area),如果在,則執(zhí)行一個(gè)換入操作;如果上述兩種情況都不滿(mǎn)足,處理程序?qū)⒎峙湫碌奈锢眄?yè)面,并把它插入到page cache中。進(jìn)程最終將更新進(jìn)程頁(yè)表。
總結(jié)
以上是生活随笔為你收集整理的Linux内存管理之mmap详解 【转】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 洛谷 P1494 BZOJ 2038 [
- 下一篇: LinkedBlockingQueue阻