成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

解鎖Linux共享內(nèi)存:進(jìn)程間通信的超高速通道

系統(tǒng) Linux
Linux 采用虛擬內(nèi)存管理機(jī)制,為每個(gè)進(jìn)程分配獨(dú)立的虛擬地址空間。這意味著每個(gè)進(jìn)程都可以認(rèn)為自己擁有 4GB(32 位系統(tǒng))或更大(64 位系統(tǒng))的連續(xù)內(nèi)存空間,而不必?fù)?dān)心物理內(nèi)存的實(shí)際大小和其他進(jìn)程的干擾。

在Linux系統(tǒng)的進(jìn)程間通信 “江湖” 中,眾多通信方式各顯神通。管道,如同隱秘的地下通道,讓有親緣關(guān)系的進(jìn)程能夠悄然傳遞信息;消息隊(duì)列則似郵局,進(jìn)程可投遞和接收格式化的消息包裹。然而,有一種通信方式卻以其獨(dú)特的 “高速” 特性脫穎而出,它就是共享內(nèi)存。想象一下,進(jìn)程們?cè)靖髯陨钤讵?dú)立的 “小天地” 里,有著自己專屬的虛擬地址空間。但共享內(nèi)存卻如同神奇的 “任意門(mén)”,打破了進(jìn)程間的隔閡,讓多個(gè)進(jìn)程能夠直接訪問(wèn)同一塊內(nèi)存區(qū)域。這種獨(dú)特的機(jī)制,使得數(shù)據(jù)在進(jìn)程間的傳遞無(wú)需繁瑣的復(fù)制過(guò)程,極大地提升了通信效率,堪稱進(jìn)程間通信的超高速通道。

在使用共享內(nèi)存時(shí),需要注意對(duì)于并發(fā)訪問(wèn)的控制,如使用鎖或其他同步機(jī)制來(lái)保證數(shù)據(jù)的一致性和安全性。此外,還需要謹(jǐn)慎處理資源管理問(wèn)題,確保正確地釋放共享內(nèi)存以避免內(nèi)存泄漏。接下來(lái),就讓我們一同深入探索 Linux 共享內(nèi)存的奧秘,揭開(kāi)它神秘的面紗,看看它是如何在 Linux 系統(tǒng)中發(fā)揮這一獨(dú)特且強(qiáng)大的作用 。

一、Linux內(nèi)存管理初窺

1.1 虛擬內(nèi)存與物理內(nèi)存

Linux 采用虛擬內(nèi)存管理機(jī)制,為每個(gè)進(jìn)程分配獨(dú)立的虛擬地址空間。這意味著每個(gè)進(jìn)程都可以認(rèn)為自己擁有 4GB(32 位系統(tǒng))或更大(64 位系統(tǒng))的連續(xù)內(nèi)存空間,而不必?fù)?dān)心物理內(nèi)存的實(shí)際大小和其他進(jìn)程的干擾。虛擬內(nèi)存與物理內(nèi)存通過(guò)內(nèi)存映射機(jī)制建立聯(lián)系,進(jìn)程訪問(wèn)的虛擬地址會(huì)被轉(zhuǎn)換為實(shí)際的物理地址。

舉個(gè)例子,當(dāng)你在 Linux 系統(tǒng)上同時(shí)運(yùn)行多個(gè)程序時(shí),每個(gè)程序都覺(jué)得自己獨(dú)占了大量?jī)?nèi)存,但實(shí)際上物理內(nèi)存是有限的。通過(guò)虛擬內(nèi)存管理,操作系統(tǒng)可以巧妙地在物理內(nèi)存和磁盤(pán)之間交換數(shù)據(jù),使得系統(tǒng)能夠運(yùn)行比物理內(nèi)存更大的程序集。就好比一個(gè)小型圖書(shū)館,雖然書(shū)架空間有限(物理內(nèi)存),但通過(guò)一個(gè)龐大的倉(cāng)庫(kù)(磁盤(pán))來(lái)存放暫時(shí)不用的書(shū)籍(數(shù)據(jù)),當(dāng)讀者需要某本書(shū)時(shí),管理員(操作系統(tǒng))會(huì)從倉(cāng)庫(kù)中取出并放到書(shū)架上供讀者使用。

1.2 內(nèi)存分頁(yè)

為了更高效地管理內(nèi)存,Linux 采用內(nèi)存分頁(yè)機(jī)制。將虛擬內(nèi)存和物理內(nèi)存按照固定大小的頁(yè)(通常為 4KB)進(jìn)行劃分,頁(yè)是內(nèi)存管理的最小單位。操作系統(tǒng)通過(guò)維護(hù)頁(yè)表來(lái)記錄虛擬頁(yè)和物理頁(yè)之間的映射關(guān)系,當(dāng)進(jìn)程訪問(wèn)某個(gè)虛擬地址時(shí),CPU 會(huì)根據(jù)頁(yè)表將其轉(zhuǎn)換為對(duì)應(yīng)的物理地址。

想象一下,內(nèi)存就像一本巨大的書(shū)籍,每一頁(yè)都有固定的頁(yè)碼(虛擬頁(yè)號(hào)和物理頁(yè)號(hào))。當(dāng)你想要查找書(shū)中的某個(gè)內(nèi)容(訪問(wèn)內(nèi)存數(shù)據(jù))時(shí),通過(guò)目錄(頁(yè)表)可以快速定位到具體的頁(yè)碼,從而找到所需內(nèi)容。

1.3 內(nèi)存分配與回收

內(nèi)存管理包括內(nèi)存的分配和回收。當(dāng)進(jìn)程需要內(nèi)存時(shí),它會(huì)向操作系統(tǒng)請(qǐng)求分配內(nèi)存,操作系統(tǒng)根據(jù)一定的算法從空閑內(nèi)存中分配相應(yīng)大小的內(nèi)存塊給進(jìn)程;當(dāng)進(jìn)程不再需要某些內(nèi)存時(shí),它會(huì)將這些內(nèi)存釋放回操作系統(tǒng),以便操作系統(tǒng)重新分配給其他需要的進(jìn)程。

例如,當(dāng)你在 Linux 系統(tǒng)上運(yùn)行一個(gè)新的程序時(shí),程序會(huì)向操作系統(tǒng)申請(qǐng)內(nèi)存來(lái)存放代碼和數(shù)據(jù)。操作系統(tǒng)會(huì)從空閑內(nèi)存池中找到合適大小的內(nèi)存塊分配給該程序。當(dāng)程序運(yùn)行結(jié)束后,它占用的內(nèi)存會(huì)被操作系統(tǒng)回收,重新加入空閑內(nèi)存池,等待下一個(gè)程序的請(qǐng)求。

二、共享內(nèi)存詳解

2.1 共享內(nèi)存是什么

共享內(nèi)存是一種高效的進(jìn)程間通信(IPC,Inter - Process Communication)機(jī)制,它允許兩個(gè)或多個(gè)進(jìn)程直接訪問(wèn)同一塊物理內(nèi)存區(qū)域。簡(jiǎn)單來(lái)說(shuō),就好比多個(gè)房間(進(jìn)程)都有一扇門(mén)可以直接通向同一個(gè)儲(chǔ)物間(共享內(nèi)存),大家可以直接在這個(gè)儲(chǔ)物間里存放和取用物品(數(shù)據(jù)) 。

在 Linux 系統(tǒng)中,共享內(nèi)存的實(shí)現(xiàn)依賴于操作系統(tǒng)的支持。當(dāng)一個(gè)進(jìn)程創(chuàng)建共享內(nèi)存時(shí),操作系統(tǒng)會(huì)在物理內(nèi)存中分配一塊區(qū)域,并為這塊區(qū)域生成一個(gè)唯一的標(biāo)識(shí)符。其他進(jìn)程可以通過(guò)這個(gè)標(biāo)識(shí)符將該共享內(nèi)存映射到自己的虛擬地址空間中,從而實(shí)現(xiàn)對(duì)共享內(nèi)存的訪問(wèn)。

2.2 為什么要用共享內(nèi)存

在進(jìn)程間通信的眾多方式中,共享內(nèi)存之所以備受青睞,是因?yàn)樗哂衅渌绞诫y以比擬的優(yōu)勢(shì)。

首先,與管道和消息隊(duì)列等通信方式相比,共享內(nèi)存的速度極快。管道和消息隊(duì)列在數(shù)據(jù)傳輸時(shí),需要進(jìn)行多次數(shù)據(jù)拷貝,數(shù)據(jù)要在內(nèi)核空間和用戶空間之間來(lái)回傳遞,這會(huì)消耗大量的時(shí)間和系統(tǒng)資源。而共享內(nèi)存則不同,多個(gè)進(jìn)程直接訪問(wèn)同一塊內(nèi)存區(qū)域,數(shù)據(jù)不需要在不同進(jìn)程的地址空間之間拷貝,大大減少了數(shù)據(jù)傳輸?shù)拈_(kāi)銷(xiāo),提高了通信效率。例如,在一個(gè)實(shí)時(shí)數(shù)據(jù)處理系統(tǒng)中,多個(gè)進(jìn)程需要頻繁地交換大量數(shù)據(jù),如果使用管道或消息隊(duì)列,可能會(huì)因?yàn)閿?shù)據(jù)傳輸?shù)难舆t而影響系統(tǒng)的實(shí)時(shí)性;而使用共享內(nèi)存,就可以快速地傳遞數(shù)據(jù),滿足系統(tǒng)對(duì)實(shí)時(shí)性的要求。

其次,共享內(nèi)存的使用非常靈活。它可以用于任何類型的進(jìn)程間通信,無(wú)論是有親緣關(guān)系的進(jìn)程(如父子進(jìn)程)還是毫無(wú)關(guān)系的進(jìn)程,都可以通過(guò)共享內(nèi)存進(jìn)行數(shù)據(jù)共享和交互。而且,共享內(nèi)存區(qū)域可以存儲(chǔ)各種類型的數(shù)據(jù)結(jié)構(gòu),開(kāi)發(fā)者可以根據(jù)實(shí)際需求自定義數(shù)據(jù)格式,這為復(fù)雜應(yīng)用場(chǎng)景的實(shí)現(xiàn)提供了便利。比如,在一個(gè)多進(jìn)程協(xié)作的圖形處理程序中,不同進(jìn)程可以通過(guò)共享內(nèi)存共享圖像數(shù)據(jù)和處理參數(shù),各自完成不同的處理任務(wù),如一個(gè)進(jìn)程負(fù)責(zé)圖像的濾波處理,另一個(gè)進(jìn)程負(fù)責(zé)圖像的邊緣檢測(cè),共享內(nèi)存使得它們能夠高效地協(xié)同工作。

此外,共享內(nèi)存還能有效地節(jié)省內(nèi)存資源。多個(gè)進(jìn)程共享同一塊內(nèi)存區(qū)域,而不是每個(gè)進(jìn)程都單獨(dú)開(kāi)辟一塊內(nèi)存來(lái)存儲(chǔ)相同的數(shù)據(jù),這在內(nèi)存資源有限的情況下顯得尤為重要。例如,在一個(gè)服務(wù)器系統(tǒng)中,可能同時(shí)有多個(gè)進(jìn)程需要訪問(wèn)一些公共的配置信息或緩存數(shù)據(jù),使用共享內(nèi)存可以避免這些數(shù)據(jù)在每個(gè)進(jìn)程中重復(fù)存儲(chǔ),從而提高內(nèi)存的利用率。

2.3 共享內(nèi)存原理

共享內(nèi)存是System V版本的最后一個(gè)進(jìn)程間通信方式。共享內(nèi)存,顧名思義就是允許兩個(gè)不相關(guān)的進(jìn)程訪問(wèn)同一個(gè)邏輯內(nèi)存,共享內(nèi)存是兩個(gè)正在運(yùn)行的進(jìn)程之間共享和傳遞數(shù)據(jù)的一種非常有效的方式。不同進(jìn)程之間共享的內(nèi)存通常為同一段物理內(nèi)存。進(jìn)程可以將同一段物理內(nèi)存連接到他們自己的地址空間中,所有的進(jìn)程都可以訪問(wèn)共享內(nèi)存中的地址。如果某個(gè)進(jìn)程向共享內(nèi)存寫(xiě)入數(shù)據(jù),所做的改動(dòng)將立即影響到可以訪問(wèn)同一段共享內(nèi)存的任何其他進(jìn)程。

特別提醒:共享內(nèi)存并未提供同步機(jī)制,也就是說(shuō),在第一個(gè)進(jìn)程結(jié)束對(duì)共享內(nèi)存的寫(xiě)操作之前,并無(wú)自動(dòng)機(jī)制可以阻止第二個(gè)進(jìn)程開(kāi)始對(duì)它進(jìn)行讀取,所以我們通常需要用其他的機(jī)制來(lái)同步對(duì)共享內(nèi)存的訪問(wèn),例如信號(hào)量。

在Linux中,每個(gè)進(jìn)程都有屬于自己的進(jìn)程控制塊(PCB)和地址空間(Addr Space),并且都有一個(gè)與之對(duì)應(yīng)的頁(yè)表,負(fù)責(zé)將進(jìn)程的虛擬地址與物理地址進(jìn)行映射,通過(guò)內(nèi)存管理單元(MMU)進(jìn)行管理。兩個(gè)不同的虛擬地址通過(guò)頁(yè)表映射到物理空間的同一區(qū)域,它們所指向的這塊區(qū)域即共享內(nèi)存。

共享內(nèi)存的通信原理示意圖:

圖片圖片

對(duì)于上圖我的理解是:當(dāng)兩個(gè)進(jìn)程通過(guò)頁(yè)表將虛擬地址映射到物理地址時(shí),在物理地址中有一塊共同的內(nèi)存區(qū),即共享內(nèi)存,這塊內(nèi)存可以被兩個(gè)進(jìn)程同時(shí)看到。這樣當(dāng)一個(gè)進(jìn)程進(jìn)行寫(xiě)操作,另一個(gè)進(jìn)程讀操作就可以實(shí)現(xiàn)進(jìn)程間通信。但是,我們要確保一個(gè)進(jìn)程在寫(xiě)的時(shí)候不能被讀,因此我們使用信號(hào)量來(lái)實(shí)現(xiàn)同步與互斥。

對(duì)于一個(gè)共享內(nèi)存,實(shí)現(xiàn)采用的是引用計(jì)數(shù)的原理,當(dāng)進(jìn)程脫離共享存儲(chǔ)區(qū)后,計(jì)數(shù)器減一,掛架成功時(shí),計(jì)數(shù)器加一,只有當(dāng)計(jì)數(shù)器變?yōu)榱銜r(shí),才能被刪除。當(dāng)進(jìn)程終止時(shí),它所附加的共享存儲(chǔ)區(qū)都會(huì)自動(dòng)脫離。

為什么共享內(nèi)存速度最快?

借助上圖說(shuō)明:Proc A 進(jìn)程給內(nèi)存中寫(xiě)數(shù)據(jù), Proc B 進(jìn)程從內(nèi)存中讀取數(shù)據(jù),在此期間一共發(fā)生了兩次復(fù)制

(1)Proc A 到共享內(nèi)存       
(2)共享內(nèi)存到 Proc B

因?yàn)橹苯釉趦?nèi)存上操作,所以共享內(nèi)存的速度也就提高了。

三、共享內(nèi)存使用指南

3.1 關(guān)鍵函數(shù)全解析

在 Linux 中使用共享內(nèi)存,離不開(kāi)一些關(guān)鍵的系統(tǒng)調(diào)用函數(shù),它們是我們操作共享內(nèi)存的有力工具。

(1)shmget 函數(shù):用于創(chuàng)建共享內(nèi)存段或獲取已存在的共享內(nèi)存段的標(biāo)識(shí)符。其函數(shù)原型為:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

key:是一個(gè)用于標(biāo)識(shí)共享內(nèi)存段的鍵值,它就像是共享內(nèi)存的 “門(mén)牌號(hào)”。通常可以使用ftok函數(shù)根據(jù)文件路徑和項(xiàng)目 ID 生成一個(gè)唯一的key值。例如:

key_t key = ftok("/tmp/somefile", 1);

這里/tmp/somefile是一個(gè)已存在的文件路徑,1 是項(xiàng)目 ID。如果key取值為IPC_PRIVATE,則會(huì)創(chuàng)建一個(gè)新的私有共享內(nèi)存段,通常用于父子進(jìn)程間的通信。

size:指定共享內(nèi)存段的大小,單位是字節(jié)。例如,若要?jiǎng)?chuàng)建一個(gè) 1024 字節(jié)大小的共享內(nèi)存段,可以這樣設(shè)置:

int shmid = shmget(key, 1024, IPC_CREAT | 0666);
  • shmflg:是一組標(biāo)志位,常用的標(biāo)志包括IPC_CREAT(如果共享內(nèi)存不存在則創(chuàng)建)和IPC_EXCL(與IPC_CREAT一起使用,確保創(chuàng)建新的共享內(nèi)存段,若已存在則報(bào)錯(cuò))。權(quán)限設(shè)置與文件權(quán)限類似,如0666表示所有者、組和其他用戶都有讀寫(xiě)權(quán)限 。如果shmget函數(shù)執(zhí)行成功,會(huì)返回一個(gè)非負(fù)整數(shù),即共享內(nèi)存段的標(biāo)識(shí)符shmid;若失敗,則返回 -1。

(2)shmat 函數(shù):將共享內(nèi)存段連接到調(diào)用進(jìn)程的地址空間,使得進(jìn)程可以訪問(wèn)共享內(nèi)存中的數(shù)據(jù)。

其函數(shù)原型為:

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmid:是由shmget函數(shù)返回的共享內(nèi)存標(biāo)識(shí)符。
  • shmaddr:指定共享內(nèi)存連接到當(dāng)前進(jìn)程中的地址位置,通常設(shè)置為NULL,表示讓系統(tǒng)自動(dòng)選擇合適的地址。例如:
void *shared_mem = shmat(shmid, NULL, 0);

shmflg:通常為 0,表示默認(rèn)的連接方式。如果設(shè)置了SHM_RDONLY,則以只讀方式連接共享內(nèi)存。如果shmat函數(shù)調(diào)用成功,會(huì)返回一個(gè)指向共享內(nèi)存起始地址的指針;若失敗,返回(void *)-1。

(3)shmdt 函數(shù):用于將共享內(nèi)存段從當(dāng)前進(jìn)程的地址空間中分離。函數(shù)原型為:

int shmdt(const void *shmaddr);

shmaddr:是shmat函數(shù)返回的共享內(nèi)存起始地址。調(diào)用該函數(shù)后,進(jìn)程不再能夠訪問(wèn)該共享內(nèi)存,但共享內(nèi)存本身并不會(huì)被刪除。例如:

int result = shmdt(shared_mem);
if (result == -1) {
    perror("shmdt failed");
}

如果分離成功,shmdt返回 0;若失敗,返回 -1。

(4)shmctl 函數(shù):用于對(duì)共享內(nèi)存進(jìn)行控制操作,如獲取共享內(nèi)存信息、設(shè)置權(quán)限、刪除共享內(nèi)存等。函數(shù)原型為:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid:共享內(nèi)存標(biāo)識(shí)符。
  • cmd:指定要執(zhí)行的控制命令,常用的命令有IPC_STAT(獲取共享內(nèi)存的狀態(tài)信息,存入buf指向的結(jié)構(gòu)體)、IPC_SET(設(shè)置共享內(nèi)存的狀態(tài)信息,如權(quán)限等,從buf指向的結(jié)構(gòu)體中獲取設(shè)置值)和IPC_RMID(刪除共享內(nèi)存段)。
  • buf:是一個(gè)指向shmid_ds結(jié)構(gòu)體的指針,用于傳遞或獲取共享內(nèi)存的相關(guān)信息。當(dāng)cmd為IPC_RMID時(shí),buf通常設(shè)置為NULL。例如,刪除共享內(nèi)存段的操作如下:
int result = shmctl(shmid, IPC_RMID, NULL);
if (result == -1) {
    perror("shmctl IPC_RMID failed");
}

如果操作成功,shmctl返回 0;若失敗,返回 -1。

3.2 代碼實(shí)戰(zhàn):共享內(nèi)存的讀寫(xiě)操作

下面通過(guò)一個(gè)完整的代碼示例,展示如何在兩個(gè)進(jìn)程間使用共享內(nèi)存進(jìn)行數(shù)據(jù)讀寫(xiě)。假設(shè)我們要在一個(gè)進(jìn)程中寫(xiě)入數(shù)據(jù),另一個(gè)進(jìn)程讀取這些數(shù)據(jù)。

首先,定義一個(gè)數(shù)據(jù)結(jié)構(gòu),用于在共享內(nèi)存中存儲(chǔ)數(shù)據(jù)。這里我們定義一個(gè)簡(jiǎn)單的結(jié)構(gòu)體,包含一個(gè)整數(shù)和一個(gè)字符數(shù)組:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define SHM_SIZE 1024

// 定義共享內(nèi)存中使用的數(shù)據(jù)結(jié)構(gòu)
typedef struct {
    int num;
    char text[100];
} SharedData;

int main() {
    int shmid;
    key_t key;
    SharedData *shared_data;

    // 生成唯一的key值
    key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    // 創(chuàng)建共享內(nèi)存段
    shmid = shmget(key, sizeof(SharedData), IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // 將共享內(nèi)存連接到當(dāng)前進(jìn)程的地址空間
    shared_data = (SharedData *)shmat(shmid, NULL, 0);
    if (shared_data == (SharedData *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 寫(xiě)入數(shù)據(jù)到共享內(nèi)存
    shared_data->num = 42;
    strcpy(shared_data->text, "Hello, shared memory!");

    printf("Data written to shared memory: num = %d, text = %s\n", shared_data->num, shared_data->text);

    // 分離共享內(nèi)存
    if (shmdt(shared_data) == -1) {
        perror("shmdt");
        exit(EXIT_FAILURE);
    }

    return 0;
}

上述代碼中,首先使用ftok函數(shù)生成一個(gè)key值,然后通過(guò)shmget創(chuàng)建一個(gè)共享內(nèi)存段,其大小為SharedData結(jié)構(gòu)體的大小。接著使用shmat將共享內(nèi)存連接到當(dāng)前進(jìn)程地址空間,向共享內(nèi)存中寫(xiě)入數(shù)據(jù),最后使用shmdt分離共享內(nèi)存。

下面是讀取共享內(nèi)存數(shù)據(jù)的代碼:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define SHM_SIZE 1024

// 定義共享內(nèi)存中使用的數(shù)據(jù)結(jié)構(gòu)
typedef struct {
    int num;
    char text[100];
} SharedData;

int main() {
    int shmid;
    key_t key;
    SharedData *shared_data;

    // 生成唯一的key值,必須與寫(xiě)入進(jìn)程一致
    key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    // 獲取已存在的共享內(nèi)存段
    shmid = shmget(key, sizeof(SharedData), 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // 將共享內(nèi)存連接到當(dāng)前進(jìn)程的地址空間
    shared_data = (SharedData *)shmat(shmid, NULL, 0);
    if (shared_data == (SharedData *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 從共享內(nèi)存讀取數(shù)據(jù)
    printf("Data read from shared memory: num = %d, text = %s\n", shared_data->num, shared_data->text);

    // 分離共享內(nèi)存
    if (shmdt(shared_data) == -1) {
        perror("shmdt");
        exit(EXIT_FAILURE);
    }

    // 刪除共享內(nèi)存段(這里僅演示,實(shí)際應(yīng)用中需謹(jǐn)慎操作)
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID");
        exit(EXIT_FAILURE);
    }

    return 0;
}

在讀取代碼中,同樣先使用ftok生成與寫(xiě)入進(jìn)程相同的key值,然后通過(guò)shmget獲取共享內(nèi)存段(注意這里沒(méi)有使用IPC_CREAT標(biāo)志,因?yàn)楣蚕韮?nèi)存已經(jīng)由寫(xiě)入進(jìn)程創(chuàng)建),接著連接共享內(nèi)存并讀取數(shù)據(jù),最后分離共享內(nèi)存并刪除共享內(nèi)存段(在實(shí)際應(yīng)用中,刪除共享內(nèi)存段的操作需要謹(jǐn)慎考慮,確保沒(méi)有其他進(jìn)程再使用該共享內(nèi)存)。

3.3 模擬共享內(nèi)存

我們用server來(lái)創(chuàng)建共享存儲(chǔ)段,用client獲取共享存儲(chǔ)段的標(biāo)識(shí)符,二者關(guān)聯(lián)起來(lái)之后server將數(shù)據(jù)寫(xiě)入共享存儲(chǔ)段,client從共享區(qū)讀取數(shù)據(jù)。通信結(jié)束之后server與client斷開(kāi)與共享區(qū)的關(guān)聯(lián),并由server釋放共享存儲(chǔ)段。

comm.h

//comm.h
#ifndef _COMM_H__
#define _COMM_H__

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATHNAME "."
#define PROJ_ID 0x6666

int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);
#endif

comm.c

//comm.c
#include"comm.h"

static int CommShm(int size,int flags)
{
	key_t key = ftok(PATHNAME,PROJ_ID);
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	int shmid = 0;
	if((shmid = shmget(key,size,flags)) < 0)
	{
		perror("shmget");
		return -2;
	}
	return shmid;
}
int DestroyShm(int shmid)
{
	if(shmctl(shmid,IPC_RMID,NULL) < 0)
	{
		perror("shmctl");
		return -1;
	}
	return 0;
}
int CreateShm(int size)
{
	return CommShm(size,IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm(int size)
{
	return CommShm(size,IPC_CREAT);
}

client.c

//client.c
#include"comm.h"

int main()
{
	int shmid = GetShm(4096);
	sleep(1);
	char *addr = shmat(shmid,NULL,0);
	sleep(2);
	int i = 0;
	while(i < 26)
	{
		addr[i] = 'A' + i;
		i++;
		addr[i] = 0;
		sleep(1);
	}
	shmdt(addr);
	sleep(2);
	return 0;
}

server.c

//server.c
#include"comm.h"

int main()
{
	int shmid = CreateShm(4096);

	char *addr = shmat(shmid,NULL,0);
	sleep(2);
	int i = 0;
	while(i++ < 26)
	{
		printf("client# %s\n",addr);
		sleep(1);
	}
	shmdt(addr);
	sleep(2);
	DestroyShm(shmid);
	return 0;
}

Makefile

//Makefile
.PHONY:all
all:server client

client:client.c comm.c
	gcc -o $@ $^
server:server.c comm.c
	gcc -o $@ $^

.PHONY:clean
clean:
rm -f client server

運(yùn)行結(jié)果:

圖片圖片

  • 優(yōu)點(diǎn):我們可以看到使用共享內(nèi)存進(jìn)行進(jìn)程之間的通信是非常方便的,而且函數(shù)的接口也比較簡(jiǎn)單,數(shù)據(jù)的共享還使進(jìn)程間的數(shù)據(jù)不用傳送,而是直接訪問(wèn)內(nèi)存,加快了程序的效率。
  • 缺點(diǎn):共享內(nèi)存沒(méi)有提供同步機(jī)制,這使得我們?cè)谑褂霉蚕韮?nèi)存進(jìn)行進(jìn)程之間的通信時(shí),往往需要借助其他手段來(lái)保證進(jìn)程之間的同步工作。

3.4 權(quán)限與生命周期管理

權(quán)限設(shè)置:在創(chuàng)建共享內(nèi)存時(shí),可以通過(guò)shmget函數(shù)的shmflg參數(shù)設(shè)置共享內(nèi)存的訪問(wèn)權(quán)限。權(quán)限設(shè)置與文件權(quán)限類似,使用三位八進(jìn)制數(shù)表示,分別對(duì)應(yīng)所有者、組和其他用戶的讀、寫(xiě)、執(zhí)行權(quán)限。例如,0666表示所有者、組和其他用戶都有讀寫(xiě)權(quán)限;0644表示所有者有讀寫(xiě)權(quán)限,組和其他用戶只有讀權(quán)限。合理的權(quán)限設(shè)置可以保證共享內(nèi)存的安全性,防止未經(jīng)授權(quán)的進(jìn)程訪問(wèn)或修改共享內(nèi)存中的數(shù)據(jù)。比如,在一個(gè)多用戶的服務(wù)器環(huán)境中,如果有一些共享內(nèi)存用于存儲(chǔ)敏感數(shù)據(jù),就需要嚴(yán)格設(shè)置權(quán)限,只允許特定的用戶或用戶組訪問(wèn)。

生命周期管理:共享內(nèi)存的生命周期獨(dú)立于使用它的進(jìn)程。當(dāng)最后一個(gè)使用共享內(nèi)存的進(jìn)程將其分離(調(diào)用shmdt)后,共享內(nèi)存仍然存在于系統(tǒng)中,直到被顯式刪除(調(diào)用shmctl并傳入IPC_RMID命令)或系統(tǒng)重啟。這就需要開(kāi)發(fā)者在使用共享內(nèi)存時(shí),謹(jǐn)慎管理其生命周期。在程序結(jié)束時(shí),應(yīng)該確保及時(shí)刪除不再使用的共享內(nèi)存,以避免內(nèi)存泄漏和資源浪費(fèi)。

比如,在一個(gè)長(zhǎng)期運(yùn)行的服務(wù)器程序中,如果不斷創(chuàng)建共享內(nèi)存而不刪除,隨著時(shí)間的推移,系統(tǒng)中會(huì)殘留大量無(wú)用的共享內(nèi)存,占用系統(tǒng)資源,影響系統(tǒng)性能。同時(shí),在刪除共享內(nèi)存之前,要確保所有使用該共享內(nèi)存的進(jìn)程都已經(jīng)將其分離,否則可能會(huì)導(dǎo)致其他進(jìn)程訪問(wèn)非法內(nèi)存地址,引發(fā)程序崩潰等問(wèn)題。

四、深入共享內(nèi)存的實(shí)現(xiàn)原理

4.1 內(nèi)核視角:共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)

在 Linux 內(nèi)核中,有幾個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)用于管理共享內(nèi)存,其中struct shmid_kernel和struct shmid_ds起著重要作用。

struct shmid_kernel是內(nèi)核中用于表示共享內(nèi)存對(duì)象的內(nèi)部數(shù)據(jù)結(jié)構(gòu),它包含了共享內(nèi)存的各種屬性和狀態(tài)信息。雖然這個(gè)結(jié)構(gòu)體對(duì)于普通開(kāi)發(fā)者來(lái)說(shuō)并不直接可見(jiàn),但了解它有助于深入理解共享內(nèi)存的工作機(jī)制。它記錄了共享內(nèi)存段的大小、所屬的進(jìn)程組、創(chuàng)建時(shí)間、最后訪問(wèn)時(shí)間等重要信息。例如,通過(guò)這個(gè)結(jié)構(gòu)體,內(nèi)核可以跟蹤共享內(nèi)存的使用情況,判斷哪些進(jìn)程正在使用它,以及何時(shí)需要回收共享內(nèi)存資源。

而struct shmid_ds則是一個(gè)更常用的數(shù)據(jù)結(jié)構(gòu),開(kāi)發(fā)者可以通過(guò)shmctl函數(shù)來(lái)訪問(wèn)和修改這個(gè)結(jié)構(gòu)體中的信息。它的定義如下:

struct shmid_ds {
    struct ipc_perm shm_perm;    /* 所有權(quán)和權(quán)限相關(guān)信息 */
    size_t          shm_segsz;   /* 共享內(nèi)存段的大小(字節(jié)) */
    time_t          shm_atime;   /* 最后一次連接到共享內(nèi)存的時(shí)間 */
    time_t          shm_dtime;   /* 最后一次從共享內(nèi)存分離的時(shí)間 */
    time_t          shm_ctime;   /* 共享內(nèi)存狀態(tài)最后一次改變的時(shí)間 */
    pid_t           shm_cpid;    /* 創(chuàng)建共享內(nèi)存的進(jìn)程ID */
    pid_t           shm_lpid;    /* 最后一次執(zhí)行shmat或shmdt操作的進(jìn)程ID */
    shmatt_t        shm_nattch;  /* 當(dāng)前連接到共享內(nèi)存的進(jìn)程數(shù) */
    ...
};
  • shm_perm:包含了共享內(nèi)存的所有權(quán)和權(quán)限信息,如所有者 ID、組 ID、訪問(wèn)權(quán)限等,類似于文件的權(quán)限管理。例如,通過(guò)設(shè)置shm_perm中的權(quán)限位,可以控制哪些進(jìn)程可以訪問(wèn)共享內(nèi)存,以及以何種方式(讀、寫(xiě)等)訪問(wèn)。
  • shm_segsz:明確了共享內(nèi)存段的大小,以字節(jié)為單位。在創(chuàng)建共享內(nèi)存時(shí),開(kāi)發(fā)者需要根據(jù)實(shí)際需求指定合適的大小。比如,在一個(gè)簡(jiǎn)單的進(jìn)程間通信場(chǎng)景中,如果只是傳遞少量的狀態(tài)信息,可能只需要分配幾十或幾百字節(jié)的共享內(nèi)存;而在一個(gè)需要共享大量數(shù)據(jù)的場(chǎng)景中,如共享視頻幀數(shù)據(jù),可能需要分配幾兆甚至更大的共享內(nèi)存空間。
  • shm_atime、shm_dtime和shm_ctime:分別記錄了共享內(nèi)存的連接時(shí)間、分離時(shí)間和狀態(tài)改變時(shí)間。這些時(shí)間戳對(duì)于調(diào)試和性能分析非常有幫助,例如,通過(guò)查看shm_atime和shm_dtime,可以了解進(jìn)程對(duì)共享內(nèi)存的使用時(shí)間間隔,判斷是否存在長(zhǎng)時(shí)間占用共享內(nèi)存而不釋放的情況;shm_ctime則可以幫助開(kāi)發(fā)者追蹤共享內(nèi)存的狀態(tài)變化歷史。
  • shm_cpid和shm_lpid:記錄了創(chuàng)建共享內(nèi)存的進(jìn)程 ID 和最后一次執(zhí)行shmat或shmdt操作的進(jìn)程 ID。這對(duì)于調(diào)試和管理共享內(nèi)存的使用非常有用,當(dāng)出現(xiàn)共享內(nèi)存相關(guān)的問(wèn)題時(shí),可以通過(guò)這些 ID 來(lái)追溯問(wèn)題的源頭,查看是哪個(gè)進(jìn)程創(chuàng)建了共享內(nèi)存,以及最近哪些進(jìn)程對(duì)共享內(nèi)存進(jìn)行了連接或分離操作。
  • shm_nattch:表示當(dāng)前連接到共享內(nèi)存的進(jìn)程數(shù)。內(nèi)核通過(guò)這個(gè)字段來(lái)管理共享內(nèi)存的生命周期,當(dāng)shm_nattch變?yōu)?0 時(shí),并且沒(méi)有其他進(jìn)程持有對(duì)該共享內(nèi)存的引用,內(nèi)核可以考慮回收該共享內(nèi)存資源。例如,在一個(gè)多進(jìn)程協(xié)作的服務(wù)器程序中,當(dāng)所有使用共享內(nèi)存的進(jìn)程都完成任務(wù)并與共享內(nèi)存分離后,shm_nattch變?yōu)?0,此時(shí)內(nèi)核可以及時(shí)釋放共享內(nèi)存,避免內(nèi)存資源的浪費(fèi)。

4.2 映射機(jī)制:虛擬內(nèi)存與物理內(nèi)存的橋梁

共享內(nèi)存能夠?qū)崿F(xiàn)高效的進(jìn)程間通信,關(guān)鍵在于其巧妙的內(nèi)存映射機(jī)制,通過(guò)頁(yè)表將虛擬內(nèi)存映射到物理內(nèi)存。

在 Linux 系統(tǒng)中,每個(gè)進(jìn)程都有自己獨(dú)立的虛擬地址空間。當(dāng)進(jìn)程創(chuàng)建或連接到共享內(nèi)存時(shí),操作系統(tǒng)會(huì)在進(jìn)程的虛擬地址空間中分配一段虛擬地址范圍,并將這段虛擬地址與共享內(nèi)存所在的物理內(nèi)存區(qū)域建立映射關(guān)系。這個(gè)映射關(guān)系是通過(guò)頁(yè)表來(lái)維護(hù)的。

頁(yè)表是一種數(shù)據(jù)結(jié)構(gòu),它記錄了虛擬頁(yè)號(hào)(VPN,Virtual Page Number)與物理頁(yè)號(hào)(PPN,Physical Page Number)之間的對(duì)應(yīng)關(guān)系。當(dāng)進(jìn)程訪問(wèn)共享內(nèi)存中的數(shù)據(jù)時(shí),CPU 首先會(huì)根據(jù)當(dāng)前進(jìn)程的頁(yè)表,將虛擬地址中的虛擬頁(yè)號(hào)轉(zhuǎn)換為物理頁(yè)號(hào),然后再加上頁(yè)內(nèi)偏移量,得到實(shí)際的物理內(nèi)存地址,從而訪問(wèn)到共享內(nèi)存中的數(shù)據(jù)。

例如,假設(shè)進(jìn)程 A 和進(jìn)程 B 共享一塊大小為 4KB 的共享內(nèi)存。當(dāng)進(jìn)程 A 創(chuàng)建共享內(nèi)存時(shí),操作系統(tǒng)會(huì)在物理內(nèi)存中分配一塊 4KB 大小的內(nèi)存區(qū)域,并為這塊區(qū)域分配一個(gè)物理頁(yè)號(hào)。然后,操作系統(tǒng)在進(jìn)程 A 的頁(yè)表中創(chuàng)建一個(gè)頁(yè)表項(xiàng),將虛擬頁(yè)號(hào)與該物理頁(yè)號(hào)關(guān)聯(lián)起來(lái),使得進(jìn)程 A 可以通過(guò)虛擬地址訪問(wèn)這塊共享內(nèi)存。當(dāng)進(jìn)程 B 連接到該共享內(nèi)存時(shí),操作系統(tǒng)同樣在進(jìn)程 B 的頁(yè)表中創(chuàng)建一個(gè)頁(yè)表項(xiàng),將其虛擬地址空間中的一段虛擬頁(yè)號(hào)也映射到相同的物理頁(yè)號(hào)上。這樣,進(jìn)程 A 和進(jìn)程 B 就可以通過(guò)各自的虛擬地址訪問(wèn)同一塊物理內(nèi)存區(qū)域,實(shí)現(xiàn)數(shù)據(jù)共享。

在這個(gè)過(guò)程中,如果所需的共享內(nèi)存數(shù)據(jù)不在物理內(nèi)存中(例如,由于內(nèi)存不足,共享內(nèi)存的部分?jǐn)?shù)據(jù)被交換到磁盤(pán)上),會(huì)發(fā)生頁(yè)面錯(cuò)誤(page fault)。此時(shí),操作系統(tǒng)會(huì)負(fù)責(zé)將所需的數(shù)據(jù)從磁盤(pán)讀入物理內(nèi)存,并更新頁(yè)表,確保進(jìn)程能夠正確訪問(wèn)共享內(nèi)存。這種動(dòng)態(tài)的內(nèi)存管理機(jī)制使得共享內(nèi)存能夠在有限的物理內(nèi)存條件下高效運(yùn)行,同時(shí)也保證了進(jìn)程間通信的穩(wěn)定性和可靠性。

Linux提供了內(nèi)存映射函數(shù)mmap, 它把文件內(nèi)容映射到一段內(nèi)存上(準(zhǔn)確說(shuō)是虛擬內(nèi)存上,運(yùn)行著進(jìn)程), 通過(guò)對(duì)這段內(nèi)存的讀取和修改, 實(shí)現(xiàn)對(duì)文件的讀取和修改。mmap()系統(tǒng)調(diào)用使得進(jìn)程之間可以通過(guò)映射一個(gè)普通的文件實(shí)現(xiàn)共享內(nèi)存。普通文件映射到進(jìn)程地址空間后,進(jìn)程可以像訪問(wèn)內(nèi)存的方式對(duì)文件進(jìn)行訪問(wèn),不需要其他內(nèi)核態(tài)的系統(tǒng)調(diào)用(read,write)去操作。

這里是講設(shè)備或者硬盤(pán)存儲(chǔ)的一塊空間映射到物理內(nèi)存,然后操作這塊物理內(nèi)存就是在操作實(shí)際的硬盤(pán)空間,不需要經(jīng)過(guò)內(nèi)核態(tài)傳遞。比如你的硬盤(pán)上有一個(gè)文件,你可以使用linux系統(tǒng)提供的mmap接口,將這個(gè)文件映射到進(jìn)程一塊虛擬地址空間,這塊空間會(huì)對(duì)應(yīng)一塊物理內(nèi)存,當(dāng)你讀寫(xiě)這塊物理空間的時(shí)候,就是在讀取實(shí)際的磁盤(pán)文件,就是這么直接高效。通常諸如共享庫(kù)的加載都是通過(guò)內(nèi)存映射的方式加載到物理內(nèi)存的。

mmap系統(tǒng)調(diào)用并不完全是為了共享內(nèi)存來(lái)設(shè)計(jì)的,它本身提供了不同于一般對(duì)普通文件的訪問(wèn)的方式,進(jìn)程可以像讀寫(xiě)內(nèi)存一樣對(duì)普通文件進(jìn)行操作,IPC的共享內(nèi)存是純粹為了共享。

內(nèi)存映射指的是將 :進(jìn)程中的1個(gè)虛擬內(nèi)存區(qū)域 & 1個(gè)磁盤(pán)上的對(duì)象,使得二者存在映射關(guān)系。當(dāng)然,也可以多個(gè)進(jìn)程同時(shí)映射到一個(gè)對(duì)象上面。

實(shí)現(xiàn)過(guò)程

  • 內(nèi)存映射的實(shí)現(xiàn)過(guò)程主要是通過(guò)Linux系統(tǒng)下的系統(tǒng)調(diào)用函數(shù):mmap()
  • 該函數(shù)的作用 = 創(chuàng)建虛擬內(nèi)存區(qū)域 + 與共享對(duì)象建立映射關(guān)系

其函數(shù)原型、具體使用 & 內(nèi)部流程 如下:

/** * 函數(shù)原型 */ 
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 /** 
* 具體使用(用戶進(jìn)程調(diào)用mmap()) 
* 下述代碼即常見(jiàn)了一片大小 = MAP_SIZE的接收緩存區(qū) & 關(guān)聯(lián)到共享對(duì)象中(即建立映射) 
*/ 

mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0); 
/** 
* 內(nèi)部原理 
* 步驟1:創(chuàng)建虛擬內(nèi)存區(qū)域 
* 步驟2:實(shí)現(xiàn)地址映射關(guān)系,即:進(jìn)程的虛擬地址空間 ->> 共享對(duì)象 
* 注:
* a. 此時(shí),該虛擬地址并沒(méi)有任何數(shù)據(jù)關(guān)聯(lián)到文件中,僅僅只是建立映射關(guān)系 
* b. 當(dāng)其中1個(gè)進(jìn)程對(duì)虛擬內(nèi)存寫(xiě)入數(shù)據(jù)時(shí),則真正實(shí)現(xiàn)了數(shù)據(jù)的可見(jiàn) 
*/

優(yōu)點(diǎn)

進(jìn)程在讀寫(xiě)磁盤(pán)的時(shí)候,大概的流程是:

以write 為例:進(jìn)程(用戶空間) -> 系統(tǒng)調(diào)用,進(jìn)入內(nèi)核 -> 將要寫(xiě)入的數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間的緩存區(qū) -> 調(diào)用磁盤(pán)驅(qū)動(dòng) -> 寫(xiě)在磁盤(pán)上面。

使用mmap之后進(jìn)程(用戶空間)--> 讀寫(xiě)映射的內(nèi)存 --> 寫(xiě)在磁盤(pán)上面。(這樣的優(yōu)點(diǎn)是 避免了頻繁的進(jìn)入內(nèi)核空間,進(jìn)行系統(tǒng)調(diào)用,提高了效率)

(1)mmap系統(tǒng)調(diào)用

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);

這就是mmap系統(tǒng)調(diào)用的接口,mmap函數(shù)成功返回指向內(nèi)存區(qū)域的指針,失敗返回MAP_FAILED。

  • addr,某個(gè)特定的地址作為起始地址,當(dāng)被設(shè)置為NULL,標(biāo)識(shí)系統(tǒng)自動(dòng)分配地址。實(shí)實(shí)在在的物理區(qū)域。
  • length說(shuō)的是內(nèi)存段的長(zhǎng)度。
  • prot是用來(lái)設(shè)定內(nèi)存段的訪問(wèn)權(quán)限。
PROT_READ	內(nèi)存段可讀
PROT_WRITE	內(nèi)存段可寫(xiě)
PROT_EXEC	內(nèi)存段可執(zhí)行
PROT_NONE	內(nèi)存段不能被訪問(wèn)

flags參數(shù)控制內(nèi)存段內(nèi)容被修改以后程序的行為。

MAP_SHARED	進(jìn)程間共享內(nèi)存,對(duì)該內(nèi)存段修改反映到映射文件中。提供了POSIX共享內(nèi)存
MAP_PRIVATE	內(nèi)存段為調(diào)用進(jìn)程所私有。對(duì)該內(nèi)存段的修改不會(huì)反映到映射文件
MAP_ANNOYMOUS	這段內(nèi)存不是從文件映射而來(lái)的。內(nèi)容被初始化為全0
MAP_FIXED	內(nèi)存段必須位于start參數(shù)指定的地址處,start必須是頁(yè)大小的整數(shù)倍(4K整數(shù)倍)
MAP_HUGETLB	按照大內(nèi)存頁(yè)面來(lái)分配內(nèi)存空間

fd參數(shù)是用來(lái)被映射文件對(duì)應(yīng)的文件描述符,通過(guò)open系統(tǒng)調(diào)用得到,offset設(shè)定從何處進(jìn)行映射。

(2)mmap用于共享內(nèi)存的方式

  1. 我們可以使用普通文件進(jìn)行提供內(nèi)存映射,例如,open系統(tǒng)調(diào)用打開(kāi)一個(gè)文件,然后進(jìn)行mmap操作,得到共享內(nèi)存,這種方式適用于任何進(jìn)程之間。
  2. 可以使用特殊文件進(jìn)行匿名內(nèi)存映射,這個(gè)相對(duì)的是具有血緣關(guān)系的進(jìn)程之間,當(dāng)父進(jìn)程調(diào)用mmap,然后進(jìn)行fork,這樣父進(jìn)程創(chuàng)建的子進(jìn)程會(huì)繼承父進(jìn)程匿名映射后的地址空間,這樣,父子進(jìn)程之間就可以進(jìn)行通信了。相當(dāng)于是mmap的返回地址此時(shí)是父子進(jìn)程同時(shí)來(lái)維護(hù)。
  3. 另外POSIX版本的共享內(nèi)存底層也是使用了mmap。所以,共享內(nèi)存在在posix上一定程度上就是指的內(nèi)存映射了。

五、Mmap和System V共享內(nèi)存的比較

共享內(nèi)存:

圖片圖片

這是System V版本的共享內(nèi)存(以下我們統(tǒng)稱為shm),下面看下mmap的:

圖片圖片

mmap是在磁盤(pán)上建立一個(gè)文件,每個(gè)進(jìn)程地址空間中開(kāi)辟出一塊空間進(jìn)行映射。而shm共享內(nèi)存,每個(gè)進(jìn)程最終會(huì)映射到同一塊物理內(nèi)存。shm保存在物理內(nèi)存,這樣讀寫(xiě)的速度肯定要比磁盤(pán)要快,但是存儲(chǔ)量不是特別大,相對(duì)于shm來(lái)說(shuō),mmap更加簡(jiǎn)單,調(diào)用更加方便,所以這也是大家都喜歡用的原因。

另外mmap有一個(gè)好處是當(dāng)機(jī)器重啟,因?yàn)閙map把文件保存在磁盤(pán)上,這個(gè)文件還保存了操作系統(tǒng)同步的映像,所以mmap不會(huì)丟失,但是shmget在內(nèi)存里面就會(huì)丟失,總之,共享內(nèi)存是在內(nèi)存中創(chuàng)建空間,每個(gè)進(jìn)程映射到此處。內(nèi)存映射是創(chuàng)建一個(gè)文件,并且映射到每個(gè)進(jìn)程開(kāi)辟的空間中,但在posix中的共享內(nèi)存就是指這種使用文件的方式“內(nèi)存映射”。

六、POSIX共享內(nèi)存

6.1 IPC機(jī)制

共享內(nèi)存是最快的可用IPC形式。它允許多個(gè)不相關(guān)(無(wú)親緣關(guān)系)的進(jìn)程去訪問(wèn)同一部分邏輯內(nèi)存。

如果需要在兩個(gè)進(jìn)程之間傳輸數(shù)據(jù),共享內(nèi)存將是一種效率極高的解決方案。一旦這樣的內(nèi)存區(qū)映射到共享它的進(jìn)程的地址空間,這些進(jìn)程間數(shù)據(jù)的傳輸就不再涉及內(nèi)核。這樣就可以減少系統(tǒng)調(diào)用時(shí)間,提高程序效率。

共享內(nèi)存是由IPC為一個(gè)進(jìn)程創(chuàng)建的一個(gè)特殊的地址范圍,它將出現(xiàn)在進(jìn)程的地址空間中。其他進(jìn)程可以把同一段共享內(nèi)存段“連接到”它們自己的地址空間里去。所有進(jìn)程都可以訪問(wèn)共享內(nèi)存中的地址。如果一個(gè)進(jìn)程向這段共享內(nèi)存寫(xiě)了數(shù)據(jù),所做的改動(dòng)會(huì)立刻被有訪問(wèn)同一段共享內(nèi)存的其他進(jìn)程看到。

要注意的是共享內(nèi)存本身沒(méi)有提供任何同步功能。也就是說(shuō),在第一個(gè)進(jìn)程結(jié)束對(duì)共享內(nèi)存的寫(xiě)操作之前,并沒(méi)有什么自動(dòng)功能能夠預(yù)防第二個(gè)進(jìn)程開(kāi)始對(duì)它進(jìn)行讀操作。共享內(nèi)存的訪問(wèn)同步問(wèn)題必須由程序員負(fù)責(zé)。可選的同步方式有互斥鎖、條件變量、讀寫(xiě)鎖、紀(jì)錄鎖、信號(hào)燈。

實(shí)際上,進(jìn)程之間在共享內(nèi)存時(shí),并不總是讀寫(xiě)少量數(shù)據(jù)后就解除映射,有新的通信時(shí),再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直到通信完畢為止。

6.2 POSIX共享內(nèi)存API

使用POSIX共享內(nèi)存需要用到下面這些API:

#include <sys/types.h>
#include <sys/stat.h>        /* For mode constants */
#include <sys/mman.h>
#include <fcntl.h>           /* For O_* constants */
#include <unistd.h>
int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
int ftruncate(int fildes, off_t length);
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
int munmap(void *addr, size_t len);
int close(int fildes);
int fstat(int fildes, struct stat *buf);
int fchown(int fildes, uid_t owner, gid_t group);
int fchmod(int fildes, mode_t mode);
  • shm_open:穿件并打開(kāi)一個(gè)新的共享內(nèi)存對(duì)象或者打開(kāi)一個(gè)既存的共享內(nèi)存對(duì)象, 與函數(shù)open的用法是類似的函數(shù)返回值是一個(gè)文件描述符,會(huì)被下面的API使用。
  • ftruncate:設(shè)置共享內(nèi)存對(duì)象的大小,新創(chuàng)建的共享內(nèi)存對(duì)象大小為0。
  • mmap:將共享內(nèi)存對(duì)象映射到調(diào)用進(jìn)程的虛擬地址空間。
  • munmap:取消共享內(nèi)存對(duì)象到調(diào)用進(jìn)程的虛擬地址空間的映射。
  • shm_unlink:刪除一個(gè)共享內(nèi)存對(duì)象名字。
  • close:當(dāng)shm_open函數(shù)返回的文件描述符不再使用時(shí),使用close函數(shù)關(guān)閉它。
  • fstat:獲得共享內(nèi)存對(duì)象屬性的stat結(jié)構(gòu)體. 結(jié)構(gòu)體中會(huì)包含共享內(nèi)存對(duì)象的大小(st_size), 權(quán)限(st_mode), 所有者(st_uid), 歸屬組 (st_gid)。
  • fchown:改變一個(gè)共享內(nèi)存對(duì)象的所有權(quán)。
  • fchmod:改變一個(gè)共享內(nèi)存對(duì)象的權(quán)限。

七、共享內(nèi)存的同步問(wèn)題

雖然共享內(nèi)存為進(jìn)程間通信提供了高效的數(shù)據(jù)共享方式,但由于多個(gè)進(jìn)程可以同時(shí)訪問(wèn)同一塊內(nèi)存區(qū)域,這就帶來(lái)了同步和互斥的問(wèn)題。如果沒(méi)有合適的同步機(jī)制,可能會(huì)出現(xiàn)以下情況:

  • 競(jìng)態(tài)條件(Race Condition):當(dāng)多個(gè)進(jìn)程同時(shí)訪問(wèn)和修改共享內(nèi)存中的數(shù)據(jù)時(shí),由于進(jìn)程執(zhí)行的先后順序不確定,可能導(dǎo)致最終的數(shù)據(jù)結(jié)果不可預(yù)測(cè)。例如,有兩個(gè)進(jìn)程 P1 和 P2 同時(shí)讀取共享內(nèi)存中的一個(gè)整數(shù)變量 count,然后各自對(duì)其加 1,最后再寫(xiě)回共享內(nèi)存。如果沒(méi)有同步機(jī)制,可能會(huì)出現(xiàn) P1 和 P2 讀取到相同的 count 值,然后各自加 1 后寫(xiě)回,這樣 count 只增加了 1,而不是預(yù)期的 2 。
  • 數(shù)據(jù)不一致性:一個(gè)進(jìn)程正在對(duì)共享內(nèi)存中的數(shù)據(jù)進(jìn)行修改時(shí),另一個(gè)進(jìn)程可能同時(shí)讀取這些未完全修改的數(shù)據(jù),從而導(dǎo)致數(shù)據(jù)不一致。比如,一個(gè)進(jìn)程正在更新共享內(nèi)存中的一個(gè)復(fù)雜數(shù)據(jù)結(jié)構(gòu),在更新過(guò)程中,另一個(gè)進(jìn)程讀取該數(shù)據(jù)結(jié)構(gòu),可能會(huì)讀到部分更新的數(shù)據(jù),使數(shù)據(jù)處于不一致的狀態(tài),進(jìn)而導(dǎo)致程序出現(xiàn)錯(cuò)誤。

解決方案:信號(hào)量與互斥鎖的應(yīng)用

為了解決共享內(nèi)存帶來(lái)的同步和互斥問(wèn)題,通常會(huì)使用信號(hào)量(Semaphore)和互斥鎖(Mutex)等同步機(jī)制。

(1)信號(hào)量:信號(hào)量是一種計(jì)數(shù)器,用于控制對(duì)共享資源的訪問(wèn)。它可以用來(lái)實(shí)現(xiàn)進(jìn)程間的同步和互斥。在共享內(nèi)存的場(chǎng)景中,信號(hào)量可以用來(lái)控制對(duì)共享內(nèi)存的訪問(wèn)權(quán)限。例如,我們可以創(chuàng)建一個(gè)信號(hào)量,初始值設(shè)為 1,表示共享內(nèi)存資源可用。當(dāng)一個(gè)進(jìn)程要訪問(wèn)共享內(nèi)存時(shí),它首先嘗試獲取信號(hào)量(通過(guò)對(duì)信號(hào)量執(zhí)行 P 操作,即減 1 操作)。如果信號(hào)量的值大于等于 0,說(shuō)明資源可用,進(jìn)程可以繼續(xù)執(zhí)行對(duì)共享內(nèi)存的訪問(wèn)操作;如果信號(hào)量的值小于 0,說(shuō)明資源已被其他進(jìn)程占用,該進(jìn)程會(huì)被阻塞,直到信號(hào)量的值大于等于 0。當(dāng)進(jìn)程完成對(duì)共享內(nèi)存的訪問(wèn)后,它會(huì)釋放信號(hào)量(通過(guò)對(duì)信號(hào)量執(zhí)行 V 操作,即加 1 操作),通知其他進(jìn)程可以訪問(wèn)共享內(nèi)存。在 Linux 中,有 POSIX 有名信號(hào)量、POSIX 無(wú)名信號(hào)量和 System V 信號(hào)量等不同類型,開(kāi)發(fā)者可以根據(jù)具體需求選擇使用。例如,使用 POSIX 有名信號(hào)量實(shí)現(xiàn)共享內(nèi)存同步的代碼示例如下:

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#define SHM_SIZE 1024
#define SEM_NAME "/my_semaphore"

int main() {
    int shm_fd;
    void *shared_memory;
    sem_t *sem;

    // 創(chuàng)建共享內(nèi)存對(duì)象
    shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(1);
    }

    // 配置共享內(nèi)存大小
    if (ftruncate(shm_fd, SHM_SIZE) == -1) {
        perror("ftruncate");
        exit(1);
    }

    // 將共享內(nèi)存映射到進(jìn)程地址空間
    shared_memory = mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shared_memory == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    // 創(chuàng)建信號(hào)量
    sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        exit(1);
    }

    // 等待信號(hào)量,獲取共享內(nèi)存訪問(wèn)權(quán)限
    if (sem_wait(sem) == -1) {
        perror("sem_wait");
        exit(1);
    }

    // 訪問(wèn)共享內(nèi)存
    printf("Accessed shared memory: %s\n", (char *)shared_memory);

    // 釋放信號(hào)量,允許其他進(jìn)程訪問(wèn)共享內(nèi)存
    if (sem_post(sem) == -1) {
        perror("sem_post");
        exit(1);
    }

    // 取消映射并關(guān)閉共享內(nèi)存
    if (munmap(shared_memory, SHM_SIZE) == -1) {
        perror("munmap");
        exit(1);
    }
    if (close(shm_fd) == -1) {
        perror("close");
        exit(1);
    }

    // 刪除共享內(nèi)存對(duì)象
    if (shm_unlink("/my_shared_memory") == -1) {
        perror("shm_unlink");
        exit(1);
    }

    // 關(guān)閉并刪除信號(hào)量
    if (sem_close(sem) == -1) {
        perror("sem_close");
        exit(1);
    }
    if (sem_unlink(SEM_NAME) == -1) {
        perror("sem_unlink");
        exit(1);
    }

    return 0;
}

(2)互斥鎖:互斥鎖是一種二元信號(hào)量,用于保證在同一時(shí)刻只有一個(gè)進(jìn)程能夠訪問(wèn)共享資源,即實(shí)現(xiàn)對(duì)共享內(nèi)存的互斥訪問(wèn)。當(dāng)一個(gè)進(jìn)程獲取到互斥鎖后,其他進(jìn)程如果試圖獲取該互斥鎖,會(huì)被阻塞,直到持有互斥鎖的進(jìn)程釋放它。在 Linux 中,使用 pthread 庫(kù)中的互斥鎖相關(guān)函數(shù)來(lái)實(shí)現(xiàn)互斥鎖的操作。例如,初始化互斥鎖可以使用pthread_mutex_init函數(shù),獲取互斥鎖使用pthread_mutex_lock函數(shù),釋放互斥鎖使用pthread_mutex_unlock函數(shù),銷(xiāo)毀互斥鎖使用pthread_mutex_destroy函數(shù)。以下是使用互斥鎖實(shí)現(xiàn)共享內(nèi)存同步的簡(jiǎn)單代碼示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#define SHM_SIZE 1024

typedef struct {
    pthread_mutex_t mutex;
    char data[SHM_SIZE];
} SharedData;

int main() {
    int shm_fd;
    SharedData *shared_data;

    // 創(chuàng)建共享內(nèi)存對(duì)象
    shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(1);
    }

    // 配置共享內(nèi)存大小
    if (ftruncate(shm_fd, sizeof(SharedData)) == -1) {
        perror("ftruncate");
        exit(1);
    }

    // 將共享內(nèi)存映射到進(jìn)程地址空間
    shared_data = (SharedData *)mmap(0, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shared_data == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    // 初始化互斥鎖
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    if (pthread_mutex_init(&shared_data->mutex, &attr) != 0) {
        perror("pthread_mutex_init");
        exit(1);
    }

    // 獲取互斥鎖,訪問(wèn)共享內(nèi)存
    if (pthread_mutex_lock(&shared_data->mutex) != 0) {
        perror("pthread_mutex_lock");
        exit(1);
    }
    printf("Accessed shared memory: %s\n", shared_data->data);
    // 釋放互斥鎖
    if (pthread_mutex_unlock(&shared_data->mutex) != 0) {
        perror("pthread_mutex_unlock");
        exit(1);
    }

    // 取消映射并關(guān)閉共享內(nèi)存
    if (munmap(shared_data, sizeof(SharedData)) == -1) {
        perror("munmap");
        exit(1);
    }
    if (close(shm_fd) == -1) {
        perror("close");
        exit(1);
    }

    // 刪除共享內(nèi)存對(duì)象
    if (shm_unlink("/my_shared_memory") == -1) {
        perror("shm_unlink");
        exit(1);
    }

    return 0;
}

通過(guò)合理使用信號(hào)量和互斥鎖等同步機(jī)制,可以有效地解決共享內(nèi)存帶來(lái)的同步和互斥問(wèn)題,確保多個(gè)進(jìn)程能夠安全、高效地共享內(nèi)存數(shù)據(jù)。

八、實(shí)際應(yīng)用場(chǎng)景及常見(jiàn)問(wèn)題解答

8.1 實(shí)際應(yīng)用場(chǎng)景

(1)數(shù)據(jù)庫(kù)緩存優(yōu)化

在數(shù)據(jù)庫(kù)系統(tǒng)中,共享內(nèi)存發(fā)揮著至關(guān)重要的作用,尤其是在緩存優(yōu)化方面。以 Oracle 數(shù)據(jù)庫(kù)為例,它使用共享全局區(qū)(SGA,Shared Global Area)來(lái)實(shí)現(xiàn)共享內(nèi)存。SGA 是一個(gè)共享的內(nèi)存結(jié)構(gòu),用于存儲(chǔ)數(shù)據(jù)塊、SQL 語(yǔ)句和其他共享信息 。

當(dāng)數(shù)據(jù)庫(kù)接收到查詢請(qǐng)求時(shí),首先會(huì)在共享內(nèi)存的緩存中查找相關(guān)數(shù)據(jù)。如果數(shù)據(jù)存在于緩存中,即命中緩存,數(shù)據(jù)庫(kù)可以直接從共享內(nèi)存中讀取數(shù)據(jù)并返回給用戶,這大大減少了磁盤(pán) I/O 操作。因?yàn)閺拇疟P(pán)讀取數(shù)據(jù)的速度遠(yuǎn)遠(yuǎn)低于從內(nèi)存讀取數(shù)據(jù)的速度,通過(guò)共享內(nèi)存緩存數(shù)據(jù),可以顯著提高查詢性能。例如,在一個(gè)高并發(fā)的在線交易系統(tǒng)中,大量用戶頻繁查詢訂單信息。如果沒(méi)有共享內(nèi)存緩存,每次查詢都需要從磁盤(pán)讀取數(shù)據(jù),磁盤(pán) I/O 很快就會(huì)成為系統(tǒng)的瓶頸,導(dǎo)致查詢響應(yīng)時(shí)間變長(zhǎng)。而使用共享內(nèi)存緩存訂單數(shù)據(jù)后,大部分查詢可以直接從內(nèi)存中獲取數(shù)據(jù),大大提高了系統(tǒng)的響應(yīng)速度和吞吐量。

同時(shí),共享內(nèi)存還可以減少內(nèi)存的重復(fù)使用,提高內(nèi)存利用率。多個(gè)數(shù)據(jù)庫(kù)進(jìn)程可以共享同一塊內(nèi)存區(qū)域,避免了每個(gè)進(jìn)程都單獨(dú)開(kāi)辟內(nèi)存來(lái)存儲(chǔ)相同的數(shù)據(jù),從而節(jié)省了內(nèi)存資源。比如,在一個(gè)包含多個(gè)數(shù)據(jù)庫(kù)實(shí)例的系統(tǒng)中,這些實(shí)例可以共享 SGA 中的數(shù)據(jù)緩存,減少了內(nèi)存的浪費(fèi),使得系統(tǒng)能夠在有限的內(nèi)存資源下高效運(yùn)行。

(2)高性能計(jì)算中的數(shù)據(jù)共享

在高性能計(jì)算領(lǐng)域,共享內(nèi)存同樣有著廣泛的應(yīng)用。在大規(guī)模的科學(xué)計(jì)算和工程模擬中,往往需要處理海量的數(shù)據(jù)和復(fù)雜的計(jì)算任務(wù),這些任務(wù)通常需要多個(gè)處理器核心或多個(gè)計(jì)算節(jié)點(diǎn)協(xié)同工作。

以分子動(dòng)力學(xué)模擬為例,這是一種用于研究分子系統(tǒng)微觀行為的計(jì)算方法,需要對(duì)大量分子的運(yùn)動(dòng)軌跡進(jìn)行模擬計(jì)算。在計(jì)算過(guò)程中,不同的處理器核心需要共享分子的初始位置、速度等數(shù)據(jù),以及模擬過(guò)程中的中間結(jié)果。通過(guò)共享內(nèi)存,這些數(shù)據(jù)可以被多個(gè)處理器核心直接訪問(wèn),避免了數(shù)據(jù)在不同處理器之間通過(guò)網(wǎng)絡(luò)或其他方式傳輸?shù)拈_(kāi)銷(xiāo),提高了計(jì)算效率。

再比如,在氣象預(yù)報(bào)模型中,需要對(duì)全球范圍內(nèi)的氣象數(shù)據(jù)進(jìn)行分析和預(yù)測(cè)。這些數(shù)據(jù)量巨大,計(jì)算任務(wù)復(fù)雜,通常會(huì)在分布式計(jì)算集群上進(jìn)行。共享內(nèi)存可以用于在不同計(jì)算節(jié)點(diǎn)之間共享氣象數(shù)據(jù)和計(jì)算參數(shù),使得各個(gè)節(jié)點(diǎn)能夠協(xié)同工作,共同完成氣象預(yù)報(bào)的計(jì)算任務(wù)。在這種場(chǎng)景下,共享內(nèi)存不僅提高了數(shù)據(jù)共享的效率,還減少了節(jié)點(diǎn)之間的通信開(kāi)銷(xiāo),對(duì)于提高整個(gè)高性能計(jì)算系統(tǒng)的性能起著關(guān)鍵作用。

8.2 避坑指南與常見(jiàn)問(wèn)題解答

在使用 Linux 共享內(nèi)存的過(guò)程中,開(kāi)發(fā)者常常會(huì)遇到一些棘手的問(wèn)題,下面我們就來(lái)總結(jié)一下這些常見(jiàn)問(wèn)題,并給出相應(yīng)的解決方案。

(1)共享內(nèi)存創(chuàng)建失敗

①問(wèn)題描述:調(diào)用shmget函數(shù)創(chuàng)建共享內(nèi)存時(shí),返回值為 -1,導(dǎo)致創(chuàng)建失敗。

②可能原因

  • 系統(tǒng)資源限制:系統(tǒng)對(duì)共享內(nèi)存的數(shù)量和大小有限制,如SHMMAX(單個(gè)共享內(nèi)存段的最大大小)和SHMMNI(系統(tǒng)中共享內(nèi)存段的最大數(shù)量)等參數(shù)。如果要?jiǎng)?chuàng)建的共享內(nèi)存超過(guò)了這些限制,就會(huì)導(dǎo)致創(chuàng)建失敗。例如,當(dāng)系統(tǒng)的SHMMAX設(shè)置為 32MB,而你嘗試創(chuàng)建一個(gè) 64MB 的共享內(nèi)存段時(shí),就會(huì)失敗。
  • 權(quán)限不足:創(chuàng)建共享內(nèi)存需要適當(dāng)?shù)臋?quán)限。如果當(dāng)前用戶沒(méi)有足夠的權(quán)限(如在一些安全限制較嚴(yán)格的系統(tǒng)中),shmget調(diào)用也會(huì)失敗。比如,普通用戶在沒(méi)有特殊權(quán)限配置的情況下,無(wú)法創(chuàng)建共享內(nèi)存。

③解決方案

檢查系統(tǒng)參數(shù):通過(guò)cat /proc/sys/kernel/shmmax和cat /proc/sys/kernel/shmmni等命令查看系統(tǒng)的共享內(nèi)存參數(shù)設(shè)置。如果需要,可以通過(guò)修改/etc/sysctl.conf文件來(lái)調(diào)整這些參數(shù),例如:

echo "kernel.shmmax = 2147483648" >> /etc/sysctl.conf
sysctl -p

上述命令將SHMMAX設(shè)置為 2GB,并使其立即生效。

④確認(rèn)權(quán)限:確保當(dāng)前用戶具有創(chuàng)建共享內(nèi)存的權(quán)限,必要時(shí)可以切換到具有足夠權(quán)限的用戶(如 root 用戶)來(lái)創(chuàng)建共享內(nèi)存,或者通過(guò)修改文件權(quán)限和用戶組等方式來(lái)賦予相應(yīng)權(quán)限。

(2)共享內(nèi)存訪問(wèn)異常

①問(wèn)題描述:在進(jìn)程訪問(wèn)共享內(nèi)存時(shí),出現(xiàn)段錯(cuò)誤(Segmentation Fault)或其他訪問(wèn)異常。

②可能原因

  • 未正確映射共享內(nèi)存:調(diào)用shmat函數(shù)時(shí),可能由于參數(shù)設(shè)置錯(cuò)誤,導(dǎo)致共享內(nèi)存沒(méi)有正確映射到進(jìn)程的地址空間。例如,shmat返回的指針為(void *)-1,表示映射失敗,但程序沒(méi)有正確處理這種情況,仍然嘗試使用該指針訪問(wèn)共享內(nèi)存,就會(huì)導(dǎo)致訪問(wèn)異常。
  • 內(nèi)存越界訪問(wèn):在對(duì)共享內(nèi)存進(jìn)行讀寫(xiě)操作時(shí),沒(méi)有正確檢查邊界條件,導(dǎo)致訪問(wèn)超出了共享內(nèi)存的范圍。比如,共享內(nèi)存大小為 1024 字節(jié),而程序嘗試寫(xiě)入 2048 字節(jié)的數(shù)據(jù),就會(huì)造成內(nèi)存越界。
  • 同步問(wèn)題:多個(gè)進(jìn)程同時(shí)訪問(wèn)共享內(nèi)存時(shí),如果沒(méi)有正確的同步機(jī)制(如信號(hào)量、互斥鎖等),可能會(huì)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)和訪問(wèn)沖突,進(jìn)而引發(fā)訪問(wèn)異常。例如,一個(gè)進(jìn)程正在修改共享內(nèi)存中的數(shù)據(jù),另一個(gè)進(jìn)程同時(shí)讀取這些未完全修改的數(shù)據(jù),就可能導(dǎo)致數(shù)據(jù)不一致和訪問(wèn)錯(cuò)誤。

③解決方案

檢查映射結(jié)果:在調(diào)用shmat后,仔細(xì)檢查返回值。如果返回(void *)-1,則根據(jù)errno變量的值進(jìn)行錯(cuò)誤處理,例如:

void *shared_mem = shmat(shmid, NULL, 0);
if (shared_mem == (void *)-1) {
    perror("shmat failed");
    exit(EXIT_FAILURE);
}
  • 邊界檢查:在對(duì)共享內(nèi)存進(jìn)行讀寫(xiě)操作時(shí),務(wù)必進(jìn)行嚴(yán)格的邊界檢查,確保不會(huì)越界訪問(wèn)。例如,在寫(xiě)入數(shù)據(jù)時(shí),要檢查數(shù)據(jù)大小是否超過(guò)共享內(nèi)存的剩余空間;在讀取數(shù)據(jù)時(shí),要確保讀取的長(zhǎng)度不超過(guò)共享內(nèi)存的有效范圍。
  • 完善同步機(jī)制:引入合適的同步機(jī)制,如使用信號(hào)量或互斥鎖來(lái)確保對(duì)共享內(nèi)存的訪問(wèn)是安全的。在訪問(wèn)共享內(nèi)存之前,先獲取同步鎖(如信號(hào)量的 P 操作或互斥鎖的加鎖操作),訪問(wèn)完成后再釋放同步鎖(如信號(hào)量的 V 操作或互斥鎖的解鎖操作)。

(3)共享內(nèi)存未及時(shí)釋放

①問(wèn)題描述:共享內(nèi)存不再被使用,但沒(méi)有被及時(shí)刪除,導(dǎo)致系統(tǒng)資源浪費(fèi)。

②可能原因

  • 程序邏輯錯(cuò)誤:在程序中沒(méi)有正確處理共享內(nèi)存的生命周期,例如沒(méi)有在合適的時(shí)機(jī)調(diào)用shmctl函數(shù)并傳入IPC_RMID命令來(lái)刪除共享內(nèi)存。
  • 進(jìn)程異常退出:使用共享內(nèi)存的進(jìn)程由于某種原因(如程序崩潰、收到異常信號(hào)等)異常退出,而沒(méi)有來(lái)得及執(zhí)行共享內(nèi)存的刪除操作。

③解決方案

優(yōu)化程序邏輯:在程序設(shè)計(jì)時(shí),明確共享內(nèi)存的生命周期,確保在不再需要共享內(nèi)存時(shí),及時(shí)調(diào)用shmctl函數(shù)刪除共享內(nèi)存。例如,在程序結(jié)束時(shí),添加如下代碼:

if (shmctl(shmid, IPC_RMID, NULL) == -1) {
    perror("shmctl IPC_RMID failed");
    exit(EXIT_FAILURE);
}

捕獲異常信號(hào):在進(jìn)程中捕獲常見(jiàn)的異常信號(hào)(如SIGSEGV、SIGABRT等),在信號(hào)處理函數(shù)中添加釋放共享內(nèi)存的操作。例如,使用signal函數(shù)注冊(cè)信號(hào)處理函數(shù):

#include <signal.h>

void cleanup_shared_memory(int signum) {
    // 釋放共享內(nèi)存相關(guān)資源
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID in signal handler failed");
    }
    exit(EXIT_FAILURE);
}

int main() {
    // 注冊(cè)信號(hào)處理函數(shù)
    signal(SIGSEGV, cleanup_shared_memory);
    signal(SIGABRT, cleanup_shared_memory);
    // 其他程序邏輯
}

通過(guò)上述方法,可以有效避免共享內(nèi)存未及時(shí)釋放的問(wèn)題,提高系統(tǒng)資源的利用率。

責(zé)任編輯:武曉燕 來(lái)源: 深度Linux
相關(guān)推薦

2018-01-12 14:35:00

Linux進(jìn)程共享內(nèi)存

2019-05-08 11:10:05

Linux進(jìn)程語(yǔ)言

2013-07-29 09:36:05

100G傳輸100G

2019-03-25 15:00:38

工具代碼測(cè)試

2012-04-17 13:12:48

2010-03-04 10:20:59

超高速寬帶谷歌市

2014-09-04 16:40:17

FTTx

2021-03-08 17:09:14

5G網(wǎng)絡(luò)俄羅斯

2016-12-28 17:04:51

1Gbps寬帶‘網(wǎng)絡(luò)

2014-02-18 09:19:04

LTE100G400G

2010-01-05 10:00:48

Linux進(jìn)程間通信

2023-08-31 14:24:06

5G技術(shù)物聯(lián)網(wǎng)

2010-03-08 10:52:29

思科超高速互聯(lián)網(wǎng)接入系統(tǒng)

2009-07-09 11:19:01

2009-08-18 17:14:47

100G超高速以太網(wǎng)

2025-05-13 07:10:31

2017-08-06 00:05:18

進(jìn)程通信開(kāi)發(fā)

2018-05-30 13:58:02

Linux進(jìn)程通信

2010-03-10 09:29:54

寬帶超高速互聯(lián)網(wǎng)思科

2015-01-05 15:11:23

日本光纖400Gbit
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 四虎永久免费黄色影片 | 华丽的挑战在线观看 | 久久大陆 | 天天av综合 | 在线a视频 | 青青青伊人 | www久久久 | 亚洲精品视频在线看 | 日日夜夜免费精品视频 | 久久久精品久久久 | 国产有码 | 亚洲精品视频免费观看 | 欧美视频偷拍 | 亚洲日日夜夜 | 国产黄色av电影 | 欧美久久一区二区三区 | 美女爽到呻吟久久久久 | 日韩欧美国产精品综合嫩v 一区中文字幕 | 在线观看中文字幕 | 在线视频 欧美日韩 | 国产农村妇女毛片精品久久麻豆 | 国产精品久久av | 色婷婷综合网站 | 久久国产精品-国产精品 | 日韩a视频| 久久久999精品 | 成人一区二区三区在线观看 | 久草综合在线视频 | 视频国产一区 | 999www视频免费观看 | 国产精品一区二区在线 | 三区四区在线观看 | 亚洲国产成人精品女人久久久野战 | 九九伦理片 | 56pao在线| 中文字幕电影在线观看 | 免费激情网站 | 中文字幕在线不卡播放 | 亚洲日韩中文字幕一区 | 欧美日韩精品亚洲 | 成人午夜在线视频 |