目錄
?寫在前面的話
System V共享內(nèi)存原理
System V共享內(nèi)存的建立
代碼實現(xiàn)System V共享內(nèi)存
創(chuàng)建共享內(nèi)存shmget()
ftok()
刪除共享內(nèi)存shmctl()
掛接共享內(nèi)存shmat()
取消掛接共享內(nèi)存shmdt()
整體通信流程的實現(xiàn)
?寫在前面的話
???????? 上一章我們講了進程間通信的第一種方式 --- 管道,這一章我們將繼續(xù)講解進程間的通信的第二種方式 --- system V共享內(nèi)存。
? ? ? ? 在講解之前,還是先建議去把進程間通信管道的方式和原理搞明白,這樣理解起system V共享內(nèi)存來會簡單許多。
System V共享內(nèi)存原理
????????共享內(nèi)存是一種進程間通信(IPC)的機制,允許多個進程共享同一塊內(nèi)存區(qū)域,以便它們可以直接讀取和寫入其中的數(shù)據(jù),從而實現(xiàn)高效的數(shù)據(jù)共享和通信。
?????????在共享內(nèi)存中,當進程task_struct創(chuàng)建或連接到共享內(nèi)存段時,操作系統(tǒng)會為每個進程分配一個虛擬地址空間mm_srtuct,將這個虛擬地址通過頁表 映射到相同的物理內(nèi)存區(qū)域。這樣,多個進程就可以通過自己的虛擬地址訪問相同的物理內(nèi)存,實現(xiàn)對同一塊內(nèi)存的共享訪問。通過虛擬地址到物理地址的映射,多個進程可以看到同一個共享內(nèi)存.
? ? ? ? 那么如何釋放共享內(nèi)存呢?
也很簡單,只需要將每個進程 和 共享內(nèi)存建立的映射去掉,然后釋放掉共享內(nèi)存即可.
System V共享內(nèi)存的建立
? ? ? ? 假設有很多進程都在用共享內(nèi)存,這樣內(nèi)存中也會出現(xiàn)大量的 共享內(nèi)存塊,所以OS要把這些共享內(nèi)存塊管理起來,方式:先描述再組織,這樣要把共享內(nèi)存屬性抽象成數(shù)據(jù)結(jié)構(gòu),然后利用一些方式將這些數(shù)據(jù)結(jié)構(gòu)組織起來.所以:
? ? ? ?1. 共享內(nèi)存 = 共享內(nèi)存塊 + 共享內(nèi)存塊對應的內(nèi)核數(shù)據(jù)結(jié)構(gòu)
? ? ? ?2.共享內(nèi)存塊一定不屬于任何一個進程,而是屬于操作系統(tǒng).
建立共享內(nèi)存的大體流程如下:
創(chuàng)建共享內(nèi)存段:使用?
shmget()
?系統(tǒng)調(diào)用來請求創(chuàng)建一個共享內(nèi)存段。該調(diào)用需要指定共享內(nèi)存的大小、權(quán)限和標志等參數(shù),并返回一個唯一的共享內(nèi)存標識符。連接到共享內(nèi)存段:使用?
shmat()
?系統(tǒng)調(diào)用將當前進程附加到共享內(nèi)存段。這個調(diào)用將返回共享內(nèi)存段的地址,并將該地址映射到當前進程的虛擬地址空間。訪問共享內(nèi)存:連接到共享內(nèi)存的進程可以通過在其地址上執(zhí)行內(nèi)存操作,直接讀取和寫入共享內(nèi)存段中的數(shù)據(jù)。進程可以使用指針、數(shù)組或結(jié)構(gòu)體等方式在共享內(nèi)存段中存儲和訪問數(shù)據(jù)。
分離共享內(nèi)存:當進程完成對共享內(nèi)存的訪問后,使用?
shmdt()
?系統(tǒng)調(diào)用將其與共享內(nèi)存段分離。分離后,進程將無法再訪問共享內(nèi)存段,但共享內(nèi)存段仍然存在。刪除共享內(nèi)存段:當不再需要共享內(nèi)存段時,可以使用?
shmctl()
?系統(tǒng)調(diào)用刪除它。這個調(diào)用需要指定共享內(nèi)存標識符和特定的控制操作,比如傳遞?IPC_RMID
?參數(shù)表示刪除共享內(nèi)存段.
? ? ? ? 具體的使用方法及原理,我們在下面馬上講解。
代碼實現(xiàn)System V共享內(nèi)存
? ? ? ? 按我們上面所說的,第一步是利用shmget()建立共享內(nèi)存段,我們來看一下它的用法.
創(chuàng)建共享內(nèi)存shmget()
? ? ? ? 這個函數(shù)作用是創(chuàng)建并獲取一個共享內(nèi)存。
? ? ? ? 先說第二個參數(shù),是size,代表的是要創(chuàng)建的共享內(nèi)存有多大
? ? ? ? 再來說第三個參數(shù)shmflg,代表我們要設置的選項,共有兩個選項:
????????1.IPC_CREATE:創(chuàng)建共享內(nèi)存,如果底層已經(jīng)存在,則獲取并返回;如果不存在,則創(chuàng)建共享內(nèi)存然后再返回,
????????2.IPC_EXCL:單獨使用它沒有意義,一般和IPC_CREATE合起來使用,見下:
? ? ? ? 3.IPC_CREATE | IPC_EXCL:如果底層不存在,則創(chuàng)建共享內(nèi)存并返回;如果底層存在,則出錯返回。言外之意,如果返回成功,那么一定是一個全新的內(nèi)存塊!
? ? ? ? 最后再來說第一個參數(shù) key.
? ? ? ? 我們利用共享內(nèi)存通信時會有一個問題,要通信的對方進程,怎么保證對方看到的共享內(nèi)存就是我創(chuàng)建的呢?畢竟有很多共享內(nèi)存.
? ? ? ? 這個我們就需要通過key,key數(shù)據(jù)是多少不重要,只要保證在系統(tǒng)里唯一即可! 兩個通信端A 和 B,只要使用同一個key,便可以找到同一塊共享內(nèi)存。因為key是唯一的,即這個共享內(nèi)存塊也是唯一的!
? ? ? ? 相當于是給每個共享內(nèi)存塊編了個號,這個號碼是唯一的,所以只要拿到了編號,就能找到相同的共享內(nèi)存塊。
? ? ? ? 那么這個唯一的key值如何得到呢?這里需要用到ftok()函數(shù)
ftok()
我們先來看一下函數(shù)的使用:
-
pathname
:一個字符串,用于標識一個文件的路徑名。通常會選擇一個已經(jīng)存在的文件,因為?ftok()
?函數(shù)將使用該文件的inode編號和?proj_id
?參數(shù)通過算法來生成鍵值key。 -
proj_id
:一個整數(shù),作為用于生成鍵的項目標識號。該參數(shù)通常取一個非負整數(shù)。
然后我們來看一下返回值:
? ? ? ? ?看到如果成功的話,生成的鍵值被返回,否則-1被返回。
這些說清楚了,我們來用一下吧:
首先四個文件,comm.hpp,里面包含了必要的頭文件及宏定義,這個便不再展示了;
? ? ? ? Log.hpp是日志文件,上一章我們寫了,這里不寫也可以,直接cout輸出也行。
? ? ? ? shmClient.cc中我們寫入以下代碼:
#include "comm.hpp"
#include "Log.hpp"
int main()
{
key_t k = ftok(PATH_NAME,PROJ_ID);
Log("create key done",Debug) << " client key : " << k << endl;
return 0;
}
? ? ? ? shmServer中拷貝一份代碼,然后把輸出語句中的client改成server,然后我們編譯運行來及看一下效果:
?????????可以發(fā)現(xiàn)生成了一樣的key值,這樣便可以找到同一塊共享內(nèi)存塊了.
這樣shmget的第一個參數(shù)也講完了,接下來說一下返回值.
????返回值是如果建立成功,則返回這段共享內(nèi)存的標識符,否則返回-1并且錯誤碼被設置。
刪除共享內(nèi)存shmctl()
????????當我們創(chuàng)建好共享內(nèi)存后,最后還需要刪除它,因為共享內(nèi)存的生命周期是隨內(nèi)核的!
? ? ? ? 不關(guān)閉的話,只要操作系統(tǒng)一直在運行,那么它就一直存在,占用空間資源。所以必須需要刪除.
? ? ? ? 有兩種方法刪除:手動指令刪除、代碼刪除
指令刪除:
? ? ? ? 我們首先創(chuàng)建了一個共享內(nèi)存,然后該進程執(zhí)行完畢,進程退出。? ? ? ? ? ? ? ??
?????????我們在終端輸入
ipcs -m
? ? ? ? 來查看當前共享內(nèi)存的使用情況
?????????可以發(fā)現(xiàn)確實沒有釋放掉。然后我們可以利用
ipcrm -m shmid
? ? ? ? 來刪除對應的共享內(nèi)存,此時我輸入這條指令后便沒有了這塊內(nèi)存了。
這里提到了perms,這個是權(quán)限的意思,我們可以在shmget的第三個選項 加上權(quán)限,如下:
int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
此時perms就變成了666.?
代碼刪除
????????每次手動刪除太麻煩了,我們直接卸載程序里到時候自動幫我們刪除不更方便嗎
? ? ? ? 所以我們需要用到一個函數(shù)shmctl()
?
-
shmid
:共享內(nèi)存段的標識符(ID),即通過調(diào)用?shmget()
?函數(shù)創(chuàng)建共享內(nèi)存時返回的?shmid
。 -
cmd
:控制命令,用于指定要執(zhí)行的操作類型。可以使用以下命令之一:-
IPC_STAT
:獲取共享內(nèi)存段的狀態(tài)信息,將結(jié)果存儲在?buf
?參數(shù)指向的?struct shmid_ds
?結(jié)構(gòu)體中。 -
IPC_SET
:設置共享內(nèi)存段的狀態(tài)信息,使用?buf
?參數(shù)中提供的值。 -
IPC_RMID
:刪除共享內(nèi)存段,將其標記為刪除狀態(tài),并在釋放最后一個進程的附加段之后銷毀。
-
-
buf
:一個指向?struct shmid_ds
?結(jié)構(gòu)體的指針,用于傳遞或接收共享內(nèi)存段的狀態(tài)信息。
? ? ?這是struct shmid_ds結(jié)構(gòu)體指針的內(nèi)容:
struct shmid_ds { struct ipc_perm shm_perm; // 共享內(nèi)存的權(quán)限信息 size_t shm_segsz; // 共享內(nèi)存的大小 time_t shm_atime; // 上一次連接共享內(nèi)存的時間 time_t shm_dtime; // 上一次與共享內(nèi)存斷開連接的時間 time_t shm_ctime; // 上一次修改共享內(nèi)存的時間 pid_t shm_cpid; // 創(chuàng)建共享內(nèi)存的進程ID pid_t shm_lpid; // 最后一個操作共享內(nèi)存的進程ID unsigned short shm_nattch; // 當前連接到共享內(nèi)存的進程數(shù)量 // 其他字段... };
其中第二個參數(shù)cmd我們目前只使用第3個IPC_RMID刪除共享內(nèi)存的選項。
第三個參數(shù)buf我們暫且不使用,直接傳入nullptr。
int n = shmctl(shmid,IPC_RMID,nullptr);
掛接共享內(nèi)存shmat()
????????我們創(chuàng)建好了共享內(nèi)存,當然需要將進程的地址空間與其掛接上,然后 才能使用,這里使用到了函數(shù)shmat().
?
shmat()
?函數(shù)接受三個參數(shù):
-
shmid
:共享內(nèi)存段的標識符(ID),即通過調(diào)用?shmget()
?函數(shù)創(chuàng)建共享內(nèi)存時返回的?shmid
。表示你想掛接哪一個共享內(nèi)存。 -
shmaddr
:共享內(nèi)存段連接到進程地址空間的首地址。通常將其設置為?NULL
,指示系統(tǒng)選擇適當?shù)牡刂贰?/strong>如果想要指定特定的地址,可以傳遞一個非空的地址值。但不建議這樣使用。 -
shmflg
:標志參數(shù),用于指定連接共享內(nèi)存的選項。常用的選項有:-
SHM_RDONLY
:以只讀方式連接共享內(nèi)存,不允許寫入。 -
SHM_RND
:將?shmaddr
?參數(shù)忽略,系統(tǒng)選擇一個地址以進行連接。
-
? ? ? ? ? ? ? 其他選項參考?shmat()
?函數(shù)的文檔以獲得更多詳細信息。
????????它的返回值是void*,是一個指向共享內(nèi)存段的指針,即連接到進程地址空間的首地址。我們需要將結(jié)果強轉(zhuǎn)為我們需要的類型,一般為char*,和malloc的使用類似。
所以可以如下這樣使用:
char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);
shmaddr便是連接的進程地址空間的首地址。
取消掛接共享內(nèi)存shmdt()
????????刪除共享內(nèi)存時,無論有多少個進程與共享內(nèi)存連接,都會被直接清理掉,這種方式不太好,所以在刪除共享內(nèi)存前,可以先取消掛接。當掛接數(shù)為0時,再釋放共享內(nèi)存。
? ? ? ? 同樣先來看一下用法:
?????????這個參數(shù)正好是我們剛才shmat返回的進程地址空間的首地址shmaddr,表示將共享內(nèi)存從調(diào)用進程的地址空間中分離,使得該進程無法再訪問該共享內(nèi)存。
????????再來看一下返回值:
? ? ? ? 如果取消掛接成功,則返回0,失敗則返回-1.
所以我們可以直接這么使用:
//3.將指定的共享內(nèi)存,掛接到自己的地址空間
char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);
//這里就是通信的邏輯
//4.將指定的共享內(nèi)存,從自己的進程地址空間取消關(guān)聯(lián)
int n = shmdt(shmaddr);
assert(n != -1);
整體通信流程的實現(xiàn)
? ? ? ? 認識了上面的接口,加上我們在共享內(nèi)存的建立中所說的步驟,我們便可以制作一個完整的利用System V共享內(nèi)存通信的流程了。????????
? ? ? ? 0. 我們要利用ftok()生成共享內(nèi)存唯一標識key.
? ? ? ? 1. 然后我們利用key調(diào)用shmget()函數(shù)創(chuàng)建共享內(nèi)存
? ? ? ? 2. 接著我們需要利用shmat()掛接上共享內(nèi)存
? ? ? ? 3. 然后實現(xiàn)通信的流程
? ? ? ? 4. 利用shmdt()取消掛接
? ? ? ? 5. 利用shmctl()刪除共享內(nèi)存(一般是server,誰創(chuàng)建的誰刪除)
這里一共四個文件,分別為Log.hpp(日志信息),comm.hpp(共用的頭文件),shmServer.cc, shmClient.cc?
Log.hpp(日志信息)
#pragma once #include <iostream> #include <ctime> #include<string> using namespace std; #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 string msg[] = { "Debug ", "Notice", "Warning", "Error" }; ostream& Log(string message,int level) { cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message; return cout; }
comm.hpp(共用的頭文件)
#pragma once #include<iostream> #include<string> #include<cstring> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> using namespace std; #define PATH_NAME "/home/hyx" #define PROJ_ID 0x66 #define SHM_SIZE 4096 //最好是頁(PAGE:4096)的整數(shù)倍,假設是4097,OS也會申請4096*2的空間,剩下的4095空間相當于浪費了
shmServer.cc
#include "comm.hpp" #include "Log.hpp" string TransToHex(key_t k) { char buffer[32]; snprintf(buffer, sizeof(buffer),"0x%x",k); return buffer; } int main() { //1.創(chuàng)建公共的key值 key_t k = ftok(PATH_NAME,PROJ_ID); Log("create key done",Debug) << " server key : " << TransToHex(k) << endl; //2.創(chuàng)建共享內(nèi)存 --- 建議創(chuàng)建一個全新的共享內(nèi)存 int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666); if(shmid == -1) { perror("shmget"); exit(1); } Log("create shm done",Debug) << " shmid: " << shmid << endl; // sleep(10); //3.將指定的共享內(nèi)存,掛接到自己的地址空間 char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY); Log("attach shm done",Debug) << " shmid: " << shmid << endl; // sleep(10); //這里就是通信的邏輯 //將共享內(nèi)存當做一個大字符串 // char buffer[SHM_SIZE]; //結(jié)論1:只要雙方使用shm,一方直接向共享內(nèi)存中寫入數(shù)據(jù)。另一方就可以立馬看到 // 共享內(nèi)存是所有進程IPC,速度最快的! 因為不需要過多的拷貝!表現(xiàn)為不需要將數(shù)據(jù)拷貝給操作系統(tǒng) for(;;) { printf("%s\n",shmaddr); if(strcmp(shmaddr,"quit") == 0) break; sleep(1); } //4.將指定的共享內(nèi)存,從自己的進程地址空間取消關(guān)聯(lián) int n = shmdt(shmaddr); assert(n != -1); Log("detach shm done",Debug) << " shmid: " << shmid << endl; // sleep(10); //5..刪除共享內(nèi)存 n = shmctl(shmid,IPC_RMID,nullptr); assert(n != -1); Log("delete shm done",Debug) << " shmid: " << shmid << endl; return 0; }
shmClient.cc?
#include "comm.hpp" #include "Log.hpp" int main() { key_t k = ftok(PATH_NAME,PROJ_ID); if(k < 0) { Log("create key done", Error) << "client key : " << k << endl; exit(1); } Log("create key done",Debug) << " client key : " << k << endl; //獲取共享內(nèi)存 int shmid = shmget(k,SHM_SIZE,IPC_CREAT); if(shmid < 0) { Log("create shm failed", Error) << "client key : " << k << endl; exit(2); } Log("create shm success", Debug) << "client key : " << k << endl; // sleep(10); char* shmaddr = (char*)shmat(shmid,nullptr,0); if(shmaddr == nullptr) { Log("attach shm failed", Error) << "client key : " << k << endl; exit(3); } Log("attach shm success", Debug) << "client key : " << k << endl; // sleep(10); //使用 //client將共享內(nèi)存看做一個char類型的buffer char a = 'a'; for(;a <= 'e'; a++) { //我們是每一次都向shmaddr[共享內(nèi)存起始地址]寫入 snprintf(shmaddr,SHM_SIZE-1,\ "hello,server, my pid is: %d, inc : %c\n",getpid(),a); sleep(2); } strcpy(shmaddr,"quit"); //去關(guān)聯(lián) int n = shmdt(shmaddr); assert(n != -1); Log("detach shm success",Debug) << "client key : " << k << endl; // sleep(10); //client 不需要刪除共享內(nèi)存,server負責這些,和client沒關(guān)系。 return 0; }
然后我們編譯運行,分兩個窗口觀察:
?client
?可以看到,雙方都實現(xiàn)了通信。文章來源:http://www.zghlxwxcb.cn/news/detail-626446.html
到這里System V共享內(nèi)存就介紹完畢了,如果有不懂或疑問的地方,歡迎評論區(qū)或私信哦~文章來源地址http://www.zghlxwxcb.cn/news/detail-626446.html
到了這里,關(guān)于【Linux】進程間通信——system V共享內(nèi)存的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!