目錄
前置:
一 原理
二 API
1. shmgetr
2. shmctl
3. 指令操作
2. 刪除
3. 掛接
4. 斷開掛接
三 demo代碼
四 共享內存的特征
前置:
1.前面說的不管是匿名管道還是命名管道都是基于文件的思想構建的一套進程間通信的方案,那有沒有完全從0單獨設計一套呢?
2. 共享內存作為 system v 標準,是由操作系統自主從0搭建的一套進程間通信機制,相對于管道而言也更快,那么下面來看看具體是怎么實現的。
一 原理
1. 進程之間通信之前必須要看到同一份資源,比如管道,看到同一個管道文件(路徑),子進程繼承父進程文件描述符。
2. 共享內存其實是在內存中開辟一段空間,并把起始地址映射到虛擬地址空間的共享區當中,訪問這個虛擬地址也就訪問到內存上的空間了,那么2個毫不相干的進程怎么看到這個共享內存呢?
3. 之前說的動態庫,在程序運行期間在內存中尋找庫文件,找不到在去磁盤上加載,所以在內存中有很多的動態庫,勢必也要管理維護起來并且提供一個字段來標識這個實例,共享內存也同樣如此,在內存中也有個結構進行維護并標識起來。
4. 讓不同進程看到同一份標識,系統提供了一個API
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, // 隨便傳入路徑int proj_id // 隨便傳入一個id// key_t -> int);
1. 傳入一個路徑和id并通過算法形成一個key_t結構,本質是int類型,算法是固定的,所以不同的進程傳入相同的字符串和id得到的key_t都是一樣的,這是用戶傳入數據生成的。
2. 返回值為-1錯誤,其他均正確。
所以不同的進程傳入相同的路徑和id就能看到同一個共享內存了。
5. 建立共享內存的映射,然后就需要進行把進程和共享內存進行掛接,也就是綁在一起,進行后續通信,結束在斷開掛接。
6. 共享內存只有創建和掛接/斷開掛接/釋放共享內存的時候使用了系統調用,通信的時候不需要,所以直接在用戶層直接向物理內存寫入數據,是進程間通信里最快的,僅需一次拷貝物理內存里的數據。
二 API
1. shmgetr
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, // 用戶生成的 key 值 size_t size, // 共享內存的大小int shmflg // 選項: 創建/獲取....);// 返回值: 內核維護的 shmid 值,用來管理共享內存的結構
2. shmctl
#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, // shmget的返回值int cmd, // 對共享內存: 刪除/修改等操作struct shmid_ds *buf // 共享內存的結構里的字段);
// 返回值: 成功為0,失敗-1
為什么有了 key ,還要有 shmid ?key是用戶形成的只是用來獲取和創建共享內存的,并不能對已經存在的共享內存進行字段修改/結構修改等,只有shmid能操作,本質就是 shmid 屬于內核提供的,key是有用戶提供的,他們的功能側重點不同,用戶管用戶,內核管內核,變相的解耦。?
3. 指令操作
1. 查看
ipcs -m // 共享內存相關信息
ipcs -q // 消息隊列相關信息
ipcs -s // 信號量相關信息
查詢到的字段
key:? ? 用戶形成的隨機值,用來獲取,創建訪問共享內存。
shmid:shmget的返回值,用來管理共享內存結構的。
owner:誰創建的。
perms:權限是什么。
bytes: 共享內存多大。
nattch:誰掛接上了。
status: 狀態標識。
2. 刪除
ipcrm -m/q/s shmid
3. 掛接
#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, // shmget的返回值const void *shmaddr, // 共享內存的起始地址int shmflg // 權限設置);
4. 斷開掛接
#include <sys/types.h>
#include <sys/shm.h>int shmdt(const void *shmaddr // shmat的返回值);
三 demo代碼
#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <cstring>// htok 用來形成隨機的 key_t
const static std::string pathkey = "/home/CD/linux/shared_memory";
const static int projid = 123456;// 區分創建者和使用者
const static std::string Create = "Create";
const static std::string User = "User";// 共享內存大小
const int shmbuff = 4096;// 轉16進制
std::string to_hex(int val)
{char buff[1024] = {0};snprintf(buff, 1024, "0x%x", val);return buff;
}// SHM類
class SHM
{
private:// 進行掛接bool Shmat(){_buff = shmat(_shmid, nullptr, 0);if (_buff == (void *)-1)return false;return true;}// 創建共享內存并獲取 shmidbool Createshmid(){_shmid = shmget(_key, shmbuff, IPC_CREAT | IPC_EXCL | 0666);if (_shmid == -1){std::cout << "shmgid create failed" << std::endl;return false;}return true;}// 獲取已經存在的共享內存的shmidbool Getshmid(){_shmid = shmget(_key, shmbuff, IPC_CREAT);if (_shmid == -1){std::cout << "shmget get failed" << std::endl;return false;}std::cout << _shmid << std::endl;return true;}public:// 構造對象SHM(const std::string &path, int proj_id, const std::string &who): _path(path), _proj_id(proj_id), _who(who), _buff(nullptr){_key = ftok(_path.c_str(), proj_id);if (_key == -1){std::cout << "ftok failed" << std::endl;exit(-1);}}// 創建共享內存/獲取共享內存shmid/掛接共享內存/獲取共享內存的起始地址void *run(){if (_who == Create){if (Createshmid() == false)exit(-2);if (Shmat() == false){std::cout << "Shmat failed" << std::endl;exit(-3);}}else{if (Getshmid() == false)exit(-2);if (Shmat() == false){std::cout << "Shmat failed" << std::endl;exit(-3);}}memset(_buff, 0, shmbuff);return _buff;}// 釋放共享內存/斷開掛起~SHM(){if (_who == Create){int n = shmctl(_shmid, IPC_RMID, nullptr);if (n == -1){std::cout << "ftok failed" << std::endl;exit(-3);}}if (_buff != nullptr)shmdt(_buff);}private:std::string _path;int _proj_id;std::string _who;int _shmid;key_t _key;void *_buff;
};
四 共享內存的特征
1. 由于通信是在用戶層直接寫到物理內存,沒有系統調用,所以不像管道有read/write接口,read的時候沒有數據內核會阻塞,而共享內存則沒有任何保護機制,也就是無同步無互斥,所以需要應用層進行處理。
2. 共享內存是進程間通信里最快的,用戶層直接通過共享映射的虛擬地址寫入即可,無任何系統調用。
3. 共享內存的大小是 4k 為基本單位的,不符合倍數就向上取整。
4. 共享內存的生命周期隨內核,除非手動代碼/指令進行釋放。