目錄
1、共享內存原理
2、申請共享內存
2.1 ftok?
2.2 測試shmget、ftok?
2.3 查看系統下的共享內存?
3、關聯共享內存
3.1 測試shmat
4、釋放共享內存?
4.1 測試shmctl?
5、實現共享內存通信?
6、共享內存的特性
結語?
前言:
????????在Linux下,有一種進程間通信方式(IPC)名為共享內存,他是IPC中通信最快的方式(通信方式為全雙工),因為他直接在物理內存上創建一塊區域并且映射在進程的地址空間中,使得進程使用共享內存就如同直接使用動態申請的空間,因此通信過程少了內核的系統調用步驟,以至于相比于其他IPC模式速度更快,不過也正是因為在通信時不受內核管轄,導致共享內存不具備同步互斥機制,因此需要用戶手動處理同步互斥問題。
????????但是需要注意的是共享內存雖然使用起來如同動態空間,但是他的底層和動態空間不一樣,動態空間具有獨立性,只限于單個進程內部的訪問,而共享內存允許多個無親緣進程進行通信,因此他和動態空間是有區別的。
1、共享內存原理
? ? ? ? 共享內存的目的就是為了進程間通信,而進程間通信的核心觀念是讓不同的進程看到同一份資源,所以共享內存必須在物理內存上開辟一塊空間,并且映射到進程地址空間中的共享區,具體示意圖如下:
? ? ? ? 但是共享內存的申請和malloc申請是不一樣的,因為共享內存要面向所有進程,要做到這一點就必須調用系統接口,所以要進行共享內存通信必須調用系統接口。
2、申請共享內存
? ? ? ? 在物理內存上申請共享內存的接口是shmget,該接口介紹如下:
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
//key是用戶給這段共享內存設置的名字,一個key對應一個共享內存
//size表示申請共享內存的大小
//shmflg表示權限設置,常用的有IPC_CREAT和IPC_EXCL//調用成功返回非負整數表示共享內存的標識碼(給系統看的),失敗返回-1
????????著重介紹shmflg:
? ? ? ? 1、當傳遞的是IPC_CREAT|IPC_EXCL,表示若以當前key值申請的共享內存不存在,則創建一個并返回新共享內存的標識碼。若以當前key值申請的共享內存存在則返回-1,表示申請失敗。
? ? ? ? 2、當傳遞的是IPC_CREAT,表示若以當前key值申請的共享內存不存在,則創建一個并返回新共享內存的標識碼。若以當前key值申請的共享內存存在則返回該共享內存的標識碼。
? ? ? ? 所以使用IPC_CREAT|IPC_EXCL可以判斷一個key對應的共享內存是否存在,即key值是否被用過,當我們想用一段新的共享內存則可以使用IPC_CREAT|IPC_EXCL。
? ? ? ? key值的作用是判斷兩個進程的共享內存是否為同一個,兩個進程所用的key一樣說明他們共用同一個共享內存,反之則否,因此可以理解為key值是用戶給一段共享內存起的名字,而shmget返回值是系統給這段共享內存起的名字。
2.1 ftok?
? ? ? ? shmget需要用到key值,key的類型雖然是key_t,但是也可以傳一個int類型的值給到key,只不過這么做會導致潛在的重名風險,并且key的值需要程序員自己維護,于是系統提供了一個接口ftok,他像是一個算法,可以計算并返回一個key_t類型的值,該接口介紹如下:
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
//接收一個路徑和一個整形
//成功返回一個key_t類型的值,失敗返回-1
? ? ? ? ?所以兩個進程調用ftok時傳參是一樣的,那么這兩個進程就會獲得相同的key值,這樣兩個進程就能看到同一份資源了,也就完成了通信的前提。
2.2 測試shmget、ftok?
?????????先用代碼測試上述接口,測試代碼如下:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <iostream>using namespace std;int main()
{const char *pathname = "/home/zh";int proj_id = 12;int size = 4096;key_t key = ftok(pathname, proj_id);if (key < 0){perror("ftok");return -1;}cout << "key值被成功創建,key:" << key << endl;int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);if (shmid < 0){perror("shmget");return -1;}cout<<"共享內存標識碼被成功創建,shmid:"<<shmid<<endl;return 0;
}
? ? ? ? 運行結果:
2.3 查看系統下的共享內存?
? ? ? ? 共享內存不同于動態申請空間,動態空間的生命周期隨進程。但是對于共享內存而言,若用戶不主動釋放共享內存,則共享內存會一直存活在系統中,他的生命周期隨內核,即內核重啟才會清理這些共享內存,在Linux下用指令ipcs -m查看當下系統的共享內存,測試如下:
? ? ? ? 并且可以通過指令ipcrm -m shmid刪除對應的shmid,測試如下:
3、關聯共享內存
? ? ? ? ?上述接口shmget可以申請一塊共享內存,但是申請到了不意味著就可以直接使用共享內存進行通信,要進行通信還要關聯共享內存,關聯共享內存的接口介紹如下:
#include <sys/types.h>
#include <sys/shm.h>//關聯共享內存
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmid表示要關聯的共享內存標識碼
//shmaddr若不為NULL且shmflg不為SHM_RND,表示將共享內存的地址附加到shmaddr處
//shmaddr若為NULL,則該函數的返回值作為共享內存的地址(通常都設為NULL)
//shmflg表示權限設置,通常設為0表示對共享內存可讀可寫
//調用成功返回指向共享內存的指針,失敗返回值(void*)-1//去關聯
int shmdt(const void *shmaddr);
//讓調用該函數的進程不再關聯該共享內存
//shmaddr表示共享內存的地址
? ? ? ? 總的來說,調用shmat關聯共享內存后,會拿到一個執行該共享內存的指針,通過該指針就可以對共享內存進行讀寫操作。
3.1 測試shmat
? ? ? ? 因為申請共享內存的代碼后續會被重復使用,為了后續更好的測試,所以對申請共享內存的接口進行再一層封裝,封裝成sharemem.hpp文件,代碼如下:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <iostream>
#include <unistd.h>using namespace std;const char *pathname = "/home/zh";
int proj_id = 12;
int size = 4096;key_t getkey()//封裝ftok
{key_t key = ftok(pathname, proj_id);if (key < 0){perror("ftok");exit(-1);}cout << "key值被成功創建,key:" << key << endl;return key;
}int getshm(int shmflg)//封裝shmget
{key_t key = getkey();int shmid = shmget(key, size, shmflg);if (shmid < 0){perror("shmget");exit(-1);}cout << "共享內存標識碼被成功創建,shmid:" << shmid << endl;return shmid;
}int creatnewshm()//只想用最新的共享內存來進程通信
{return getshm(IPC_CREAT|IPC_EXCL|0666);//為了能夠觀察到變化,所以要保證共享內存的權限
}int getoldshm()//獲取一個已經存在的共享內存進行通信
{return getshm(IPC_CREAT);
}
?????????后續的測試只需要包含該文件即可,測試shmat代碼如下:
#include "sharemem.hpp"int main()
{int shmid = creatnewshm();cout<<"申請共享內存成功"<<endl;sleep(2);//觀察nattch的值char* poi = (char*)shmat(shmid,NULL,0);cout<<"關聯共享內存成功"<<endl;sleep(2);//觀察nattch的值return 0;
}
? ? ? ? 運行結果:
? ? ? ? 其中,右側nattch表示當前有多少個進程在關聯該共享內存,當一個進程關聯某個共享內存后,該共享內存的nattch+1,并且當該進程結束后,對應的nattch會-1。當然也可以使用shmdt手動去關聯。
4、釋放共享內存?
? ? ? ? 手動釋放共享內存的接口是shmctl,該接口本質的功能是控制共享內存,只不過也有刪除選項,具體介紹如下:
#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid表示要釋放的共享內存的標識碼
//cmd表示該函數執行的具體任務,比如IPC_RMID表示刪除任務
//buf表示指向共享內存數據結構的指針,若使用刪除任務則該指針置為NULL即可//調用成功返回0,失敗返回-1
4.1 測試shmctl?
? ? ? ? ?測試shmctl的代碼如下:
#include "sharemem.hpp"int main()
{int shmid = creatnewshm();cout<<"申請共享內存成功"<<endl;sleep(2);//觀察nattch的值char* poi = (char*)shmat(shmid,NULL,0);cout<<"關聯共享內存成功"<<endl;sleep(2);//觀察nattch的值shmdt(poi);cout<<"成功去關聯共享內存"<<endl;sleep(2);//觀察nattch的值shmctl(shmid,IPC_RMID,nullptr);cout<<"成功刪除共享內存"<<endl;return 0;
}
? ? ? ? 運行結果:
? ? ? ? 從結果可以看到,無論是去關聯測試還是刪除共享內存,在右邊的監控中都會顯示對應的效果。?
5、實現共享內存通信?
?????????有了上述的接口以及sharemem.hpp文件,就可以實現兩個進程的通信了,所以需要寫一個客戶端進程和一個服務器進程,其中服務器進程創建共享內存,讓他們兩都關聯該共享內存,并且由客戶端向服務器發送消息,服務器代碼如下:
#include "sharemem.hpp"int main()
{int shmid = creatnewshm();char *poi = (char *)shmat(shmid, nullptr, 0);cout << "關聯共享內存成功" << endl;while (true){cout<<"服務器接收:"<<poi<<endl;sleep(1);}shmdt(poi);shmctl(shmid,IPC_RMID,nullptr);return 0;
}
? ? ? ? 客戶端代碼如下:
#include "sharemem.hpp"int main()
{int shmid = getoldshm();char* poi = (char*)shmat(shmid,nullptr,0);cout<<"關聯共享內存成功"<<endl;while (true){string message;cout<<"客戶端發送:";cin>>message;strcpy(poi,message.c_str());}shmdt(poi);return 0;
}
? ? ? ? 測試結果:
? ? ? ? 從結果可以發現,共享內存的通信本質就是對一個空間進行內存式的訪問,無需調用read、write這些系統接口,直接用內存函數寫入數據至內存對方就能夠讀取到內存里的數據。
6、共享內存的特性
? ? ? ? 1、共享內存不需要調用系統接口實現進程間通信,只需要調用內存函數對內存進行讀寫即可實現進程間通信。
? ? ? ? 2、共享內存本身沒有同步互斥的概念,體現在上面的運行結果中讀端會一直讀內容(說明沒有同步),并不會因為寫端還未寫而阻塞住。并且讀端和寫端可以同時訪問共享內存(說明沒有互斥)。
? ? ? ? 3、共享內存在讀寫效率上更為高效,因為少了write和read這些步驟,即少了一層拷貝。?
結語?
? ? ? ? 以上就是關于共享內存通信的講解,共享內存作為IPC的其中一種方式,相對于其他通信方式,他有利有弊,在實際應用里先熟悉他的接口以及使用共享內存的步驟:申請共享內存(包括創建key值)、關聯共享內存(shmat)、去關聯(shmdt)、釋放共享內存(shmctl)?,通過以上步驟可以實現完整的共享內存通信。
????????最后如果本文有遺漏或者有誤的地方歡迎大家在評論區補充,謝謝大家!!