目錄
一 前言
二?共享內存概念
?三 共享內存創建?
四 查看共享內存?
五 共享內存的刪除
六 共享內存的關聯?
七 共享內存去關聯?
八 共享內存的使用(通信)
?九 共享內存的特點
一 前言
共享內存區是最快的IPC形式(進程間通信:IPC,InterProcess Communication)?一旦這樣的內存映射到共享它的進程的地址空間,這些進程間數據傳遞不再涉及到內核,換句話說是進程不再通過執行進入內核的系統調用write()和read()來傳遞彼此的數據。
二?共享內存概念
?在上一篇進程間的管道通信中我們提到過,在進程間進行通信的時候,由于程序地址空間的存在,進程間的獨立性使得他們之間的通信很麻煩,如果想要通信則需要兩個進程看到同一份資源,上篇通過系統調用創建管道文件,使得進程之間看到共享資源(內存級文件),而本節進程間通信時進程之間看到的同一份資源是??共享內存。
🚀什么是共享內存呢??
實際上,我們在學習程序地址空間的時候,如上圖所示,我們已經看到了有一個區域的名字是共享區,在之前我們學習動靜態庫的時候,就說過動態庫是在進程運行的時候加載到程序地址空間中的共享區的。當程序需要的時候,就會來到這部分讀取數據。這一塊內存就可以看作是一塊只讀共享區,共享內存進程通信實際上就是這個原理。
? ? ? ? ? ? ?共享內存進程通信就是在物理內存中開辟一塊可以讓所有進程都看到的內存空間,然后多個進程只需要向這塊空間中讀取或者寫入數據,這樣就達到了多個進程間一起通信的目的。
也就是說,共享內存進程間的通信就是在物理內存中開辟一塊空間當作共享內存,然后通信的進程們通過各自的頁表將這塊物理內存(共享內存)映射到各自的程序地址空間中?
?三 共享內存創建?
shmget()? ? (share?memory? get)
這個接口的參數一共有三個?
- key_t key :??這是一個鍵值,key_t是一個整型,此參數其實是傳入的是一個整數。通常這個鍵是通過??ftok()?函數從一個文件路徑和一個項目ID生成的.?這個key值其實就是共享內存段在操作系統層面的唯一標識符。共享內存是Linux系統的一種進程通信的手段, 而操作系統中共享內存段一定是有許多的, 為了管理這些共享內存段, 操作系統一定會描述共享內存段的各種屬性。類似其他管理方式,操作系統也會為共享內存維護一個結構體,在這個結構體內會維護一個key值,表示此共享內存在系統層面的唯一標識符,其一般由用戶傳入,為了區別每一塊的共享內存,key的獲取也是有一定的方法。
ftok()函數的作用是將 一個文件 和 項目id 轉換為一個System V IPC key值。用戶就是使用這個函數來生成key值。
他有兩個參數,第一個參數顯而易見是文件的路徑,第二個參數則是隨意的8bite位的數值。ftok()函數執行成功則會返回一個key值,這個key值是該函數通過傳入文件的inode值和傳入的proi_id值通過一定的算法計算出來的。由于每一個文件的inode值是唯一的,所以我們不用擔心key值得重復。
-
size_t size:??該參數傳入的是想要開辟的共享內存的大小,單位是 byte字節。值得注意的是系統是按照4KB大小為單位開辟空間的,因為我們在磁盤一篇中學到系統I/O的單位大小就是4KB,也就是說無論這個參數傳的是1、1024還是4096時,系統都會開辟4KB,但是雖然系統是按照4KB為單位開辟的空間,但是實際上用戶能使用的空間的大小還是傳入的size字節大小。
-
int shmflg: 這里傳入的是一組標識位,可以控制shemget的行為,它包括權限標志(類似0666)和命令標志,就像我們使用文件接口open時的O_WRONLY、O_RDONLY一樣。共享內存接口標識符的兩個最重要的宏是:IPC_CREAT、IPC_EXCL
IPC_CREAT:傳入該宏,表示創建一個新的共享內存段,若共享內存段已經存在,則獲取此內存段;若不存在就創建一個新的內存段。
IPC_EXCL:該宏需要和IPC_CREAT一起使用。表示如果創建的內存段不存在,則正常創建,若存在則返回錯誤。使用該宏保證的是此次使用shmget()接口創建成功時,創建出來的共享內存是全新的。
-
shmget()函數的返回值,如果創建共享內存成功或者找到共享內存則返回共享內存的id,該id的作用是可以讓通信的進程找到同一份塊的資源。此id 是給上層用戶使用,是為了標識共享內存。而key也是為了標識共享內存,但是是從系統層面來說。
🚍:接下來我們來學習和認識共享內存的創建
///comm.hpp/
#ifndef _COMM_HPP_
#define COMM_HPP_#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>#define PATHNAME "."
#define PROJ_ID 0x66 //目標標識符 int proj_id 就是一個整型
#define MAX_SIZE 4096
key_t getKey()
{key_t k = ftok(PATHNAME,PROJ_ID);//可以獲取同一個keyif(k < 0){//cin ,cout,cerr -------->stdin stdout stderr--->0 1 2std::cerr<<errno<<":"<<strerror(errno)<<std::endl;//strerror(errno) 打印錯誤碼對應的錯誤信息exit(1);}return k;
}
/shm_client.cpp///
#include "comm.hpp"
int main()
{key_t k=getKey();printf("key: 0x%x\n",k); return 0;
}
///shm_server.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);return 0;
}
?運行結果:
?🚋:key的值是什么并不重要,重要的是能進行唯一性標識。
有了key之后,我們就可以用唯一的key進行共享內存的創建
//comm.hpp
//將一些函數進行封裝到comm.hpp,然后client和server進行調用
int getShmHelper(key_t k,int flags)
{int shmid=shmget(k,MAX_SIZE,flags);//創建共享內存函數shmgetif(shmid < 0){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(2);}return shmid;
}//獲取共享內存
int getShm(key_t k)
{return getShmHelper(k,IPC_CREAT); //如果存在就獲取,所以在客戶端client,我們可以通過這個函數,來獲取共享內存
}
////創建共享內存
int createShm(key_t k)//創建共享內存的工作,有服務端server來做
{return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0600);//如果存在就創建失敗,保證了我們創建的一定是新的共享內存,0600 代表創建的共享內存可讀可寫
}
//server.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=createShm(k);//服務端進行創建共享內存printf("shmid: %d\n",shmid);return 0;
}
///client.cpp
#include "comm.hpp"
int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=getShm(k);//客戶端進行獲取共享內存printf("shmid: %d\n",shmid);return 0;
}
?運行結果:
?🍉這里的 key 和 shmid 有什么區別呢?
key:是系統層面的,系統通過key來創建共享內存。
shmid:是上層用戶層面,用戶通過shmid來找到共享內存。
四 查看共享內存?
可是當我們再次運行服務端的時候,會發現出現如下問題:文件已存在
?這是什么原因呢?事實上,共享內存并不會隨著進程的退出而退出,在創建共享內存的進程退出之后,共享內存是依舊存在于操作系統中的。而我們的服務端用key創建共享內存的時候,必須要求創建一個新的,如果當前的key對應的共享內存已經存在,則報錯。
我們可以通過命令查看共享內存資源: ipcs -m??
這表明共享內存的生命周期是隨著OS的,并不會因為進程的退出,而把共享內存刪除。
五 共享內存的刪除
我們可以使用 ipcrm -m (InterProcess Communication Remove Memory) 命令來刪除。
那我們是通過 key 刪除共享內存還是 shmid呢? 前面我們也說了key是內核層面,操作系統使用key,而刪除共享內存,是指令操作,屬于用戶層面,所以我們通過shmid刪除共享內存。
?我們還可以通過調用系統函數? shmctl 來對共享內存進行刪除
我們在comm.hpp中對 shmctl進行封裝成刪除共享內存的函數。
/comm.hpp
void delShm(int shmid)
{if(shmctl(shmid,IPC_RMID,nullptr)==-1)//返回-1代表調用失敗{std::cerr<<"shmctl"<<errno<<":"<<strerror(errno)<<std::endl;}
}
/sever.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=createShm(k);//服務端進行創建共享內存printf("shmid: %d\n",shmid);sleep(5);delShm(shmid);//調用刪除共享內存函數}
?再次測試:
六 共享內存的關聯?
🌏:前面我們講述了服務端對共享內存的創建以及刪除,但是要想使得兩個進程進行通信,我們還需要進行共享內存對兩個進程關聯起來。
?系統調用函數 shmat (attach)
?我們在comm.hpp中對 shmat進行封裝成關聯共享內存的函數。
/comm.hpp//
//關聯共享內存
void* attachShm(int shmid)
{void* mem =shmat(shmid,nullptr,0);if((long long)mem==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(3);}return mem;
}
///server.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=createShm(k);//服務端進行創建共享內存printf("shmid: %d\n",shmid);sleep(5);//關聯共享內存char* start=(char*)attachShm(shmid);//返回值是共享內存地址printf("attach success,address start:%p\n",start);//刪除sleep(5);delShm(shmid);return 0;
}
測試結果
七 共享內存去關聯?
既然共享內存可以關聯,自然也可以去關聯,去關聯并不是刪除共享內存,而是去除進程與共享內存的聯系。
系統調用函數 shmdt()? ? (detach)
我們在comm.hpp中對 shmdt進行封裝成去關聯共享內存的函數。
//comm.cpp////去關聯共享內存
void detachShm(void* start)
{if(shmdt(start)==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;}
}
八 共享內存的使用(通信)
🌿,在前面我們做了以下工作
共享內存的創建------------->關聯共享內存---------->(這里我們將進行共享內存的通信)---------------->關聯共享內存------------------------------------>刪除共享內存?
//client.cpp
// 4.使用即通信const char* message="hello server, 我是另外一個進程正在和你通信";pid_t id=getpid();int count =0;while(true){sleep(1);//向共享內存輸入消息snprintf(start,MAX_SIZE,"%s[pid:%d][消息編號:%d]",message,id,count++);//pid count message}
/server.cpp///
//4.使用while(true){printf("client say: %s\n",start);//打印由客戶端發來的消息,直接打印共享內存地址即可sleep(1);}
測試結果:
?
?完整測試如下
/comm.hpp
#ifndef _COMM_HPP_
#define COMM_HPP_#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>#define PATHNAME "."
#define PROJ_ID 0x66 //目標標識符 int proj_id 就是一個整型
#define MAX_SIZE 4096
key_t getKey()
{key_t k = ftok(PATHNAME,PROJ_ID);//可以獲取同一個keyif(k < 0){//cin ,cout,cerr -------->stdin stdout stderr--->0 1 2std::cerr<<errno<<":"<<strerror(errno)<<std::endl;//strerror(errno) 打印錯誤碼對應的錯誤信息exit(1);}return k;
}int getShmHelper(key_t k,int flags)
{int shmid=shmget(k,MAX_SIZE,flags);//創建共享內存函數shmgetif(shmid < 0){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(2);}return shmid;
}//獲取共享內存
int getShm(key_t k)
{return getShmHelper(k,IPC_CREAT); //如果存在就獲取,所以在客戶端client,我們可以通過這個函數,來獲取共享內存
}
////創建共享內存
int createShm(key_t k)//創建共享內存的工作,有服務端server來做
{return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0666);//如果存在就創建失敗,保證了我們創建的一定是新的共享內存
}//關聯共享內存
void* attachShm(int shmid)
{void* mem =shmat(shmid,nullptr,0);if((long long)mem==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(3);}return mem;
}//去關聯共享內存
void detachShm(void* start)
{if(shmdt(start)==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;}
}
//刪除共享內存
void delShm(int shmid)
{if(shmctl(shmid,IPC_RMID,nullptr)==-1)//返回-1代表調用失敗{std::cerr<<"shmctl"<<errno<<":"<<strerror(errno)<<std::endl;}
}#endif
client.cpp///
#include "comm.hpp"
int main()
{//1.獲取keykey_t k=getKey();printf("key: 0x%x\n",k);//2.獲取共享內存int shmid=getShm(k);//客戶端進行獲取共享內存printf("shmid: %d\n",shmid);sleep(5);//3.進行關聯char* start=(char*)attachShm(shmid);printf("attach success,address start:%p\n",start);sleep(5);// 4.使用即通信const char* message="hello server, 我是另外一個進程正在和你通信";pid_t id=getpid();int count =0;while(true){sleep(1);//向共享內存輸入消息snprintf(start,MAX_SIZE,"%s[pid:%d][消息編號:%d]",message,id,count++);//pid count message}// sleep(5);//5.去關聯detachShm(start);return 0;
}
/server.cpp
#include "comm.hpp"int main()
{//1.創建keykey_t k=getKey();printf("key: 0x%x\n",k);//2.創建共享內存int shmid=createShm(k);//服務端進行創建共享內存printf("shmid: %d\n",shmid);sleep(5);//3. 關聯共享內存char* start=(char*)attachShm(shmid);//返回值是共享內存地址printf("attach success,address start:%p\n",start);//4.使用while(true){printf("client say: %s\n",start);//打印由客戶端發來的消息,直接打印共享內存地址即可sleep(1);}// // 5.去關聯detachShm(start);sleep(5);//刪除sleep(10);delShm(shmid);return 0;
}
?九 共享內存的特點
共享內存的優點:所以進程間通信,速度是最快的,能大大減少拷貝次數。
同樣的代碼,考慮到鍵盤輸入和顯示器輸出 ,如果用管道來實現,會對數據進行幾次拷貝?
共享內存的缺點:不給我們進行同步和互斥的操作,沒有對數據做任何保護。
即客戶端不進行寫,服務端也一直進行讀取,服務端不進行讀取,客戶端依然進行寫入。?