Linux共享內(nèi)存原理及分析
一、前言
我們之前提到過,進(jìn)程通信是讓兩個不同的進(jìn)程看到同一份資源,所以我們給大家講解了匿名管道和命名管道。
今天我們還有一種方式兩個不同的進(jìn)程看到同一份資源。
那就是共享內(nèi)存!
二、共享內(nèi)存原理
我們先來看這張圖,我們在物理內(nèi)存創(chuàng)建一個共享區(qū),我們再讓兩個不同的進(jìn)程通過頁表分別映射到對應(yīng)的共享區(qū)里,然后返回虛擬地址的起始地址。

這就是共享內(nèi)存,我們總結(jié)分為三步
- 申請內(nèi)存。
- 將該內(nèi)存掛接到進(jìn)程地址空間的共享區(qū)。
- 返回虛擬地址首地址。
以上我們就創(chuàng)建好共享內(nèi)存了,那我們該如何釋放共享內(nèi)存呢?
去關(guān)聯(lián),我們通過系統(tǒng)調(diào)用釋放共享內(nèi)存,這個系統(tǒng)調(diào)用由操作系統(tǒng)提供,并且進(jìn)程調(diào)用系統(tǒng)調(diào)用是由操作系統(tǒng)操作。
我們可能會有很多個共享內(nèi)存,那操作系統(tǒng)要不要管理所有的共享內(nèi)存呢?
- 當(dāng)然要,還是六個字——“先描述,后組織”。
- 由內(nèi)核結(jié)構(gòu)體描述共享內(nèi)存。
接下來我們通過寫一些代碼了解該原理,在寫代碼之前我們要先了解一些接口。
三、代碼
1、系統(tǒng)調(diào)用接口
man shmget int shmget(key_t key, size_t size, int shmflg);

我們先把這個key放一放最后再講
這個size_t size是創(chuàng)建共享內(nèi)存的大小,單位是字節(jié)。
shmflg我們知道,我們這個共享內(nèi)存創(chuàng)建出來了以后,就不需要再次創(chuàng)建了,只需要獲取即可
所以就注定了我們需要如何創(chuàng)建,如何獲取。如果需要關(guān)注的選項就是下面兩個。

- IPC_CREAT(單獨使用)的意思是創(chuàng)建一個共享內(nèi)存,如果這個共享內(nèi)存存在,直接獲取,如果不存在就創(chuàng)建并返回他
- IPC_CREAT | IPC_EXCL(兩個一起使用):如果申請的共享額你存不存在,就創(chuàng)建,如果存在就出錯返回。它可以確保如果我們申請成功了,這個共享內(nèi)存一定是一個新的
- IPC_EXCL不單獨使用
對于它的返回值

如果成功,他會返回一個共享內(nèi)存的標(biāo)識,如果失敗,返回-1 (問題二)
這里我想問一下大家?我們怎么判斷兩個進(jìn)程是不是看到同一份資源呢?
這就跟key值有關(guān)了。
只要兩個進(jìn)程拿到同一個key,就能看到同一份共享內(nèi)存。
因為它在內(nèi)核中具有唯一性,能夠讓不同的進(jìn)程進(jìn)行唯一性標(biāo)識。
第一個進(jìn)程可以通過key創(chuàng)建共享內(nèi)存,第二個進(jìn)程只需要通過拿著同一個key就可以和第一個進(jìn)程看到同一個共享內(nèi)存!
那么問題來了,為什么這個key具有唯一性呢?
這里先給大家介紹另一個接口
man ftok key_t ftok(const char *pathname, int proj_id);

其實這是一套算法,把pathname和proj_id進(jìn)行數(shù)值運算,最后得到這個key值,返回這個key。
pathname和proj_id是由用戶自由指定的,最后形成這個key。
因為同一個函數(shù)中,使用同一個參數(shù)可以產(chǎn)生同一個key。我們這就可以確保兩個進(jìn)程具有一樣的key了,這就是為什么key具有唯一性。
那為什么key不由操作系統(tǒng)創(chuàng)建呢?而是由用戶
因為操作系統(tǒng)并不知道是哪兩個進(jìn)程進(jìn)行通信,哪幾個進(jìn)程通信由用戶決定!
創(chuàng)建好的共享內(nèi)存中,key在哪呢?
key在共享內(nèi)存的描述對象中。
這個接下來在寫代碼的時候給大家具體描述
2、創(chuàng)建共享內(nèi)存
我們先創(chuàng)建一個共享內(nèi)存
comm.hpp(這里我們直接用了上節(jié)課講的日志系統(tǒng))
#include "log.hpp"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
const string pathname ="/home/cyp";
const int proj_id = 0x6667;
const int size = 4096;
Log log;
key_t Getkey()
{
key_t key = ftok(pathname.c_str(),proj_id);
if(key < 0)
{
log.logmessage(Fatal,"ftok error:%s",strerror(errno));
exit(1);
}
log.logmessage(Info,"key creat success:%d",key);
return key;
}
int GetShareMeHeler(int flag)
{
key_t key=Getkey();
int shmid = shmget(key,size,flag);
if(shmid==-1)
{
log.logmessage(Fatal,"shmget error:%s",strerror(errno));
exit(2);
}
log.logmessage(Info,"creat success:%d",shmid);
return shmid;
}
processa.cc
#include "comm.hpp"
int main()
{
int shmid = GetShareMeHeler(IPC_CREAT|IPC_EXCL);
log.logmessage(Info,"process quit..");
return 0;
}結(jié)果如下
這里我們用ipcs -m能查看共享內(nèi)存資源,我們能看到共享內(nèi)存被創(chuàng)建出來了。

key VS shmid
- key是操作系統(tǒng)內(nèi)標(biāo)定的唯一性。
- shmid只有在進(jìn)程內(nèi)表示資源的唯一性。
從上述運行結(jié)果看,進(jìn)程退出了,我們用該命令還能查看到我們在該進(jìn)程創(chuàng)建的共享內(nèi)存。
結(jié)論 :共享內(nèi)存的生命周期是隨內(nèi)核的,用戶不主動關(guān)閉共享內(nèi)存,共享內(nèi)存會一直存在。
如果我們想刪掉共享內(nèi)存的話,我們可以用這個指令
iprm -m 刪除共享內(nèi)存資源,后面接著共享內(nèi)存的標(biāo)識符

查看共享內(nèi)存,有幾個我們不認(rèn)識,perms是權(quán)限,它其實也是一個文件,我們可以為他設(shè)置對應(yīng)的權(quán)限。nattch是關(guān)聯(lián)的意思,意思是當(dāng)前有幾個進(jìn)程與這塊共享內(nèi)存是關(guān)聯(lián)的

那么我們該怎么設(shè)置權(quán)限呢?


接下來我們創(chuàng)建共享內(nèi)存接口,一個是創(chuàng)建,一個是獲取。

3、將進(jìn)程掛接到共享內(nèi)存
這里我們給大家介紹一個接口
man shmdt void *shmat(int shmid, const void *shmaddr, int shmflg);

- 第一個參數(shù)好說,我們知道就是我們剛剛的共享內(nèi)存的shmid
- 第二個參數(shù)shmaddr,代表的含義是我們想將共享內(nèi)存掛接到哪個位置。即共享區(qū)的哪個位置,我們一般可以設(shè)置為nullptr,讓系統(tǒng)去決定掛到哪里去了
- 第三個參數(shù)就是該共享內(nèi)存的權(quán)限,比如只讀等等,當(dāng)然我們也可以設(shè)置為0,就是按照默認(rèn)的來
- 這個返回值void*比較像我們以前的malloc,malloc得到是一塊虛擬地址,只有當(dāng)讀寫的時候才發(fā)生缺頁中斷,才申請內(nèi)存

我們寫一份代碼將進(jìn)程掛接到該共享內(nèi)存上。

結(jié)果如下:

4、去關(guān)聯(lián)
這里給大家再介紹一個接口
man shmdt int shmdt(const void *shmaddr);

這里的shmaddr是我們在掛接的時候返回強轉(zhuǎn)的變量。
這個接口是比較類似于之前free接口的
那么我們這個是怎么知道我們要釋放多少呢?
所以這里一定隱藏著我們沒有看到的數(shù)據(jù)。malloc也是一樣的
malloc其實會比我們要申請的內(nèi)存多一點點的。用于存儲一些我們看不到的數(shù)據(jù)。所以malloc其實更適合用于申請大空間,因為這樣影響比較小。
我們一般把這些多申請的空間叫做cookie。

這個接口的返回值是成功為0,失敗為-1

5、釋放共享內(nèi)存
如果我們每次運行這個進(jìn)程的時候,總會報錯,這個共享內(nèi)存已經(jīng)存在,那么我們有沒有什么辦法能讓這個共享內(nèi)存隨著我們的進(jìn)程一起釋放掉呢?
man shmctl int shmctl(int shmid, int cmd, struct shmid_ds *buf);

- 第一個參數(shù)我們還是比較清楚的就是共享內(nèi)存的id
- 第二個參數(shù)是我們控制這個共享內(nèi)存的方法
我們目前只關(guān)心這個,它的作用是將共享內(nèi)存標(biāo)記為被刪除的。而我們刪除的時候是不關(guān)心它的屬性的,所以屬性直接設(shè)置為nullptr
對于返回值,成功為0,失敗為-1

- 第三個參數(shù)就是這樣一個結(jié)構(gòu)體

這些就是一些屬性。這個結(jié)構(gòu)體里面的屬性是我們內(nèi)核當(dāng)中管理共享內(nèi)存的一個子集。也就是可以獲取共享內(nèi)存里面的屬性

6、兩個進(jìn)程通過共享內(nèi)存通信
- processa進(jìn)程接收信息

- processb進(jìn)程發(fā)送消息

shmaddr指定共享內(nèi)存連接到進(jìn)程地址空間中的哪個位置。如果設(shè)置為NULL,系統(tǒng)會自動選擇一個合適的(隨機(jī)的)地址。
我們之前強轉(zhuǎn)了shmaddr返回了它的地址,我們就能通過它的地址進(jìn)行通信。
結(jié)果如下:
- 它不像管道,沒有數(shù)據(jù)寫入,讀端就會堵塞。
- 共享內(nèi)存不管寫不寫入,我會一直讀。

當(dāng)寫端進(jìn)程執(zhí)行。

這里我們存在一個問題,當(dāng)我們又寫入又讀,不會發(fā)生錯亂錯誤嗎?
會,所以我們要用管道去維護(hù)它。
- 在我們的頭文件里增加一個類

- 在processa里增加管道代碼

- processb增加向管道寫入代碼

運行結(jié)果如下:
- 寫入端還沒寫,讀取端阻塞

- 通信成功

四、共享內(nèi)存特點
- 共享內(nèi)存沒有同步互斥的保護(hù)機(jī)制
- 共享內(nèi)存是隨意進(jìn)程通信中,速度最快的,因為拷貝少
- 共享內(nèi)存內(nèi)部數(shù)據(jù),由用戶自己維護(hù)
五、共享內(nèi)存的屬性
如下所示,是我們前面所提及的共享內(nèi)存的一些屬性

在第一個結(jié)構(gòu)體中,最重要的字段是第一個shm_perm,這個還是一個結(jié)構(gòu)體,也就是下面的這個。其他的字段后面都有解釋
在這個第二個結(jié)構(gòu)體中,第一個就是key。所以我們應(yīng)用層中生成的key,最后一定會被寫入到內(nèi)核中。里面的這個mode就是共享內(nèi)存的權(quán)限問題
我們可以直接去看看這些屬性
先看看shmctrl接口的控制方法

我們創(chuàng)建了共享內(nèi)存屬性的結(jié)構(gòu)體

運行結(jié)果如下:

總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
CentOS7 下安裝telnet服務(wù)的實現(xiàn)方法
這篇文章主要介紹了CentOS7 下安裝telnet服務(wù)的實現(xiàn)方法的相關(guān)資料,希望通過本文能幫助到大家,讓大家實現(xiàn)這樣功能的方法,需要的朋友可以參考下2017-10-10
Linux環(huán)境實現(xiàn)替換jar包中class文件
文章介紹了如何通過替換class文件來避免重復(fù)打包的問題,具體步驟包括查找目標(biāo)文件目錄、解壓目標(biāo)文件、替換已解壓文件、替換jar包文件等2025-11-11
PHP程序員玩轉(zhuǎn)Linux系列 Nginx中的HTTPS詳解
這篇文章主要為大家詳細(xì)介紹了PHP程序員玩轉(zhuǎn)Linux系列文章,MySQL備份還原教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
CentOS服務(wù)器平臺搭建mysql主從復(fù)制與讀寫分離的方法
這篇文章主要介紹了CentOS服務(wù)器平臺搭建mysql主從復(fù)制與讀寫分離的方法,結(jié)合實例形式較為詳細(xì)的分析了CentOS平臺搭建mysql主從復(fù)制與讀寫分離的步驟、設(shè)置方法、相關(guān)操作技巧與注意事項,需要的朋友可以參考下2018-04-04

