0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux應(yīng)用開發(fā)之共享內(nèi)存

嵌入式應(yīng)用研究院 ? 來源:TLPI系統(tǒng)編程筆記 ? 2023-04-06 09:51 ? 次閱讀

概述

mmap() 系統(tǒng)調(diào)用在調(diào)用進(jìn)程的虛擬地址空間中創(chuàng)建一個(gè)新的內(nèi)存映射,映射分為兩種:

文件映射:將一個(gè)文件的一部分直接映射到調(diào)用進(jìn)程的虛擬內(nèi)存中,一旦一個(gè)文件被映射之后,就可以通過在相應(yīng)的內(nèi)存區(qū)域中操作字節(jié)來訪問文件內(nèi)容,映射的分頁會在需要的時(shí)候從文件中自動加載,這種映射也稱為基于文件的映射或內(nèi)存映射文件

匿名映射:沒有對應(yīng)的文件,相反,這種映射的分頁會被初始化為 0

一個(gè)進(jìn)程的映射中的內(nèi)存可以與其他進(jìn)程中的映射共享(即各個(gè)子進(jìn)程的頁表?xiàng)l目指向 RAM 中相同的分頁)。這種情況會存在2種情況發(fā)生:

當(dāng)兩個(gè)進(jìn)程映射了一個(gè)文件的同一個(gè)區(qū)域時(shí),它們會共享物理內(nèi)存的相同分頁

通過 fork() 創(chuàng)建的子進(jìn)程會繼承父進(jìn)程的映射的副本,并且這些映射所引用的物理內(nèi)存分頁與父進(jìn)程中相應(yīng)映射所引用的分頁相同

當(dāng)兩個(gè)或者多個(gè)進(jìn)程共享相同分頁時(shí),每個(gè)進(jìn)程都有可能看到其他進(jìn)程對分頁內(nèi)容作出的改變,這當(dāng)然要取決于映射是私有的還是共享的:

私有映射(MAP_PRIVATE) :在映射內(nèi)容上發(fā)生的變更對其他進(jìn)程是不可見的,對于文件映射來說,變更將不會在底層文件上進(jìn)行。盡管一個(gè)私有映射的分頁在上面介紹的情況中, 初始時(shí)是共享的,但對映射內(nèi)容作出的變更對各個(gè)進(jìn)程來講是私有的。內(nèi)核使用了寫時(shí)復(fù)制技術(shù)完成了這個(gè)任務(wù)。這意味著,當(dāng)一個(gè)進(jìn)程試圖修改一個(gè)分頁 的內(nèi)容時(shí),內(nèi)核首先會為該進(jìn)程創(chuàng)建一個(gè)新分頁,并將需要修改的分頁中的內(nèi)容復(fù)制到新分頁中(以及調(diào)整進(jìn)程的頁表)。正因?yàn)檫@個(gè)原因,MAP_PRIVATE 映射會被稱為私有,寫時(shí)復(fù)制映射

共享映射(MAP_SHARED):在映射內(nèi)容上發(fā)生的變更對所有共享同一個(gè)映射的其他進(jìn)程都是可見的,對于文件映射來說,變更將會發(fā)生在底層文件上

各種映射的用途:13e96f22-d40d-11ed-bfe3-dac502259ad0.png

私有文件映射:映射的內(nèi)容被初始化為一個(gè)而文件區(qū)域的內(nèi)容。多個(gè)映射同一個(gè)文件的進(jìn)程初始時(shí)會共享同樣的內(nèi)存物理分頁,但系統(tǒng)使用寫時(shí)復(fù)制技術(shù)使得一個(gè)進(jìn)程對映射所做的變更對其他進(jìn)程不可見。這種映射的主要用途是使用一個(gè)文件的內(nèi)容來來初始化一塊內(nèi)存區(qū)域。一些常見的例子包括根據(jù)二進(jìn)制可執(zhí)行文件或共享文件的相應(yīng)部分來初始化一個(gè)進(jìn)程的文件和數(shù)據(jù)段

私有匿名映射:每次調(diào)用 mmap() 創(chuàng)建一個(gè)私有映射時(shí)都會產(chǎn)生一個(gè)新映射,該映射與同一進(jìn)程創(chuàng)建的其他匿名映射是不同的(即不會共享物理分頁)。盡管子進(jìn)程會繼承父進(jìn)程的映射,但寫時(shí)復(fù)制語義確保了 fork() 之后父進(jìn)程和子進(jìn)程不會看到其他進(jìn)程對映射所做的變更。匿名映射的主要用途是為一個(gè)進(jìn)程分配新(用0填充)的內(nèi)存(如在分配大塊內(nèi)存時(shí)用 malloc() 會為此使用 mmap())

共享文件映射:所有映射一個(gè)文件同一區(qū)域的進(jìn)程會共享同樣的內(nèi)存物理分頁,這些分頁的內(nèi)容將被初始化為該文件區(qū)域。對映射內(nèi)容的修改將直接在文件中進(jìn)行。這種映射主要用于2個(gè)用途:

允許內(nèi)存映射 IO, 這表示一個(gè)文件會被加載到進(jìn)程的虛擬內(nèi)存中的一個(gè)區(qū)域中并且對該區(qū)域的變更會自動被寫入到這個(gè)文件中。因此,內(nèi)存映射 IO 為使用 read(),write() 來執(zhí)行文件 IO 這種做法提供了一種替代方案。

允許無關(guān)進(jìn)程共享一塊內(nèi)容以便以一種類似于 System V 共享內(nèi)存段的方式來執(zhí)行(快速) IPC

共享匿名映射:與私有匿名映射一樣,每次調(diào)用 mmap() 創(chuàng)建一個(gè)共享匿名映射時(shí)都會產(chǎn)生一個(gè)新的,與任何其他映射不共享分頁的截然不同的映射。這里的區(qū)別是映射的分頁不會被寫時(shí)復(fù)制。這意味著, 當(dāng)一個(gè)子進(jìn)程在 fork() 之后繼承映射,父進(jìn)程和子進(jìn)程共享同一的 RAM 分頁,并且一個(gè)進(jìn)程對映射內(nèi)容所作出的變更會對其他進(jìn)程可見。共享匿名映射允許以一種類似于 System V 共享內(nèi)存段的方式來進(jìn)行 IPC,但只有相關(guān)進(jìn)程可以這么做

一個(gè)進(jìn)程在執(zhí)行 exec() 后映射會丟失,但通過 fork() 創(chuàng)建的子進(jìn)程會繼承映射,映射類型(MAP_PRIVATE 和 MAP_SHARED) 也會被繼承。通過 Linux 下特有的 /proc/PID/maps 文件能夠查看與一個(gè)進(jìn)程的映射有關(guān)的所有信息

創(chuàng)建一個(gè)映射

#include

void*mmap(void*addr,size_tlength,intprot,intflags,intfd,off_toffset);

addr建立映射區(qū)的首地址,由 Linux 內(nèi)核指定。用戶程序調(diào)用時(shí)直接傳遞NULL,那么內(nèi)核會為映射選擇一個(gè)合適地址,如果不是NULL,內(nèi)核會再選擇將映射放置在何處時(shí)將這個(gè)參數(shù)值作為一個(gè)提示信息來處理

成功時(shí)返回創(chuàng)建的映射區(qū)首地址,失敗時(shí)返回MAP_FAILED

length指定了映射的字節(jié)數(shù),盡管length無需是一個(gè)系統(tǒng)分頁大小的倍數(shù),但是內(nèi)核會以分頁大小為單位來創(chuàng)建映射,因此,實(shí)際上lenght會被向上提升為分頁大小的下一個(gè)倍數(shù)

prot映射區(qū)的權(quán)限:

1408b9ae-d40d-11ed-bfe3-dac502259ad0.png

flags` 標(biāo)志位參數(shù),常用于設(shè)定更新物理區(qū)域、設(shè)置共享、創(chuàng)建匿名映射區(qū):

MAP_SHARED:創(chuàng)建一個(gè)共享映射,映射區(qū)所做的修改會反映到物理設(shè)備(磁盤)上

MAP_PRIVATE:創(chuàng)建一個(gè)私有映射,映射區(qū)所做的修改不會反映到物理設(shè)備上

fd是用來建立映射區(qū)的文件描述符。

offset映射文件的偏移量,它必須是系統(tǒng)分頁大小的整數(shù)倍,可以映射整個(gè)文件,也可以只映射一部分內(nèi)容

有關(guān)內(nèi)存保護(hù)的更多細(xì)節(jié)

如果一個(gè)進(jìn)程在訪問一個(gè)內(nèi)存區(qū)域時(shí)違反了該區(qū)域上的保護(hù)位,那么內(nèi)核會向該進(jìn)程發(fā)送一個(gè)SIG_SEGV信號。

標(biāo)記為PROT_NONE的分頁內(nèi)存的一個(gè)用途是作為一個(gè)進(jìn)程分配的內(nèi)存區(qū)域的起始位置或結(jié)束位置的守護(hù)分頁,如果進(jìn)程意外地訪問了標(biāo)記為PROT_NONE的分頁,那么內(nèi)核會通過一個(gè)SIGSEGV信號來通知該進(jìn)程。

內(nèi)存保護(hù)信息駐留在進(jìn)程的私有虛擬內(nèi)存表中,因此不同的進(jìn)程可能會使用不同的保護(hù)位來映射同一個(gè)內(nèi)存區(qū)域。

標(biāo)準(zhǔn)中規(guī)定的對offset和addr的對齊約束

SUSv3 規(guī)定mmap()的offset參數(shù)必須要與分頁對齊,而addr參數(shù)在指定了MAP_FIXED的情況下也必須要與分頁對齊。

SUSV4 放寬了這些要求:

一個(gè)實(shí)現(xiàn)可能會要求offset為系統(tǒng)分頁大小的倍數(shù)

如果指定了MAP_FIXED,那么一個(gè)實(shí)現(xiàn)可能會要求addr是分頁對齊的

如果指定了MAP_FIXED,并且addr為非零值,那么addr和offset除以系統(tǒng)分頁大小所得的余數(shù)應(yīng)該相等

解除映射區(qū)域

#include

intmunmap(void*addr,size_tlength);

munmap()與mmap()` 操作相反,即從調(diào)用進(jìn)程的虛擬地址空間中刪除一個(gè)映射

addr參數(shù)是待解除映射的地址范圍的起始地址,它必須與一個(gè)分頁邊界對齊

length參數(shù)是一個(gè)非負(fù)整數(shù),它指定了待解除映射區(qū)域的大小,范圍為系統(tǒng)分頁大小的下一個(gè)倍數(shù)的地址空間將會解除映射

通常會解除整個(gè)映射,因此將addr指定為上一個(gè)mmap()調(diào)用返回的地址,并且length的值與mmap()調(diào)用中使用的length的值一樣。也可以解除一個(gè)映射中的部分映射,這樣原來的映射要么會收縮,要么會被分為兩個(gè),這取決于在何處開始解除映射。還可以指定一個(gè)跨越多個(gè)映射的地址范圍,這樣的話所有范圍內(nèi)的映射都會被解除

如果在addr和length指定的地址范圍中不存在映射,那么munmap()將不起任何作用并返回 0

在解除映射期間,內(nèi)核會刪除進(jìn)程持有的在指定地址范圍內(nèi)的所有內(nèi)存鎖

當(dāng)一個(gè)進(jìn)程終止或執(zhí)行了一個(gè)exec()之后進(jìn)程中所有的映射會自動解除

為確保一個(gè)共享文件映射的內(nèi)容會被寫入到底層文件中,在使用munmap()解除一個(gè)映射之前需要調(diào)用msync()

文件映射

創(chuàng)建一個(gè)文件映射的步驟:

獲取一個(gè)文件的描述符,通常通過調(diào)用open()來獲取

將文件描述符作為fd參數(shù),傳入mmap()調(diào)用中

執(zhí)行上面操作之后,mmap()會將打開的文件的內(nèi)容映射到調(diào)用進(jìn)程的地址空間。一旦mmap()被調(diào)用之后就能夠關(guān)閉文件描述符了,而不會對映射產(chǎn)生任何影響。

除了普通的磁盤文件,使用mmap()還能夠映射各種真實(shí)和虛擬設(shè)備的內(nèi)容,如硬盤,光盤以及/dev/mem。

在打開描述符fd引用的文件時(shí)必須要具備與port和flags參數(shù)值匹配的權(quán)限。

offset參數(shù)指定了從文件區(qū)域中的哪個(gè)字節(jié)開始映射,它必須是系統(tǒng)分頁大小的倍數(shù),將offset指定為 0 會導(dǎo)致從文件的起始位置開始映射,length參數(shù)指定了映射的字節(jié)數(shù)。

141eaad4-d40d-11ed-bfe3-dac502259ad0.png

私有文件映射

私有文件映射用途:

允許多個(gè)執(zhí)行同一程序或者使用同一個(gè)共享庫的進(jìn)程共享同樣(只讀的)文本段,它是從底層可執(zhí)行文件或庫文件的相應(yīng)部分映射而來的

映射一個(gè)可執(zhí)行文件或共享庫的初始化數(shù)據(jù)段。這種映射會被處理成私有,使得對映射數(shù)據(jù)段內(nèi)容的變更不會發(fā)生在底層文件上

mmap()的這兩種用法通常對程序是不可見的,因?yàn)檫@些映射是由程序加載器和動態(tài)鏈接器創(chuàng)建的,可以在/proc/PID/maps中發(fā)現(xiàn)這兩種映射。

共享文件映射

當(dāng)多個(gè)進(jìn)程創(chuàng)建了同一個(gè)文件區(qū)域的共享映射時(shí),它們會共享同樣的內(nèi)存物理分頁。此外,對映射內(nèi)容的變更將會反應(yīng)到文件上。共享文件映射有兩個(gè)用途:內(nèi)存映射 IO 和 IPC。

1436603e-d40d-11ed-bfe3-dac502259ad0.png

內(nèi)存映射 IO

由于共享文件映射中的內(nèi)容是從文件初始化而來,并且對映射內(nèi)容所做的變更都會自動反應(yīng)到文件上,因此可以簡單的通過訪問內(nèi)存中的字節(jié)來執(zhí)行文件 IO,而 依靠內(nèi)核來確保對內(nèi)存的變更會被傳遞到映射文件上。(一般來說,一個(gè)程序會定義一個(gè)結(jié)構(gòu)化數(shù)據(jù)類型來與磁盤文件中的內(nèi)容對應(yīng)起來,然后使用該數(shù)據(jù)類型來轉(zhuǎn)換映射的內(nèi)容)這項(xiàng)技術(shù)被稱為內(nèi)存映射 IO,它是使用read()和write()來訪問文件內(nèi)容這種方法的替代方案。

內(nèi)存映射 IO 具備兩個(gè)潛在的優(yōu)勢:

使用內(nèi)存訪問來代替read()和write()系統(tǒng)調(diào)用能夠簡化一些應(yīng)用程序的邏輯

在一些情況下,它能夠比使用傳統(tǒng)的 IO 系統(tǒng)調(diào)用執(zhí)行文件 IO 這種做法提供更好的性能

內(nèi)存映射 IO 之所以能夠帶來性能的優(yōu)勢的原因如下:

正常的read()或者write()需要兩次傳輸,一次是在文件和內(nèi)核高速緩沖區(qū)之間,另一次是在高速緩沖區(qū)和用戶空間緩沖區(qū)之間,使用mmap()就無需第二次傳輸:

對于輸入:一旦內(nèi)核將相應(yīng)的文件塊映射進(jìn)內(nèi)存之后用戶進(jìn)程就能夠使用這些數(shù)據(jù)

對于輸出:用戶進(jìn)程僅僅需要修改內(nèi)存中的內(nèi)容,然后可以依靠內(nèi)核內(nèi)存管理器來自動更新底層的文件

mmap()還能夠通過減少所需使用的內(nèi)存來提升性能,當(dāng)使用read()或write()時(shí),數(shù)據(jù)將被保存在兩個(gè)緩沖區(qū)中:一個(gè)位于用戶空間,另一個(gè)位于內(nèi)核空間,當(dāng)使用mmap()時(shí),內(nèi)核空間和用戶空間會共享同一個(gè)緩沖區(qū),此外,如果多個(gè)進(jìn)程正在在同一個(gè)文件上執(zhí)行 IO,那么它們通過使用mmap()就能夠共享同一個(gè)內(nèi)核緩沖區(qū),從而又能夠節(jié)省內(nèi)存的消耗

內(nèi)存映射 IO 所帶來的性能優(yōu)勢在大型文件執(zhí)行重復(fù)隨機(jī)訪問時(shí)最有可能提現(xiàn)出來。對于小數(shù)據(jù)量 IO 來講,內(nèi)存映射 IO 的開銷實(shí)際上要比簡單的read()或write()大。

使用共享文件映射的 IPC

由于所有使用同樣文件區(qū)域的共享映射的進(jìn)程共享同樣的內(nèi)存物理分頁,因此共享文件映射的第二個(gè)用途就是作為一種 IPC 方法。這種共享內(nèi)存區(qū)域與 System V 共享內(nèi)存對象之間的區(qū)別在于,區(qū)域上的內(nèi)容的更變會反應(yīng)到底層的映射文件上。這種特性對于那些需要共享內(nèi)容在應(yīng)用程序或系統(tǒng)重啟時(shí)能夠持久化的應(yīng)用程序來說是非常有用的。

邊界情況

在很多情況下,一個(gè)映射的大小是系統(tǒng)分頁大小的整數(shù)倍,并且映射會完全落入映射文件的范圍之內(nèi),但這個(gè)要求不是必須的。

映射完全落入映射文件的范圍之內(nèi)但區(qū)域大小并不是系統(tǒng)分頁大小的一個(gè)整數(shù)倍的情況,假設(shè)分頁大小為 4096 字節(jié):

144f62be-d40d-11ed-bfe3-dac502259ad0.png

由于映射大小不是系統(tǒng)分頁大小的整數(shù)倍,因此它會被向上舍入到系統(tǒng)分頁大小的下一個(gè)整數(shù)倍。當(dāng)映射擴(kuò)充過了底層文件的結(jié)尾處時(shí),情況更加復(fù)雜:

147722cc-d40d-11ed-bfe3-dac502259ad0.png

雖然向上舍入的字節(jié)是可以訪問的,但它們不會被映射到底層文件上,這部分會被初始化為0。

通過擴(kuò)展文件的大小(如使用ftruncate()或write()) 可以使得這種映射中之前不可訪問的部分變得可用。

內(nèi)存保護(hù)和文件訪問模式交互

一般從原則來講:

PROT_READ和PROT_EXEC保護(hù)要求被映射的文件使用O_RDONLY或O_RDWR打開

PROT_WRITE保護(hù)要求被映射的文件使用O_WRONLY或O_RDWR打開

但是,由于一些硬件架構(gòu)提供的內(nèi)存保護(hù)粒度有限,因此情況會變得復(fù)雜:

所有內(nèi)存保護(hù)組合與使用O_RDWR標(biāo)記打開文件是兼容的

沒有內(nèi)存保護(hù)組合

使用O_RDONLY標(biāo)記打開一個(gè)文件的結(jié)果依賴于在調(diào)用mmap()時(shí)是否指定了可以指定任意的內(nèi)存保護(hù)組合,因?yàn)閷τ谝粋€(gè)MAP_PRIVATE分頁內(nèi)容做出的變更不會被寫入到文件中

同步映射區(qū)域

內(nèi)核會自動將發(fā)生在MAP_SHARED映射內(nèi)容上的變更寫入到底層文件中的,但默認(rèn)情況下,內(nèi)核不保證這種同步操作會在何時(shí)發(fā)生。

#include

intmsync(void*addr,size_tlength,intflags);

msync()系統(tǒng)調(diào)用讓應(yīng)用程序能夠顯式地控制何時(shí)完成共享映射與映射文件之間的同步

addr和length指定了需同步的內(nèi)存區(qū)域的起始地址和大小

flags參數(shù)的可取值:

MS_SYNC:執(zhí)行一個(gè)同步的文件寫入。這個(gè)調(diào)用會阻塞直到內(nèi)存區(qū)域中所有被修改過的分頁被寫入底層磁盤為止

MS_ASYNC:執(zhí)行一個(gè)異步寫入。內(nèi)存區(qū)域中被修改的分頁會在后面某個(gè)時(shí)刻被寫入磁盤并立即在相應(yīng)文件區(qū)域中執(zhí)行read()的其他進(jìn)程可見

另外一種區(qū)分這兩個(gè)值的方式可以表述為,在MS_SYNC操作之后,內(nèi)存區(qū)域會與磁盤同步,而在MS_ASYNC之后,內(nèi)存區(qū)域僅僅是與內(nèi)核高速緩沖區(qū)同步。

flags參數(shù)還可以加上下面這個(gè)值:

MS_INVALIDATE:使映射數(shù)據(jù)的緩存副本失效,當(dāng)內(nèi)存區(qū)域中所有被修改過的分頁被同步到文件中之后,內(nèi)存區(qū)域中所有與底層文件不一致的分頁會被標(biāo)記為無效,當(dāng)下次引用這些分頁時(shí)會從文件的相應(yīng)位置處復(fù)制相應(yīng)的分頁內(nèi)容,其結(jié)果是其他進(jìn)程對文件做出的所有更新將會在內(nèi)存區(qū)域中可見

其他mmap()標(biāo)記

mmap()的flags參數(shù)的位掩碼值:

1498cd00-d40d-11ed-bfe3-dac502259ad0.png

MAP_ANONYMOUS:創(chuàng)建一個(gè)匿名映射,即沒有底層文件對應(yīng)的映射

MAP_HUGETLB:與SHM_HUGETLB標(biāo)記在 System V 共享內(nèi)存段中所起的作用一樣

MAP_LOCKED:按照mlock()的方式預(yù)加載映射分頁并將映射分頁鎖進(jìn)內(nèi)存

MAP_NORESERVE:這個(gè)標(biāo)記用來控制是否提前為映射的交換空間執(zhí)行預(yù)留操作

MAP_POPULATE:填充一個(gè)映射的分頁,對于文件映射來將,這將會在文件上執(zhí)行一個(gè)超前讀取

MAP_UNINITIALIZED:指定一個(gè)標(biāo)記會防止一個(gè)匿名映射被清零,它能夠帶來性能上的提升,但同時(shí)也帶來了安全風(fēng)險(xiǎn),因?yàn)橐逊峙涞姆猪撝锌赡軙弦粋€(gè)進(jìn)程留下來的敏感信息

匿名映射

匿名映射是沒有對應(yīng)文件的一種映射。

MAP_ANONYMOUS和/dev/zero

在Linux 中,使用mmap()創(chuàng)建匿名映射存在2種不同但等級的方法:

在flags中指定MS_ANONYMOUS并將fd指定為 -1

打開/dev/zero設(shè)備文件,并將得到的文件描述符傳遞給mmap()

不管使用哪種方法,得到映射中的字節(jié)會被初始化為 0。offset參數(shù)都將會被忽略,因?yàn)闆]有底層文件,所以也無法指定偏移量。

MAP_PRIVATE匿名映射

MAP_PRIVATE匿名映射用來分配進(jìn)程私有的內(nèi)存塊并將其中的內(nèi)容初始化為 0。

下面的代碼使用/dev/zero技術(shù)創(chuàng)建一個(gè)MAP_PRIVATE匿名映射:

fd=open("/dev/zero",O_RDWR);
if(fd==-1)
errExit("open");
addr=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
if(addr==MAP_FAILED)
errExit("mmap");

MAP_SHARED匿名映射

MAP_SHARED匿名映射允許相關(guān)進(jìn)程共享一塊內(nèi)存區(qū)域而無需一個(gè)對應(yīng)的映射文件。

使用MAP_ANONYMOUS技術(shù)創(chuàng)建一個(gè)MAP_SHARED匿名映射:

addr=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,-1,0);
if(addr==MAP_FAILED)
errExit("mmap");

重新映射一個(gè)映射區(qū)域

UNIX 實(shí)現(xiàn)上一旦映射被創(chuàng)建,其位置和大小就無法改變了。Linux 提供mremap()系統(tǒng)調(diào)用可以執(zhí)行此類變更。

#define_GNU_SOURCE
#include

void*mremap(void*old_address,size_told_size,size_tnew_size,intflags,.../*void*new_address*/);

old_address和old_size指定了需要擴(kuò)展或收縮的既有映射的位置和大小

在執(zhí)行重映射的過程中內(nèi)核可能會為映射進(jìn)程的虛擬地址空間中重新指定一個(gè)位置,是否允許這種行為由flag參數(shù)來控制:

MREMAP_MAYMOVE:內(nèi)核可能會為映射在進(jìn)程的虛擬地址空間中重新指定一個(gè)位置,如果沒有指定這個(gè)標(biāo)記,并且在當(dāng)前位置處沒有足夠的空間來擴(kuò)展這個(gè)映射,那么就返回ENOMEM錯誤

MREMAP_FIXED:只能和MREMAP_MAYMOVE一起使用,如果指定了這個(gè)標(biāo)記,那么mremap()會接收一個(gè)額外的參數(shù)void *new_address,該參數(shù)指定了一個(gè)分頁對齊的地址,并且映射將會被遷移至該地址處

mremap()成功時(shí)返回映射的起始地址

MAP_NORESERVE和過度利用交換空間

一些應(yīng)用程序會創(chuàng)建大的映射,但之用映射區(qū)域中的一小部分。如果內(nèi)核總是為此類映射分配(或者預(yù)留)足夠的交換空間,那么很多交換空間可能會被浪費(fèi)。相反,內(nèi)核可以只在需要的時(shí)候用到映射分頁的時(shí)候(即當(dāng)應(yīng)用程序訪問分頁時(shí))為它們預(yù)留交換空間,這種方法稱為懶交換預(yù)留。它的一個(gè)優(yōu)點(diǎn)是,應(yīng)用程序總共使用的虛擬內(nèi)存量能夠超過 RAM 加上交換空間的總量。

懶交換預(yù)留允許交換空間被過度利用,這種方式能夠很好地工作,只要所有進(jìn)程都不試圖訪問整個(gè)映射,但如果所有應(yīng)用程序都試圖訪問整個(gè)映射,那么 RAM 和交換空間就會被耗盡。在這種情況下,內(nèi)核會通過殺死系統(tǒng)中的一個(gè)或多個(gè)進(jìn)程來降低內(nèi)存壓力。

內(nèi)核如何處理交換空間的預(yù)留是由調(diào)用mmap()時(shí)是否使用了MAP_NORESERVE標(biāo)記以及影響系統(tǒng)層面的交換空間過度利用操作的/proc接口來控制的:

14bbac12-d40d-11ed-bfe3-dac502259ad0.png

Linux 特有的/proc/sys/vm/overcommit_memory文件包含了一個(gè)整數(shù)值,它控制著內(nèi)核對交換空間過度利用的處理。

過度利用監(jiān)控只適用于下面的這些映射:

私有可寫映射,這種映射的交換開銷等于所有使用該映射的進(jìn)程為該映射所分配的空間總和

共享匿名映射,這種映射的交換開銷等于映射的大小

為只讀私有映射預(yù)留交換空間是沒有必要的,因?yàn)橛成渲械膬?nèi)容是不可變更的,從而無需使用交換空間,共享文件映射也不需要使用交換空間,因?yàn)橛成湮募旧頁?dān)當(dāng)了映射的交換空間。

當(dāng)一個(gè)子進(jìn)程在fork()調(diào)用中繼承了一個(gè)映射時(shí),它將會繼承該映射的MAP_NORESERVER設(shè)置。

OOM 殺手

內(nèi)核中用來在內(nèi)存被耗盡時(shí)選擇殺死哪個(gè)進(jìn)程的代碼通常被稱為out-of-memory(OOM) 殺手,OOM 殺手會嘗試選擇殺死能夠緩解內(nèi)存消耗情況的最佳進(jìn)程,這里的 “最佳” 是由一組因素決定的。如一個(gè)進(jìn)程消耗的內(nèi)存越多,它越可能成為 OOM 殺手選擇的目標(biāo)。內(nèi)核一般不會殺死下列進(jìn)程:

特權(quán)進(jìn)程,因?yàn)樗鼈兛赡苷趫?zhí)行重要的任務(wù)

正在訪問裸設(shè)備的進(jìn)程,因?yàn)闅⑺浪鼈兛赡軙?dǎo)致設(shè)備處理一個(gè)不可用的狀態(tài)

已經(jīng)運(yùn)行了很長時(shí)間或已經(jīng)消耗了大量 CPU 的進(jìn)程,因?yàn)闅⑺浪鼈兛赡軙?dǎo)致丟失很多 "工作"

為殺死一個(gè)被選中的進(jìn)程,OOM 殺手會向其發(fā)送一個(gè)SIGKILL信號。

Linux 特有的/proc/PID/oom_score文件給出了在需要調(diào)用 OOM 殺手時(shí)內(nèi)核賦予給每個(gè)進(jìn)程的權(quán)重。在這個(gè)文件中,進(jìn)程的權(quán)重越大,那么在必要的時(shí)候被 OOM 殺手選中的可能性就越大。

Linux 特有的/proc/PID/oom_adj文件能夠用來影響一個(gè)進(jìn)程的oom_score值,這個(gè)文件可以被設(shè)置成[-16,15]之間的任意一個(gè)值,其中負(fù)數(shù)會減小oom_score的值,而整數(shù)則會增大oom_score的值,-17會完全將進(jìn)程從 OOM 殺手的候選目標(biāo)中刪除。

MAP_FIXED標(biāo)記

在mmap()中的flags參數(shù)指定MAP_FIXED標(biāo)記會強(qiáng)制內(nèi)核原樣地解釋addr中的地址,而不是只將其作為一種提示信息。如果指定了MAP_FIXED,那么addr就必須是分頁對齊的。

一般在一個(gè)可移植的應(yīng)用程序不應(yīng)該使用MAP_FIXED,并且需要將addr指定為NULL。

如果在調(diào)用mmap()時(shí)指定了MAX_FIXED,并且內(nèi)存區(qū)域的起始位置為addr,覆蓋的 length 字節(jié)與之前的映射分頁重疊了,那么重疊的分頁會被新映射替換,使用這個(gè)特性可以可移植地將一個(gè)文件或者多個(gè)文件的多個(gè)部分映射進(jìn)一塊連續(xù)的內(nèi)存區(qū)域:

使用mmap()創(chuàng)建一個(gè)匿名映射,addr指定為NULL,并且不指定MAP_FIXED,這樣就允許內(nèi)核為映射選擇一個(gè)地址

使用一系列指定了MAP_FIXED標(biāo)記的mmap()調(diào)用來將文件區(qū)域映射進(jìn)在上一步中創(chuàng)建的映射的不同部分

從 Linux 2.6 開始,使用remap_file_pages()系統(tǒng)調(diào)用也能夠取得同樣的效果,但是使用MAP_FIXED的可移植性更強(qiáng),因?yàn)閞emap_file_pages()是 Linux 特有的。

非線性映射

使用mmap()創(chuàng)建的文件映射是連續(xù)的:映射文件的分頁與內(nèi)存區(qū)域的分頁存在一個(gè)順序的,一對一的對應(yīng)關(guān)系。

但是一些應(yīng)用程序需要創(chuàng)建大量的非線性映射:

14de24b8-d40d-11ed-bfe3-dac502259ad0.png

使用多個(gè)帶MAP_FIXED標(biāo)記的mmap()調(diào)用,然而這種方法的伸縮性不夠好,其問題在于其中每個(gè)mmap()調(diào)用都會創(chuàng)建一個(gè)獨(dú)立的內(nèi)核虛擬內(nèi)存區(qū)域 VMA 數(shù)據(jù)結(jié)構(gòu)。每個(gè) VMA 的配置需要花費(fèi)時(shí)間并且會消耗一些不可交換的內(nèi)核內(nèi)存。此外,大量的 VMA 會降低虛擬內(nèi)存管理器的性能。/proc/PID/maps中的一行表示一個(gè) VMA。

Linux 提供的remap_file_pages()調(diào)用來在無需創(chuàng)建多個(gè) VMA 的情況下創(chuàng)建非線性映射,具體如下:

使用mmap()創(chuàng)建一個(gè)映射

使用一個(gè)或者多個(gè)remap_file_pages()調(diào)用來調(diào)整內(nèi)存分頁和文件分頁之間的對應(yīng)關(guān)系

#define_GNU_SOURCE
#include

intremap_file_pages(void*addr,size_tsize,intprot,size_tpgoff,intflags);

remap_file_pages()所做的工作是操作進(jìn)程的頁表,僅適用于MAP_SHARED映射

pgoff和size參數(shù)標(biāo)識了一個(gè)在內(nèi)存中的位置待改變的文件區(qū)域,pgoff參數(shù)指定了文件區(qū)域的起始位置,其單位是系統(tǒng)分頁代銷(sysconf(_SC_PAGESIZE)的返回值),size參數(shù)指定了文件區(qū)域的長度,其單位是字節(jié)

addr參數(shù)起兩個(gè)作用:

它標(biāo)識了分頁需要調(diào)整的既有映射,addr必須是一個(gè)位于之前通過mmap()映射的區(qū)域中的地址

它指定了通過pgoff和size標(biāo)識出的文件分頁所處的內(nèi)存地址

addr和size都應(yīng)該是系統(tǒng)分頁大小的整數(shù)倍,如果不是,那么它們會被向下舍入到最近的分頁大小的整數(shù)倍

prot參數(shù)會被忽略,必須指定為 0

flags參數(shù)當(dāng)前未被使用

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11335

    瀏覽量

    210067
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3045

    瀏覽量

    74200
  • 文件
    +關(guān)注

    關(guān)注

    1

    文章

    569

    瀏覽量

    24791
  • 共享內(nèi)存
    +關(guān)注

    關(guān)注

    0

    文章

    16

    瀏覽量

    8329
  • 進(jìn)程
    +關(guān)注

    關(guān)注

    0

    文章

    204

    瀏覽量

    13973

原文標(biāo)題:Linux應(yīng)用開發(fā)之共享內(nèi)存

文章出處:【微信號:嵌入式應(yīng)用研究院,微信公眾號:嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Linux下進(jìn)程間如何實(shí)現(xiàn)共享內(nèi)存通信

    這次我們來講一下Linux進(jìn)程通信中重要的通信方式:共享內(nèi)存作為Linux軟件開發(fā)攻城獅,進(jìn)程間通信是必須熟練掌握的重要技能,而
    發(fā)表于 04-26 17:14 ?702次閱讀

    關(guān)于共享內(nèi)存的函數(shù)shmget()

    Linux進(jìn)程間通信源碼剖析,共享內(nèi)存(shmget函數(shù)詳解)標(biāo)簽: linuxstruct數(shù)據(jù)結(jié)構(gòu)存儲systemobject2010-12-22 23:18 8567人閱讀 評論(1
    發(fā)表于 09-24 15:47

    Linux進(jìn)程間通信——使用共享內(nèi)存

    Linux進(jìn)程間通信——使用共享內(nèi)存 圖文詳情見附件
    發(fā)表于 11-21 10:53

    請問IPC安裝完后的共享內(nèi)存是誰給劃定的?,LINUX和SYS/BISO的messageQ是怎樣實(shí)現(xiàn)共享內(nèi)存的同步的?

    一直沒看懂,IPC安裝完后的共享內(nèi)存是誰給劃定的,LINUX和SYS/BISO的messageQ是怎樣實(shí)現(xiàn)共享內(nèi)存的同步的(因?yàn)閙essag
    發(fā)表于 07-24 08:51

    linux如何共享內(nèi)存實(shí)驗(yàn)

    更多技術(shù)文章地址:http://www.hqyj.com/news/emb211.htm?lcg-opendv  1.實(shí)驗(yàn)?zāi)康摹 ⊥ㄟ^編寫共享內(nèi)存實(shí)驗(yàn),進(jìn)一步了解使用共享內(nèi)存的具體步驟
    發(fā)表于 06-08 07:47

    linux中的共享內(nèi)存是指什么?共享內(nèi)存有哪些優(yōu)缺點(diǎn)

    什么是進(jìn)程?進(jìn)程有哪幾種狀態(tài)?共享內(nèi)存是指什么?共享內(nèi)存有哪些優(yōu)缺點(diǎn)?
    發(fā)表于 02-28 09:32

    Linux Ril中使用共享內(nèi)存交互相關(guān)資料分享

    1、移植RIL到ART-Smart共享內(nèi)存簡析本次移植的linux ril中,rild作為一個(gè)client與modem通過AT命令交互,同時(shí)也作為一個(gè)server與應(yīng)用通信。在/li
    發(fā)表于 07-01 10:17

    進(jìn)程間通信共享內(nèi)存

    8.5.1 共享內(nèi)存概述 可以說,共享內(nèi)存是一種最為高效的進(jìn)程間通信方式。因?yàn)檫M(jìn)程可以直接讀寫內(nèi)存,不需要任何數(shù)據(jù)的復(fù)制。為了在多個(gè)進(jìn)程間交
    發(fā)表于 10-18 16:08 ?1次下載
    進(jìn)程間通信<b class='flag-5'>之</b><b class='flag-5'>共享</b><b class='flag-5'>內(nèi)存</b>

    共享內(nèi)存IPC原理,Linux進(jìn)程間如何共享內(nèi)存

    共享內(nèi)存是在內(nèi)存中單獨(dú)開辟的一段內(nèi)存空間,這段內(nèi)存空間有自己特有的數(shù)據(jù)結(jié)構(gòu),包括訪問權(quán)限、大小和最近訪問的時(shí)間等。該數(shù)據(jù)結(jié)構(gòu)定義如下
    的頭像 發(fā)表于 07-16 13:43 ?8662次閱讀
    <b class='flag-5'>共享</b><b class='flag-5'>內(nèi)存</b>IPC原理,<b class='flag-5'>Linux</b>進(jìn)程間如何<b class='flag-5'>共享</b><b class='flag-5'>內(nèi)存</b>?

    你知道Linux共享內(nèi)存與tmpfs文件系統(tǒng)是什么樣?

    共享內(nèi)存主要用于進(jìn)程間通信,Linux有兩種共享內(nèi)存(Shared Memory)機(jī)制
    發(fā)表于 05-04 17:33 ?2153次閱讀
    你知道<b class='flag-5'>Linux</b>的<b class='flag-5'>共享</b><b class='flag-5'>內(nèi)存</b>與tmpfs文件系統(tǒng)是什么樣?

    Linux IPC System V 共享內(nèi)存

    SHM_REMAP?(Linux-specific)表示如果要映射的共享內(nèi)存已經(jīng)有現(xiàn)存的內(nèi)存,那么就將舊的替換//掛接共享
    發(fā)表于 04-02 14:46 ?281次閱讀

    深入剖析Linux共享內(nèi)存原理

    不同進(jìn)程之間進(jìn)行通信,需要讓不同進(jìn)程共享相同的物理內(nèi)存,Linux通過? 共享內(nèi)存 ?來實(shí)現(xiàn)這個(gè)功能。下面先來介紹一下
    的頭像 發(fā)表于 10-30 09:52 ?2317次閱讀
    深入剖析<b class='flag-5'>Linux</b><b class='flag-5'>共享</b><b class='flag-5'>內(nèi)存</b>原理

    Linux系統(tǒng)的共享內(nèi)存的使用

    但有時(shí)候?yàn)榱俗尣煌M(jìn)程之間進(jìn)行通信,需要讓不同進(jìn)程共享相同的物理內(nèi)存,Linux通過 共享內(nèi)存 來實(shí)現(xiàn)這個(gè)功能。下面先來介紹一下
    的頭像 發(fā)表于 11-14 11:55 ?1347次閱讀

    Linux進(jìn)程間共享內(nèi)存通信時(shí)如何同步?

    今天我們來講講進(jìn)程間使用共享內(nèi)存通信時(shí)為了確保數(shù)據(jù)的正確,如何進(jìn)行同步?
    的頭像 發(fā)表于 05-11 18:25 ?1599次閱讀

    Linux進(jìn)程間如何實(shí)現(xiàn)共享內(nèi)存通信

    在上面的例程中,我們首先使用ftok()函數(shù)生成一個(gè)key值作為共享內(nèi)存的標(biāo)識符。然后使用shmget()函數(shù)創(chuàng)建共享內(nèi)存區(qū)域,shmaddr指向
    發(fā)表于 06-19 09:55 ?653次閱讀