Linux進(jìn)程通信實(shí)現(xiàn)機(jī)制有很多名惩,也有各自優(yōu)缺點(diǎn)和適用場(chǎng)景泌绣,關(guān)于她們之間的對(duì)比钮追,等各種通信機(jī)制一一介紹后,再來(lái)一個(gè)匯總赞别,俗話說(shuō)“沒(méi)有對(duì)比就沒(méi)有傷害”畏陕,通過(guò)“傷害”讓大家徹底了解正確使用姿勢(shì)。
背景:
原由一:對(duì)內(nèi)存的管理仿滔。
大家熟悉的Glibc庫(kù)提供的有:malloc惠毁、realloc、calloc(三者各自區(qū)別是什么崎页,后續(xù)專題解說(shuō))鞠绰;
可能熟悉的有:Google的Tcmalloc、FaceBook的Jemalloc飒焦,以及優(yōu)化升級(jí)版的Ptmalloc蜈膨;
brk、sbrk(用戶和內(nèi)核均可用來(lái)“申請(qǐng)內(nèi)存”牺荠,這塊后續(xù)專題介紹)翁巍;
mmap,今天的主咖休雌,一起看下她的前世今生灶壶,來(lái)龍去脈。
Slab緩存機(jī)制杈曲、Buddy伙伴算法(這塊后續(xù)專題介紹)驰凛;
內(nèi)核申請(qǐng)內(nèi)存:kmalloc(內(nèi)核空間胸懈,物理連續(xù))、vmalloc(內(nèi)核空間恰响,物理不連續(xù)趣钱,虛擬連續(xù)。思考malloc單次調(diào)用申請(qǐng)的內(nèi)存在物理上連續(xù)不)胚宦;
內(nèi)核管理頁(yè)表:pgd_offset(mm, addr)得到一級(jí)頁(yè)表入口首有、pmd_offset(pgd, addr)得到二級(jí)頁(yè)表入口
、通過(guò)pte_offset_map(pmd, addr)得到目標(biāo)頁(yè)表項(xiàng)枢劝;
get_zeroed_page(unsigned int flags);指向一個(gè)清零的新page绞灼、 __get_free_page(unsigned int flags);指向新頁(yè)但不清零,實(shí)際上是用了order為0的下一個(gè)函數(shù)呈野、__get_free_pages(unsigned int flags, unsigned int order);獲取多個(gè)pages數(shù)量是2^order,不清零印叁;
virt_to_phys()實(shí)現(xiàn)內(nèi)核虛擬向物理地址的轉(zhuǎn)化(感興趣可以自行查看)被冒。
原由二:進(jìn)程高效通信和文件讀寫(xiě)
無(wú)名知道對(duì)于像有名/無(wú)名管道和消息隊(duì)列等通信方式,需要在內(nèi)核和用戶空間進(jìn)行兩次運(yùn)行級(jí)別切換(系統(tǒng)調(diào)用導(dǎo)致保護(hù)和恢復(fù)進(jìn)程上下文環(huá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)核通過(guò)一定策略刷盤(pán)玻驻,后續(xù)專題介紹)悼凑。共享內(nèi)存中的數(shù)據(jù)往往是在解除映射時(shí)才寫(xiě)回文件的。因此璧瞬,采用共享內(nèi)存的通信方式效率是非常高的户辫。
一起細(xì)數(shù)mmap
1.共享內(nèi)存的概念
共享內(nèi)存可以說(shuō)是最有用的進(jìn)程間通信方式,也是最快的IPC形式嗤锉。兩個(gè)不同進(jìn)程A渔欢、B共享內(nèi)存的意思是,同一塊物理內(nèi)存被映射到進(jìn)程A档冬、B各自的進(jìn)程地址空間膘茎。進(jìn)程A可以(通過(guò)flag設(shè)置)即時(shí)看到進(jìn)程B對(duì)共享內(nèi)存中數(shù)據(jù)的更新桃纯,反之亦然。
2.mmap工作原理
mmap是一種內(nèi)存映射文件的方法披坏,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間态坦,實(shí)現(xiàn)文件磁盤(pán)地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(guān)系。實(shí)現(xiàn)這樣的映射關(guān)系后棒拂,進(jìn)程就可以采用指針的方式讀寫(xiě)操作這一段內(nèi)存伞梯,而系統(tǒng)會(huì)自動(dòng)回寫(xiě)臟頁(yè)面到對(duì)應(yīng)的文件磁盤(pán)上,即完成了對(duì)文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)帚屉。相反谜诫,內(nèi)核空間對(duì)這段區(qū)域的修改也直接反映用戶空間,從而可以實(shí)現(xiàn)不同進(jìn)程間的文件共享攻旦。如下圖所示:
mmap內(nèi)存映射的實(shí)現(xiàn)過(guò)程喻旷,總的來(lái)說(shuō)可以分為三個(gè)階段:
①進(jìn)程啟動(dòng)映射過(guò)程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
進(jìn)程在用戶空間調(diào)用mmap函數(shù)牢屋。
在當(dāng)前進(jìn)程的虛擬地址空間中且预,尋找一段空閑的滿足要求的連續(xù)的虛擬地址。
為此虛擬去分配一個(gè)vm_area_struct結(jié)構(gòu)烙无,并對(duì)該結(jié)構(gòu)各個(gè)域進(jìn)行初始化锋谐。
將新建的vm_area_struct插入到進(jìn)程的虛擬地址區(qū)域鏈表或樹(shù)中。
②調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap(不同于用戶空間函數(shù))截酷,實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系
為映射分配了新的虛擬地址區(qū)域后涮拗,通過(guò)待映射的文件指針,在文件描述符表中找到對(duì)應(yīng)的文件描述符迂苛,通過(guò)文件描述符三热,連接到內(nèi)核“已打開(kāi)文集”中該文件的文件結(jié)構(gòu)體struct file,每個(gè)文件結(jié)構(gòu)體維護(hù)著和這個(gè)已打開(kāi)文件的相關(guān)信息(從open函數(shù)調(diào)用到內(nèi)核返回文件描述符這塊隸屬于文件系統(tǒng)的知識(shí)三幻,后續(xù)專題補(bǔ)充)康铭。
通過(guò)該文件的文件結(jié)構(gòu)體,連接到file_operations模塊赌髓,調(diào)用 內(nèi)核函數(shù)mmap从藤,int mmap(struct file *filep,struct vm_area_struct *vma)。
內(nèi)核mmap函數(shù)通過(guò)虛擬文件系統(tǒng)inode模塊定位到文件磁盤(pán)物理地址锁蠕。
通過(guò)remap_pfn_range函數(shù)建立頁(yè)表夷野,即實(shí)現(xiàn)了文件地址和虛擬地址區(qū)域的映射,此時(shí)這片虛擬地址區(qū)域沒(méi)有任何數(shù)據(jù)關(guān)聯(lián)到主存中
③進(jìn)程發(fā)起對(duì)這片映射空間的訪問(wèn)荣倾,引發(fā)缺頁(yè)異常悯搔,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝
進(jìn)程的讀寫(xiě)操作訪問(wèn)虛擬地址空間的這一段映射地址,通過(guò)查詢頁(yè)表舌仍,發(fā)現(xiàn)這一段地址不在物理頁(yè)面上妒貌,因?yàn)?b>只是建立了地址映射通危,真正的磁盤(pán)數(shù)據(jù)還沒(méi)有拷貝到內(nèi)存中,因此引發(fā)缺頁(yè)異常缺頁(yè)異常進(jìn)行一系列判斷灌曙,確定無(wú)非法操作后菊碟,內(nèi)核發(fā)起請(qǐng)求調(diào)頁(yè)過(guò)程調(diào)頁(yè)過(guò)程先在交換緩存空間中尋找需要訪問(wèn)的內(nèi)存頁(yè),如果沒(méi)有則調(diào)用nopage函數(shù)把所缺的頁(yè)面從磁盤(pán)裝入主存中之后進(jìn)程可對(duì)這片主存進(jìn)行讀或?qū)懖僮髟诖蹋绻麑?xiě)操作改變了內(nèi)容逆害,一定時(shí)間后系統(tǒng)會(huì)自動(dòng)回寫(xiě)臟頁(yè)面到對(duì)應(yīng)的磁盤(pán)地址,也就是完成了寫(xiě)入到文件的過(guò)程修改過(guò)的臟頁(yè)面不會(huì)立即更新到文件中蚣驼,而是有一段時(shí)間的延遲魄幕,可以調(diào)用msync來(lái)強(qiáng)制同步,這樣所寫(xiě)的內(nèi)容就立即保存到文件里了颖杏。
對(duì)于共享內(nèi)存映射情況纯陨,缺頁(yè)異常處理程序首先在swap cache中尋找目標(biāo)頁(yè)(符address_space以及偏移量的物理頁(yè)),如果找到留储,則直接返回地址队丝;如果沒(méi)有找到,則判斷該頁(yè)是否在交換區(qū)(swap area)欲鹏,如果在,則執(zhí)行一個(gè)換入操作臭墨;如果上述兩種情況都不滿足赔嚎,處理程序?qū)⒎峙湫碌奈锢眄?yè)面,并把它插入到page cache中胧弛。進(jìn)程最終將更新進(jìn)程頁(yè)表尤误。?
注:對(duì)于映射普通文件情況(非共享映射),缺頁(yè)異常處理程序首先會(huì)在page cache中根據(jù)address_space以及數(shù)據(jù)偏移量尋找相應(yīng)的頁(yè)面结缚。如果沒(méi)有找到损晤,則說(shuō)明文件數(shù)據(jù)還沒(méi)有讀入內(nèi)存,處理程序會(huì)從磁盤(pán)讀入相應(yīng)的頁(yè)面红竭,并返回相應(yīng)地址尤勋,同時(shí),進(jìn)程頁(yè)表也會(huì)更新茵宪。? ? ? ? ? ? ? ? ?所有進(jìn)程在映射同一個(gè)共享內(nèi)存區(qū)域時(shí)最冰,情況都一樣,在建立線性地址與物理地址之間的映射之后稀火,不論進(jìn)程各自的返回地址如何暖哨,實(shí)際訪問(wèn)的必然是同一個(gè)共享內(nèi)存區(qū)域?qū)?yīng)的物理頁(yè)面。??
函數(shù)原型:
mmap函數(shù)
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
使用mmap目的有三:
使用普通文件以提供內(nèi)存映射IO凰狞;
使用特殊文件以提供匿名內(nèi)存映射篇裁;
使用shm_open以提供無(wú)親緣關(guān)系的進(jìn)程間的Poxis共享內(nèi)存區(qū)沛慢;
mmap的作用是映射文件描述符fd指定文件的 [offset,offset + length]區(qū)域至調(diào)用進(jìn)程的[start, start + length]的內(nèi)存區(qū)域。
函數(shù)返回成功時(shí)指向目標(biāo)內(nèi)存區(qū)域的指針达布,失敗返回MAP_FAILED((void*)-1)并設(shè)置錯(cuò)誤碼errorno的值团甲。
參數(shù)介紹:
start:映射區(qū)開(kāi)始地址,用戶可以給定往枣。建議設(shè)定NULL伐庭,有內(nèi)核給定起始地址。
length:映射區(qū)的長(zhǎng)度分冈。
prot:期望的內(nèi)存保護(hù)標(biāo)志圾另,不能與文件打開(kāi)模式?jīng)_突
?PROT_EXEC :頁(yè)內(nèi)容可以被執(zhí)行
?PROT_READ :頁(yè)內(nèi)容可以被讀取
?PROT_WRITE :頁(yè)可以被寫(xiě)入
?PROT_NONE :頁(yè)不可訪問(wèn)
fd:有效的文件描述符。參數(shù)fd為即將映射到進(jìn)程空間的文件描述字雕沉,一般由open()返回集乔,同時(shí),fd可以指定為-1坡椒,此時(shí)須指定flags參數(shù)中的MAP_ANON扰路,表明進(jìn)行的是匿名映射(不涉及具體的文件名,避免了文件的創(chuàng)建及打開(kāi)倔叼,很顯然只能用于具有親緣關(guān)系的進(jìn)程間通信汗唱,即用于通過(guò)fork/vfork/clone系統(tǒng)調(diào)用創(chuàng)建的子進(jìn)程之間的匿名映射通信。這里要強(qiáng)調(diào)的是雖說(shuō)子進(jìn)程繼承父進(jìn)程的Address Space孵化自己丈攒,但是任何一方對(duì)共享變量發(fā)生寫(xiě)操作哩罪,就會(huì)產(chǎn)生COW。所以為了有助于大家理解巡验,可以說(shuō)是父子進(jìn)程“公用”調(diào)用fork/vfork/clone函數(shù)之前所定義的變量對(duì)象际插,而不是共享。)
offset:設(shè)置文件從何處開(kāi)始映射(對(duì)于不需要讀入整個(gè)文件的情況)显设。
flags:指定映射對(duì)象的類型框弛,映射選項(xiàng)和映射頁(yè)是否可以共享。它的值可以是一個(gè)或者多個(gè)以下位的組合體捕捂,具體如下:
MAP_FIXED使用指定的映射起始地址瑟枫,如果由start和length參數(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的別稱,不再被使用絮蒿。
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ū)的訪問(wèn)不會(huì)被頁(yè)違例阻塞。
MAP_NONBLOCK僅和MAP_POPULATE一起使用時(shí)才有意義常侣。不執(zhí)行預(yù)讀蜡饵,只為已存在于內(nèi)存中的頁(yè)面建立頁(yè)表。
munmap函數(shù)
int munmap(void *addr, size_t len);?
從某個(gè)進(jìn)程的地址空間刪除一個(gè)映射關(guān)系胳施。再次訪問(wèn)這些地址會(huì)導(dǎo)致調(diào)用產(chǎn)生一個(gè)SIGSEGV信號(hào)(這里假設(shè)后續(xù)mmap調(diào)用沒(méi)有恰巧重用這部分地址空間)溯祸。如果被映射去是使用MAP_PRIVATE標(biāo)示映射的,那么調(diào)用進(jìn)程對(duì)她所作的變動(dòng)都被丟棄。如果是MAP_SHARED映射焦辅,內(nèi)核的虛擬內(nèi)存算法保障內(nèi)存映射文件(一般在磁盤(pán)上)與內(nèi)存映射區(qū)(在用戶態(tài)內(nèi)存中)的同步博杖。然而有時(shí)候在業(yè)務(wù)上需要確保硬盤(pán)上的文件內(nèi)容和內(nèi)存映射區(qū)中內(nèi)容一致,可以調(diào)用masync函數(shù)來(lái)“手動(dòng)”執(zhí)行同步筷登。 參數(shù)介紹 addr參數(shù)是由mmap函數(shù)返回的地址剃根,len是映射區(qū)大小。
msync函數(shù)
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)容一致。
3.mmap和常規(guī)文件操作的區(qū)別
常規(guī)文件系統(tǒng)操作(調(diào)用read/fread等類函數(shù))中莺匠,函數(shù)的調(diào)用過(guò)程:
1.進(jìn)程發(fā)起讀文件請(qǐng)求金吗。
2.內(nèi)核通過(guò)查找進(jìn)程文件符表,定位到內(nèi)核已打開(kāi)文件集上的文件信息趣竣,從而找到此文件的inode(這塊后續(xù)有專題去介紹)摇庙。一個(gè)具體的文件在打開(kāi)后,內(nèi)核會(huì)在內(nèi)存中為之建立一個(gè)struct inode結(jié)構(gòu)遥缕,其中的i_mapping域指向一個(gè)address_space結(jié)構(gòu)卫袒。
3.inode在address_space上查找要請(qǐng)求的文件頁(yè)是否已經(jīng)緩存在頁(yè)緩存中。如果存在单匣,則直接返回這片文件頁(yè)的內(nèi)容夕凝。一個(gè)文件對(duì)應(yīng)一個(gè)address_space結(jié)構(gòu),一個(gè)address_space與一個(gè)偏移量能夠確定一個(gè)page cache 或swap cache中的一個(gè)頁(yè)面 户秤。因此码秉,當(dāng)要尋址某個(gè)數(shù)據(jù)時(shí),很容易根據(jù)給定的文件及數(shù)據(jù)在文件內(nèi)的偏移量而找到相應(yīng)的頁(yè)面鸡号。
4.如果不存在转砖,則通過(guò)inode定位到文件磁盤(pán)地址,將數(shù)據(jù)從磁盤(pán)復(fù)制到頁(yè)緩存鲸伴。之后再次發(fā)起讀頁(yè)面過(guò)程府蔗,進(jìn)而將頁(yè)緩存中的數(shù)據(jù)通過(guò)copy_to_user把數(shù)據(jù)copy到用戶進(jìn)程cache。
總結(jié)來(lái)說(shuō)汞窗,常規(guī)文件操作為了提高讀寫(xiě)效率和保護(hù)磁盤(pán)姓赤,使用了頁(yè)緩存機(jī)制。這樣造成讀文件時(shí)需要先將文件頁(yè)從磁盤(pán)拷貝到頁(yè)緩存中仲吏,由于頁(yè)緩存處在內(nèi)核空間不铆,不能被用戶進(jìn)程直接尋址蝌焚,所以還需要將頁(yè)緩存中數(shù)據(jù)頁(yè)再次拷貝到內(nèi)存對(duì)應(yīng)的用戶空間中。這樣狂男,通過(guò)兩次數(shù)據(jù)拷貝過(guò)程综看,才能完成進(jìn)程對(duì)文件內(nèi)容的獲取任務(wù)。寫(xiě)操作也是一樣岖食,待寫(xiě)入的buffer在內(nèi)核空間不能直接訪問(wèn)红碑,必須要先拷貝至內(nèi)核空間對(duì)應(yīng)的主存,再寫(xiě)回磁盤(pán)中(延遲寫(xiě)回)泡垃,也是需要兩次數(shù)據(jù)拷貝析珊。
而使用mmap操作文件中,創(chuàng)建新的虛擬內(nèi)存區(qū)域和建立文件磁盤(pán)地址和虛擬內(nèi)存區(qū)域映射這兩步蔑穴,沒(méi)有任何文件拷貝操作忠寻。而之后訪問(wèn)數(shù)據(jù)時(shí)發(fā)現(xiàn)內(nèi)存中并無(wú)數(shù)據(jù)而發(fā)起的缺頁(yè)異常過(guò)程,可以通過(guò)已經(jīng)建立好的映射關(guān)系存和,只使用一次數(shù)據(jù)拷貝奕剃,就從磁盤(pán)中將數(shù)據(jù)傳入內(nèi)存的用戶空間中,供進(jìn)程使用捐腿。
mmap優(yōu)點(diǎn)總結(jié)
1.對(duì)文件的讀取操作跨過(guò)了頁(yè)緩存纵朋,減少了數(shù)據(jù)的拷貝次數(shù)。用內(nèi)存讀寫(xiě)取代I/O讀寫(xiě)茄袖,提高了文件讀取效率操软。兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi),從而被對(duì)方空間及時(shí)捕捉宪祥。
2.實(shí)現(xiàn)了用戶空間和內(nèi)核空間的高效交互方式聂薪。
3.提供進(jìn)程間共享內(nèi)存以及相互通信的方式。兩個(gè)進(jìn)程將自身的用戶空間映射到同一片區(qū)域蝗羊,從而達(dá)到通信或者共享的目的藏澳。(進(jìn)程A和進(jìn)程B都映射了區(qū)域C,當(dāng)A第一次讀取C時(shí)通過(guò)缺頁(yè)異常從磁盤(pán)復(fù)制文件頁(yè)到內(nèi)存中耀找,但當(dāng)B再去讀取C的時(shí)候翔悠,雖然會(huì)產(chǎn)生缺頁(yè)異常,但是不需要再?gòu)拇疟P(pán)復(fù)制文件涯呻,而是可以直接使用保存在內(nèi)存中的文件數(shù)據(jù))凉驻。
4腻要、可用于實(shí)現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸复罐。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個(gè)方面雄家,解決方案往往是借助硬盤(pán)空間協(xié)助操作效诅,補(bǔ)充內(nèi)存的不足。但是進(jìn)一步會(huì)造成大量的文件I/O操作,極大影響效率乱投。這個(gè)問(wèn)題可以通過(guò)mmap映射很好的解決咽笼。換句話說(shuō),但凡是需要用磁盤(pán)空間代替內(nèi)存的時(shí)候戚炫,mmap都可以發(fā)揮其功效剑刑。
總而言之,常規(guī)文件操作需要從磁盤(pán)到頁(yè)緩存再到用戶主存的兩次數(shù)據(jù)拷貝双肤。而mmap操控文件施掏,只需要從磁盤(pán)到用戶主存的一次數(shù)據(jù)拷貝過(guò)程。說(shuō)白了茅糜,mmap的關(guān)鍵點(diǎn)是實(shí)現(xiàn)了用戶空間和內(nèi)核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不通的繁瑣過(guò)程七芭。因此mmap效率更高。
4.mmap細(xì)節(jié)及使用舉例
1蔑赘、使用mmap需要注意的一個(gè)關(guān)鍵點(diǎn)是狸驳,mmap映射區(qū)域大小必須是物理頁(yè)大小(page_size)的整倍數(shù)(32位系統(tǒng)中通常是4k字節(jié))。原因是缩赛,內(nèi)存的最小粒度是頁(yè)耙箍,而進(jìn)程虛擬地址空間和內(nèi)存的映射也是以頁(yè)為單位。為了匹配內(nèi)存的操作峦筒,mmap從磁盤(pán)到虛擬地址空間的映射也必須是頁(yè)究西。
再啰嗦幾句:
linux采用的是頁(yè)式管理機(jī)制。對(duì)于用mmap()映射普通文件來(lái)說(shuō)物喷,進(jìn)程會(huì)在自己的地址空間新增一塊空間卤材,空間大小由mmap()的len參數(shù)指定,注意峦失,進(jìn)程并不一定能夠?qū)θ啃略隹臻g都能進(jìn)行有效訪問(wèn)扇丛。進(jìn)程能夠訪問(wèn)的有效地址大小取決于文件被映射部分的大小。簡(jiǎn)單的說(shuō)尉辑,能夠容納文件被映射部分大小的最少頁(yè)面?zhèn)€數(shù)決定了進(jìn)程從mmap()返回的地址開(kāi)始帆精,能夠有效訪問(wèn)的地址空間大小。超過(guò)這個(gè)空間大小隧魄,內(nèi)核會(huì)根據(jù)超過(guò)的嚴(yán)重程度返回發(fā)送不同的信號(hào)給進(jìn)程卓练。
2、內(nèi)核可以跟蹤被內(nèi)存映射的底層對(duì)象(文件)的大小购啄,進(jìn)程可以合法的訪問(wèn)在當(dāng)前文件大小以內(nèi)又在內(nèi)存映射區(qū)以內(nèi)的那些字節(jié)襟企。也就是說(shuō),如果文件的大小一直在擴(kuò)張狮含,只要在映射區(qū)域范圍內(nèi)的數(shù)據(jù)顽悼,進(jìn)程都可以合法得到曼振,這和映射建立時(shí)文件的大小無(wú)關(guān)。具體情形參見(jiàn)“情形三”蔚龙。
3冰评、映射建立之后,即使文件關(guān)閉木羹,映射依然存在甲雅。因?yàn)橛成涞氖谴疟P(pán)的地址,不是文件本身坑填,和文件句柄無(wú)關(guān)务荆。同時(shí)可用于進(jìn)程間通信的有效地址空間不完全受限于被映射文件的大小,因?yàn)槭前错?yè)映射穷遂。
在上面的知識(shí)前提下函匕,我們下面看看如果大小不是頁(yè)的整倍數(shù)的具體情況:
情形一:一個(gè)文件的大小是5000字節(jié),mmap函數(shù)從一個(gè)文件的起始位置開(kāi)始蚪黑,映射5000字節(jié)到虛擬內(nèi)存中盅惜。
分析:因?yàn)閱挝晃锢眄?yè)面的大小是4096字節(jié),雖然被映射的文件只有5000字節(jié)忌穿,但是對(duì)應(yīng)到進(jìn)程虛擬地址區(qū)域的大小需要滿足整頁(yè)大小抒寂,因此mmap函數(shù)執(zhí)行后,實(shí)際映射到虛擬內(nèi)存區(qū)域8192個(gè) 字節(jié)掠剑,5000~8191的字節(jié)部分用零填充屈芜。映射后的對(duì)應(yīng)關(guān)系如下圖所示:
此時(shí):
(1)讀/寫(xiě)前5000個(gè)字節(jié)(0~4999),會(huì)返回操作文件內(nèi)容朴译。
(2)讀字節(jié)5000~8191時(shí)井佑,結(jié)果全為0。寫(xiě)5000~8191時(shí)眠寿,進(jìn)程不會(huì)報(bào)錯(cuò)躬翁,但是所寫(xiě)的內(nèi)容不會(huì)寫(xiě)入原文件中 。
(3)讀/寫(xiě)8192以外的磁盤(pán)部分盯拱,會(huì)返回一個(gè)SIGSECV錯(cuò)誤盒发。
情形二:一個(gè)文件的大小是5000字節(jié),mmap函數(shù)從一個(gè)文件的起始位置開(kāi)始狡逢,映射15000字節(jié)到虛擬內(nèi)存中宁舰,即映射大小超過(guò)了原始文件的大小。
分析:由于文件的大小是5000字節(jié)奢浑,和情形一一樣蛮艰,其對(duì)應(yīng)的兩個(gè)物理頁(yè)。那么這兩個(gè)物理頁(yè)都是合法可以讀寫(xiě)的殷费,只是超出5000的部分不會(huì)體現(xiàn)在原文件中印荔。由于程序要求映射15000字節(jié),而文件只占兩個(gè)物理頁(yè)详羡,因此8192字節(jié)~15000字節(jié)都不能讀寫(xiě)仍律,操作時(shí)會(huì)返回異常。如下圖所示:
此時(shí):
(1)進(jìn)程可以正常讀/寫(xiě)被映射的前5000字節(jié)(0~4999)实柠,寫(xiě)操作的改動(dòng)會(huì)在一定時(shí)間后反映在原文件中水泉。
(2)對(duì)于5000~8191字節(jié),進(jìn)程可以進(jìn)行讀寫(xiě)過(guò)程窒盐,不會(huì)報(bào)錯(cuò)草则。但是內(nèi)容在寫(xiě)入前均為0,另外蟹漓,寫(xiě)入后不會(huì)反映在文件中炕横。
(3)對(duì)于8192~14999字節(jié),進(jìn)程不能對(duì)其進(jìn)行讀寫(xiě)葡粒,會(huì)報(bào)SIGBUS錯(cuò)誤份殿。
(4)對(duì)于15000以外的字節(jié),進(jìn)程不能對(duì)其讀寫(xiě)嗽交,會(huì)引發(fā)SIGSEGV錯(cuò)誤卿嘲。
情形三:一個(gè)文件初始大小為0,使用mmap操作映射了1000*4K的大小夫壁,即1000個(gè)物理頁(yè)大約4M字節(jié)空間拾枣,mmap返回指針ptr。
分析:如果在映射建立之初盒让,就對(duì)文件進(jìn)行讀寫(xiě)操作梅肤,由于文件大小為0,并沒(méi)有合法的物理頁(yè)對(duì)應(yīng)邑茄,如同情形二一樣凭语,會(huì)返回SIGBUS錯(cuò)誤。
但是如果撩扒,每次操作ptr讀寫(xiě)前似扔,先增加文件的大小,那么ptr在文件大小內(nèi)部的操作就是合法的搓谆。例如炒辉,文件擴(kuò)充4096字節(jié),ptr就能操作ptr到 [ (char)ptr + 4095]的空間泉手。只要文件擴(kuò)充的范圍在1000個(gè)物理頁(yè)(映射范圍)內(nèi)黔寇,ptr都可以對(duì)應(yīng)操作相同的大小。這樣斩萌,方便隨時(shí)擴(kuò)充文件空間缝裤,隨時(shí)寫(xiě)入文件屏轰,不造成空間浪費(fèi)。
其他實(shí)戰(zhàn)實(shí)例憋飞,我會(huì)補(bǔ)充在GitHub霎苗。