共享內(nèi)存允許兩個(gè)或多個(gè)進(jìn)程共享一給定的存儲(chǔ)區(qū)助琐,因?yàn)閿?shù)據(jù)不需要來回復(fù)制烙丛,所以是最快的一種進(jìn)程間通信機(jī)制舅巷。共享內(nèi)存可以通過mmap()映射普通文件 (特殊情況下還可以采用匿名映射)機(jī)制實(shí)現(xiàn),也可以通過systemV共享內(nèi)存機(jī)制實(shí)現(xiàn)河咽。
mmap內(nèi)存文件映射
一钠右、傳統(tǒng)文件訪問
unix訪問文件的傳統(tǒng)方法使用open打開他們,如果有多個(gè)進(jìn)程訪問一個(gè)文件忘蟹,則每一個(gè)進(jìn)程在其地址空間都包含有該文件的副本飒房,這不必要地浪費(fèi)了存儲(chǔ)空間。下面說明了兩個(gè)進(jìn)程同時(shí)讀一個(gè)文件的同一頁(yè)的情形媚值,系統(tǒng)要將該頁(yè)從磁盤讀到高速緩沖區(qū)中狠毯,每個(gè)進(jìn)程再執(zhí)行一個(gè)內(nèi)存期內(nèi)的復(fù)制操作將數(shù)據(jù)從高速緩沖區(qū)讀到自己的地址空間。
二褥芒、共享內(nèi)存映射
現(xiàn)在考慮林一種處理方法:進(jìn)程A和進(jìn)程B都將該頁(yè)映射到自己的地址空間嚼松,當(dāng)進(jìn)程A第一次訪問該頁(yè)中的數(shù)據(jù)時(shí),它生成一個(gè)缺頁(yè)終端锰扶,內(nèi)核此時(shí)讀入這一頁(yè)到內(nèi)存并更新頁(yè)表使之指向它献酗,以后,當(dāng)進(jìn)程B訪問同一頁(yè)面而出現(xiàn)缺頁(yè)中斷時(shí)少辣,該頁(yè)已經(jīng)在內(nèi)存凌摄,內(nèi)核只需要將進(jìn)程B的頁(yè)表登記項(xiàng)指向次頁(yè)即可。
內(nèi)存映射漓帅,簡(jiǎn)而言之就是將用戶空間的一段內(nèi)存區(qū)域映射到內(nèi)核空間锨亏,映射成功后,用戶對(duì)這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間忙干,同樣器予,內(nèi)核空間對(duì)這段區(qū)域的修改也直接反映用戶空間。那么對(duì)于內(nèi)核空間<---->用戶空間兩者之間需要大量數(shù)據(jù)傳輸?shù)炔僮鞯脑捫适欠浅8叩摹?/p>
以下是一個(gè)把普遍文件映射到用戶空間的內(nèi)存區(qū)域的示意圖。
圖一:
二、基本函數(shù)
mmap函數(shù)是unix/linux下的系統(tǒng)調(diào)用淑玫,詳細(xì)內(nèi)容可參考《Unix Netword programming》卷二12.2節(jié)危纫。
mmap系統(tǒng)調(diào)用并不是完全為了用于共享內(nèi)存而設(shè)計(jì)的路星。它本身提供了不同于一般對(duì)普通文件的訪問方式橄唬,進(jìn)程可以像讀寫內(nèi)存一樣對(duì)普通文件的操作赎瞎。而Posix或系統(tǒng)V的共享內(nèi)存IPC則純粹用于共享目的揍很,當(dāng)然mmap()實(shí)現(xiàn)共享內(nèi)存也是其主要應(yīng)用之一雷则。
mmap系統(tǒng)調(diào)用使得進(jìn)程之間通過映射同一個(gè)普通文件實(shí)現(xiàn)共享內(nèi)存辆雾。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以像訪問普通內(nèi)存一樣對(duì)文件進(jìn)行訪問月劈,不必再調(diào)用read()度迂,write()等操作。mmap并不分配空間, 只是將文件映射到調(diào)用進(jìn)程的地址空間里(但是會(huì)占掉你的 virutal memory), 然后你就可以用memcpy等操作寫文件, 而不用write()了.寫完后猜揪,內(nèi)存中的內(nèi)容并不會(huì)立即更新到文件中惭墓,而是有一段時(shí)間的延遲,你可以調(diào)用msync()來顯式同步一下, 這樣你所寫的內(nèi)容就能立即保存到文件里了.這點(diǎn)應(yīng)該和驅(qū)動(dòng)相關(guān)而姐。 不過通過mmap來寫文件這種方式?jīng)]辦法增加文件的長(zhǎng)度, 因?yàn)橐成涞拈L(zhǎng)度在調(diào)用mmap()的時(shí)候就決定了.如果想取消內(nèi)存映射腊凶,可以調(diào)用munmap()來取消內(nèi)存映射
mmap是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間毅人,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(guān)系吭狡,函數(shù)原型如下
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
實(shí)現(xiàn)這樣的映射關(guān)系后,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存丈莺,而系統(tǒng)會(huì)自動(dòng)回寫臟頁(yè)面到對(duì)應(yīng)的文件磁盤上划煮,即完成了對(duì)文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)。如下圖所示
mmap除了可以減少read,write等系統(tǒng)調(diào)用以外缔俄,還可以減少內(nèi)存的拷貝次數(shù)弛秋,比如在read調(diào)用時(shí),一個(gè)完整的流程是操作系統(tǒng)讀磁盤文件到頁(yè)緩存俐载,再?gòu)捻?yè)緩存將數(shù)據(jù)拷貝到read傳遞的buffer里蟹略,而如果使用mmap之后,操作系統(tǒng)只需要將磁盤讀到用戶空間遏佣,然后用戶就可以直接通過指針的方式操作mmap映射的內(nèi)存挖炬,減少了從內(nèi)核態(tài)到用戶態(tài)的數(shù)據(jù)拷貝。
原理
首先状婶,“映射”這個(gè)詞意敛,就和數(shù)學(xué)課上說的“一一映射”是一個(gè)意思,就是建立一種一一對(duì)應(yīng)關(guān)系膛虫,在這里主要是只 硬盤上文件 的位置與進(jìn)程 邏輯地址空間 中一塊大小相同的區(qū)域之間的一一對(duì)應(yīng)草姻,如圖1中過程1所示。這種對(duì)應(yīng)關(guān)系純屬是邏輯上的概念稍刀,物理上是不存在的撩独,原因是進(jìn)程的邏輯地址空間本身就是不存在的。在內(nèi)存映射的過程中,并沒有實(shí)際的數(shù)據(jù)拷貝综膀,文件沒有被載入內(nèi)存澳迫,只是邏輯上被放入了內(nèi)存,具體到代碼僧须,就是建立并初始化了相關(guān)的數(shù)據(jù)結(jié)構(gòu)(struct address_space)纲刀,這個(gè)過程有系統(tǒng)調(diào)用mmap()實(shí)現(xiàn),所以建立內(nèi)存映射的效率很高担平。
既然建立內(nèi)存映射沒有進(jìn)行實(shí)際的數(shù)據(jù)拷貝,那么進(jìn)程又怎么能最終直接通過內(nèi)存操作訪問到硬盤上的文件呢锭部?那就要看內(nèi)存映射之后的幾個(gè)相關(guān)的過程了暂论。
mmap()會(huì)返回一個(gè)指針ptr,它指向進(jìn)程邏輯地址空間中的一個(gè)地址拌禾,這樣以后取胎,進(jìn)程無需再調(diào)用read或write對(duì)文件進(jìn)行讀寫,而只需要通過ptr就能夠操作文件湃窍。但是ptr所指向的是一個(gè)邏輯地址闻蛀,要操作其中的數(shù)據(jù),必須通過MMU將邏輯地址轉(zhuǎn)換成物理地址您市,如圖1中過程2所示觉痛。這個(gè)過程與內(nèi)存映射無關(guān)。
前面講過茵休,建立內(nèi)存映射并沒有實(shí)際拷貝數(shù)據(jù)薪棒,這時(shí),MMU在地址映射表中是無法找到與ptr相對(duì)應(yīng)的物理地址的榕莺,也就是MMU失敗俐芯,將產(chǎn)生一個(gè)缺頁(yè)中斷,缺頁(yè)中斷的中斷響應(yīng)函數(shù)會(huì)在swap中尋找相對(duì)應(yīng)的頁(yè)面钉鸯,如果找不到(也就是該文件從來沒有被讀入內(nèi)存的情況)吧史,則會(huì)通過mmap()建立的映射關(guān)系,從硬盤上將文件讀取到物理內(nèi)存中唠雕,如圖1中過程3所示贸营。這個(gè)過程與內(nèi)存映射無關(guān)。
如果在拷貝數(shù)據(jù)時(shí)及塘,發(fā)現(xiàn)物理內(nèi)存不夠用莽使,則會(huì)通過虛擬內(nèi)存機(jī)制(swap)將暫時(shí)不用的物理頁(yè)面交換到硬盤上,如圖1中過程4所示笙僚。這個(gè)過程也與內(nèi)存映射無關(guān)芳肌。
效率
從代碼層面上看,從硬盤上將文件讀入內(nèi)存,都要經(jīng)過文件系統(tǒng)進(jìn)行數(shù)據(jù)拷貝亿笤,并且數(shù)據(jù)拷貝操作是由文件系統(tǒng)和硬件驅(qū)動(dòng)實(shí)現(xiàn)的翎迁,理論上來說,拷貝數(shù)據(jù)的效率是一樣的净薛。但是通過內(nèi)存映射的方法訪問硬盤上的文件汪榔,效率要比read和write系統(tǒng)調(diào)用高,這是為什么呢肃拜?原因是read()是系統(tǒng)調(diào)用痴腌,其中進(jìn)行了數(shù)據(jù)拷貝,它首先將文件內(nèi)容從硬盤拷貝到內(nèi)核空間的一個(gè)緩沖區(qū)燃领,如圖2中過程1士聪,然后再將這些數(shù)據(jù)拷貝到用戶空間,如圖2中過程2猛蔽,在這個(gè)過程中剥悟,實(shí)際上完成了 兩次數(shù)據(jù)拷貝 ;而mmap()也是系統(tǒng)調(diào)用曼库,如前所述区岗,mmap()中沒有進(jìn)行數(shù)據(jù)拷貝,真正的數(shù)據(jù)拷貝是在缺頁(yè)中斷處理時(shí)進(jìn)行的毁枯,由于mmap()將文件直接映射到用戶空間慈缔,所以中斷處理函數(shù)根據(jù)這個(gè)映射關(guān)系,直接將文件從硬盤拷貝到用戶空間后众,只進(jìn)行了 一次數(shù)據(jù)拷貝 胀糜。因此,內(nèi)存映射的效率要比read/write效率高蒂誉。
回頭再來看
mmap基礎(chǔ)概念
mmap是一種內(nèi)存映射文件的方法教藻,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(guān)系右锨。實(shí)現(xiàn)這樣的映射關(guān)系后括堤,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會(huì)自動(dòng)回寫臟頁(yè)面到對(duì)應(yīng)的文件磁盤上绍移,即完成了對(duì)文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)悄窃。相反,內(nèi)核空間對(duì)這段區(qū)域的修改也直接反映用戶空間蹂窖,從而可以實(shí)現(xiàn)不同進(jìn)程間的文件共享轧抗。如下圖所示:
由上圖可以看出,進(jìn)程的虛擬地址空間瞬测,由多個(gè)虛擬內(nèi)存區(qū)域構(gòu)成横媚。虛擬內(nèi)存區(qū)域是進(jìn)程的虛擬地址空間中的一個(gè)同質(zhì)區(qū)間纠炮,即具有同樣特性的連續(xù)地址范圍。上圖中所示的text數(shù)據(jù)段(代碼段)灯蝴、初始數(shù)據(jù)段恢口、BSS數(shù)據(jù)段、堆穷躁、棧和內(nèi)存映射耕肩,都是一個(gè)獨(dú)立的虛擬內(nèi)存區(qū)域。而為內(nèi)存映射服務(wù)的地址空間處在堆棧之間的空余部分问潭。
linux內(nèi)核使用vm_area_struct結(jié)構(gòu)來表示一個(gè)獨(dú)立的虛擬內(nèi)存區(qū)域猿诸,由于每個(gè)不同質(zhì)的虛擬內(nèi)存區(qū)域功能和內(nèi)部機(jī)制都不同,因此一個(gè)進(jìn)程使用多個(gè)vm_area_struct結(jié)構(gòu)來分別表示不同類型的虛擬內(nèi)存區(qū)域狡忙。各個(gè)vm_area_struct結(jié)構(gòu)使用鏈表或者樹形結(jié)構(gòu)鏈接两芳,方便進(jìn)程快速訪問,如下圖所示:
vm_area_struct結(jié)構(gòu)中包含區(qū)域起始和終止地址以及其他相關(guān)信息去枷,同時(shí)也包含一個(gè)vm_ops指針,其內(nèi)部可引出所有針對(duì)這個(gè)區(qū)域可以使用的系統(tǒng)調(diào)用函數(shù)是复。這樣删顶,進(jìn)程對(duì)某一虛擬內(nèi)存區(qū)域的任何操作需要用要的信息,都可以從vm_area_struct中獲得淑廊。mmap函數(shù)就是要?jiǎng)?chuàng)建一個(gè)新的vm_area_struct結(jié)構(gòu)逗余,并將其與文件的物理磁盤地址相連。
mmap內(nèi)存映射原理
mmap內(nèi)存映射的實(shí)現(xiàn)過程季惩,總的來說可以分為三個(gè)階段:
(一)進(jìn)程啟動(dòng)映射過程录粱,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
1、進(jìn)程在用戶空間調(diào)用庫(kù)函數(shù)mmap画拾,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
2啥繁、在當(dāng)前進(jìn)程的虛擬地址空間中,尋找一段空閑的滿足要求的連續(xù)的虛擬地址
3青抛、為此虛擬區(qū)分配一個(gè)vm_area_struct結(jié)構(gòu)旗闽,接著對(duì)這個(gè)結(jié)構(gòu)的各個(gè)域進(jìn)行了初始化
4、將新建的虛擬區(qū)結(jié)構(gòu)(vm_area_struct)插入進(jìn)程的虛擬地址區(qū)域鏈表或樹中
(二)調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap(不同于用戶空間函數(shù))蜜另,實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系
5适室、為映射分配了新的虛擬地址區(qū)域后,通過待映射的文件指針举瑰,在文件描述符表中找到對(duì)應(yīng)的文件描述符捣辆,通過文件描述符,鏈接到內(nèi)核“已打開文件集”中該文件的文件結(jié)構(gòu)體(struct file)此迅,每個(gè)文件結(jié)構(gòu)體維護(hù)著和這個(gè)已打開文件相關(guān)各項(xiàng)信息汽畴。
6旧巾、通過該文件的文件結(jié)構(gòu)體,鏈接到file_operations模塊整袁,調(diào)用內(nèi)核函數(shù)mmap菠齿,其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用戶空間庫(kù)函數(shù)坐昙。
7绳匀、內(nèi)核mmap函數(shù)通過虛擬文件系統(tǒng)inode模塊定位到文件磁盤物理地址。
8炸客、通過remap_pfn_range函數(shù)建立頁(yè)表疾棵,即實(shí)現(xiàn)了文件地址和虛擬地址區(qū)域的映射關(guān)系。此時(shí)痹仙,這片虛擬地址并沒有任何數(shù)據(jù)關(guān)聯(lián)到主存中是尔。
三)進(jìn)程發(fā)起對(duì)這片映射空間的訪問,引發(fā)缺頁(yè)異常开仰,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝
注:前兩個(gè)階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射拟枚,但是并沒有將任何文件數(shù)據(jù)的拷貝至主存。真正的文件讀取是當(dāng)進(jìn)程發(fā)起讀或?qū)懖僮鲿r(shí)众弓。
9恩溅、進(jìn)程的讀或?qū)懖僮髟L問虛擬地址空間這一段映射地址,通過查詢頁(yè)表谓娃,發(fā)現(xiàn)這一段地址并不在物理頁(yè)面上脚乡。因?yàn)槟壳爸唤⒘说刂酚成洌嬲挠脖P數(shù)據(jù)還沒有拷貝到內(nèi)存中滨达,因此引發(fā)缺頁(yè)異常奶稠。
10、缺頁(yè)異常進(jìn)行一系列判斷捡遍,確定無非法操作后锌订,內(nèi)核發(fā)起請(qǐng)求調(diào)頁(yè)過程。
11稽莉、調(diào)頁(yè)過程先在交換緩存空間(swap cache)中尋找需要訪問的內(nèi)存頁(yè)瀑志,如果沒有則調(diào)用nopage函數(shù)把所缺的頁(yè)從磁盤裝入到主存中。
12污秆、之后進(jìn)程即可對(duì)這片主存進(jìn)行讀或者寫的操作劈猪,如果寫操作改變了其內(nèi)容,一定時(shí)間后系統(tǒng)會(huì)自動(dòng)回寫臟頁(yè)面到對(duì)應(yīng)磁盤地址良拼,也即完成了寫入到文件的過程战得。
注:修改過的臟頁(yè)面并不會(huì)立即更新回文件中,而是有一段時(shí)間的延遲庸推,可以調(diào)用msync()來強(qiáng)制同步, 這樣所寫的內(nèi)容就能立即保存到文件里了常侦。
mmap和常規(guī)文件操作的區(qū)別
常規(guī)文件系統(tǒng)操作(調(diào)用read/fread等類函數(shù))中浇冰,函數(shù)的調(diào)用過程:
1、進(jìn)程發(fā)起讀文件請(qǐng)求聋亡。
2肘习、內(nèi)核通過查找進(jìn)程文件符表,定位到內(nèi)核已打開文件集上的文件信息坡倔,從而找到此文件的inode漂佩。
3、inode在address_space上查找要請(qǐng)求的文件頁(yè)是否已經(jīng)緩存在頁(yè)緩存中罪塔。如果存在投蝉,則直接返回這片文件頁(yè)的內(nèi)容。
4征堪、如果不存在瘩缆,則通過inode定位到文件磁盤地址,將數(shù)據(jù)從磁盤復(fù)制到頁(yè)緩存佃蚜。之后再次發(fā)起讀頁(yè)面過程庸娱,進(jìn)而將頁(yè)緩存中的數(shù)據(jù)發(fā)給用戶進(jìn)程。
總結(jié)來說谐算,常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤涌韩,使用了頁(yè)緩存機(jī)制。這樣造成讀文件時(shí)需要先將文件頁(yè)從磁盤拷貝到頁(yè)緩存中氯夷,由于頁(yè)緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址靶擦,所以還需要將頁(yè)緩存中數(shù)據(jù)頁(yè)再次拷貝到內(nèi)存對(duì)應(yīng)的用戶空間中腮考。這樣,通過了兩次數(shù)據(jù)拷貝過程玄捕,才能完成進(jìn)程對(duì)文件內(nèi)容的獲取任務(wù)踩蔚。寫操作也是一樣,待寫入的buffer在內(nèi)核空間不能直接訪問枚粘,必須要先拷貝至內(nèi)核空間對(duì)應(yīng)的主存馅闽,再寫回磁盤中(延遲寫回),也是需要兩次數(shù)據(jù)拷貝馍迄。
而使用mmap操作文件中福也,創(chuàng)建新的虛擬內(nèi)存區(qū)域和建立文件磁盤地址和虛擬內(nèi)存區(qū)域映射這兩步,沒有任何文件拷貝操作攀圈。而之后訪問數(shù)據(jù)時(shí)發(fā)現(xiàn)內(nèi)存中并無數(shù)據(jù)而發(fā)起的缺頁(yè)異常過程暴凑,可以通過已經(jīng)建立好的映射關(guān)系,只使用一次數(shù)據(jù)拷貝赘来,就從磁盤中將數(shù)據(jù)傳入內(nèi)存的用戶空間中现喳,供進(jìn)程使用凯傲。
總而言之,常規(guī)文件操作需要從磁盤到頁(yè)緩存再到用戶主存的兩次數(shù)據(jù)拷貝嗦篱。而mmap操控文件冰单,只需要從磁盤到用戶主存的一次數(shù)據(jù)拷貝過程。說白了灸促,mmap的關(guān)鍵點(diǎn)是實(shí)現(xiàn)了用戶空間和內(nèi)核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不通的繁瑣過程诫欠。因此mmap效率更高。
mmap優(yōu)點(diǎn)總結(jié)
1腿宰、對(duì)文件的讀取操作跨過了頁(yè)緩存呕诉,減少了數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代I/O讀寫吃度,提高了文件讀取效率甩挫。
2、實(shí)現(xiàn)了用戶空間和內(nèi)核空間的高效交互方式椿每。兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi)伊者,從而被對(duì)方空間及時(shí)捕捉。
3间护、提供進(jìn)程間共享內(nèi)存及相互通信的方式亦渗。不管是父子進(jìn)程還是無親緣關(guān)系的進(jìn)程,都可以將自身用戶空間映射到同一個(gè)文件或匿名映射到同一片區(qū)域汁尺。從而通過各自對(duì)映射區(qū)域的改動(dòng)法精,達(dá)到進(jìn)程間通信和進(jìn)程間共享的目的。
同時(shí)痴突,如果進(jìn)程A和進(jìn)程B都映射了區(qū)域C搂蜓,當(dāng)A第一次讀取C時(shí)通過缺頁(yè)從磁盤復(fù)制文件頁(yè)到內(nèi)存中;但當(dāng)B再讀C的相同頁(yè)面時(shí)辽装,雖然也會(huì)產(chǎn)生缺頁(yè)異常帮碰,但是不再需要從磁盤中復(fù)制文件過來,而可直接使用已經(jīng)保存在內(nèi)存中的文件數(shù)據(jù)拾积。
4殉挽、可用于實(shí)現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸。內(nèi)存空間不足拓巧,是制約大數(shù)據(jù)操作的一個(gè)方面斯碌,解決方案往往是借助硬盤空間協(xié)助操作,補(bǔ)充內(nèi)存的不足肛度。但是進(jìn)一步會(huì)造成大量的文件I/O操作输拇,極大影響效率。這個(gè)問題可以通過mmap映射很好的解決贤斜。換句話說策吠,但凡是需要用磁盤空間代替內(nèi)存的時(shí)候逛裤,mmap都可以發(fā)揮其功效。