Linux之systemV共享內(nèi)存方式
一、工作原理

操作系統(tǒng)在物理內(nèi)存上申請(qǐng)一塊空間,然后將申請(qǐng)到的空間通過(guò)頁(yè)表映射到進(jìn)程地址空間mm_struct的共享區(qū)中,然后返回虛擬地址供程序使用,如果多個(gè)進(jìn)程申請(qǐng)的是同一塊物理空間,那么它們就可以進(jìn)行通信由于同一時(shí)間可能有多組進(jìn)程進(jìn)行通信,所以系統(tǒng)當(dāng)中可能存在多個(gè)共享內(nèi)存塊,所以操作系統(tǒng)要把這些內(nèi)存管理起來(lái),所以內(nèi)核中會(huì)有一個(gè)結(jié)構(gòu)體來(lái)描述共享內(nèi)存
二、系統(tǒng)調(diào)用接口
1、申請(qǐng)共享內(nèi)存
(一)key的獲取
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
- 返回值:成功ftok 會(huì)將
proj_id與pathname對(duì)應(yīng)的文件信息結(jié)合起來(lái)生成最終的key值,失敗返回-1 pathname:已經(jīng)存在的文件或目錄的路徑名proj_id:由用戶指定的非零整數(shù),通常是一個(gè)單字符(因?yàn)橹挥械?8 位會(huì)被使用),
只要這兩個(gè)才參數(shù)一致,那么生成的key值就一定一致
key值是我們少見(jiàn)的由我們自己傳參生成的固定的值,Linux在涉及內(nèi)存方面的時(shí)候,通常是操作系統(tǒng)代為處理,這里其實(shí)是因?yàn)榧僭O(shè)我們有兩個(gè)進(jìn)程AB,操作系統(tǒng)生成一個(gè)key給到A,但是B也需要這個(gè)key,但是操作系統(tǒng)是不知道哪兩個(gè)進(jìn)程要建立信道進(jìn)行通信的,只有程序員知道我們哪兩個(gè)進(jìn)程要建立信道進(jìn)行通信,因?yàn)樵诮⑿诺乐斑M(jìn)程之間是相互獨(dú)立的,所以就不能由操作系統(tǒng)來(lái)分配key值
(二)共享內(nèi)存的申請(qǐng)
shmget函數(shù)的主要作用是在內(nèi)核中創(chuàng)建或獲取共享內(nèi)存段的標(biāo)識(shí)符
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
返回值:成功返回一個(gè)非負(fù)整數(shù),即該共享內(nèi)存段的標(biāo)識(shí)碼(shmid),失敗返回-1
key:key是一個(gè)數(shù)字,它是不同共享內(nèi)存讓自己具備唯一性的標(biāo)識(shí)size:共享內(nèi)存大小,單位為字節(jié),一般最好是4096的整數(shù)倍,它按照頁(yè)申請(qǐng),即需要4097字節(jié)就申請(qǐng)4096*2個(gè)字節(jié)
shmflg是一個(gè)標(biāo)識(shí)符
| shmflg | 含義 | 作用 |
|---|---|---|
| IPC_CREAT | 創(chuàng)建標(biāo)志 | 如果指定的共享內(nèi)存段不存在,就創(chuàng)建一個(gè)新的共享內(nèi)存段;若已存在,則直接返回其標(biāo)識(shí)符,常用于在不確定共享內(nèi)存是否存在時(shí)創(chuàng)建或獲取它 |
| IPC_EXCL | 排他標(biāo)志 | 需和 IPC_CREAT 一起使用,若共享內(nèi)存已存在,shmget 調(diào)用失敗,errno 設(shè)為 EEXIST,可確保新創(chuàng)建共享內(nèi)存 |
| IPC_NOWAIT | 非阻塞標(biāo)志 | 當(dāng)操作不能立即完成時(shí),不阻塞調(diào)用進(jìn)程,直接返回錯(cuò)誤,例如在獲取共享內(nèi)存時(shí),若當(dāng)前資源無(wú)法立即獲取,不等待而直接返回錯(cuò)誤 |
| SHM_HUGETLB | 大頁(yè)標(biāo)志 | 嘗試使用大頁(yè)內(nèi)存分配共享內(nèi)存段,大頁(yè)內(nèi)存可減少頁(yè)表項(xiàng)數(shù)量,降低內(nèi)存管理開(kāi)銷,提高系統(tǒng)性能,適合處理大量數(shù)據(jù) |
| SHM_NORESERVE | 不保留交換空間標(biāo)志 | 不預(yù)先為共享內(nèi)存段保留交換空間,正常創(chuàng)建共享內(nèi)存時(shí),系統(tǒng)會(huì)預(yù)留交換空間以防內(nèi)存不足,使用此標(biāo)志可節(jié)省交換空間,但可能導(dǎo)致內(nèi)存緊張時(shí)出現(xiàn)頁(yè)面交換問(wèn)題 |
| 權(quán)限標(biāo)志(如 0600、0666 等) | 權(quán)限設(shè)置 | 類似文件權(quán)限設(shè)置,用于規(guī)定共享內(nèi)存段的訪問(wèn)權(quán)限,0600 表示只有所有者有讀寫權(quán)限,0666 表示所有用戶都有讀寫權(quán)限 |
2、將共享內(nèi)存段連接到進(jìn)程地址空間
shmat函數(shù)的核心作用是在調(diào)用進(jìn)程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,在調(diào)用shmget函數(shù)時(shí),雖然創(chuàng)建或獲取了共享內(nèi)存段的標(biāo)識(shí)符,但進(jìn)程還不能直接訪問(wèn)該共享內(nèi)存,只有通過(guò)shmat函數(shù)將共享內(nèi)存段附加到進(jìn)程的地址空間后,進(jìn)程才能像訪問(wèn)普通內(nèi)存一樣訪問(wèn)共享內(nèi)存段中的數(shù)據(jù)
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
- 返回值:成功返回一個(gè)指針,指向共享內(nèi)存的起始地址,失敗返回 -1
shmid:共享內(nèi)存段的標(biāo)識(shí)符,用于指定要附加的共享內(nèi)存段,即shmget的返回值shmaddr:指定共享內(nèi)存段要附加到的進(jìn)程地址空間的地址,如果為 NULL,則由系統(tǒng)自動(dòng)選擇合適的地址shmflg:標(biāo)識(shí)符
| 類型 | 含義 | 作用 |
|---|---|---|
| SHM_RDONLY | 共享內(nèi)存只讀 | 以只讀模式將共享內(nèi)存段附加到進(jìn)程的地址空間,進(jìn)程只能讀取共享內(nèi)存中的數(shù)據(jù),不能進(jìn)行寫操作,增強(qiáng)數(shù)據(jù)安全性,適用于多進(jìn)程共享只讀數(shù)據(jù)的場(chǎng)景 |
| SHM_RND | 地址舍入 | 當(dāng) shmaddr 參數(shù)不為 NULL 時(shí),將 shmaddr 向下舍入到一個(gè)合適的內(nèi)存邊界(通常是系統(tǒng)頁(yè)面大小的整數(shù)倍),保證共享內(nèi)存的正確附加 |
| SHM_REMAP | 重新映射 | 如果共享內(nèi)存段已經(jīng)被附加到進(jìn)程的地址空間,使用此標(biāo)志可以重新映射該共享內(nèi)存段,常用于更新共享內(nèi)存的映射關(guān)系 |
| SHM_EXEC | 可執(zhí)行權(quán)限 | 允許在共享內(nèi)存段上執(zhí)行程序指令,不過(guò)并非所有系統(tǒng)都支持該標(biāo)志,適用于需要在共享內(nèi)存中執(zhí)行代碼的特殊場(chǎng)景 |
| SHM_COPY | 創(chuàng)建私有副本 | 某些系統(tǒng)支持該標(biāo)志,嘗試創(chuàng)建共享內(nèi)存段的一個(gè)私有副本,后續(xù)對(duì)該副本的修改不會(huì)影響其他進(jìn)程看到的共享內(nèi)存內(nèi)容,用于實(shí)現(xiàn)進(jìn)程對(duì)共享內(nèi)存的獨(dú)立修改 |
| SHM_ANON | 匿名共享內(nèi)存 | 部分系統(tǒng)支持該標(biāo)志,用于創(chuàng)建匿名共享內(nèi)存段,此時(shí) shmid 參數(shù)會(huì)被忽略,可結(jié)合 shmaddr 使用,常用于父子進(jìn)程間的內(nèi)存共享 |
3、將內(nèi)存共享段與當(dāng)前進(jìn)程脫離
當(dāng)進(jìn)程調(diào)用shmat函數(shù)將共享內(nèi)存段附加到自己的地址空間后,系統(tǒng)會(huì)在進(jìn)程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,使得進(jìn)程可以像訪問(wèn)普通內(nèi)存一樣訪問(wèn)共享內(nèi)存,而shmdt函數(shù)的核心作用就是解除這種映射關(guān)系
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
- 返回值:成功返回0,失敗返回-1
shmaddr:shmat返回的指針
4、控制共享內(nèi)存
通過(guò)cmd控制共享內(nèi)存
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 返回值:成功返回0,失敗返回-1
shmid:同上cmd:將要采取的動(dòng)作
| 類型 | 含義 | 作用 |
|---|---|---|
| IPC_STAT | 獲取狀態(tài)信息 | 將與 shmid 關(guān)聯(lián)的共享內(nèi)存段的當(dāng)前狀態(tài)信息復(fù)制到 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中,這個(gè)結(jié)構(gòu)體包含了如共享內(nèi)存段的大小、所有者、權(quán)限、創(chuàng)建時(shí)間、最后訪問(wèn)時(shí)間等信息 |
| IPC_SET | 設(shè)置狀態(tài)信息 | 使用 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中的值來(lái)更新與 shmid 關(guān)聯(lián)的共享內(nèi)存段的部分狀態(tài)信息,可以更新的信息包括共享內(nèi)存段的所有者、權(quán)限等 |
| IPC_RMID | 刪除共享內(nèi)存段 | 標(biāo)記與 shmid 關(guān)聯(lián)的共享內(nèi)存段為刪除狀態(tài),當(dāng)最后一個(gè)使用該共享內(nèi)存段的進(jìn)程分離它之后,系統(tǒng)會(huì)真正釋放該共享內(nèi)存段所占用的資源,此時(shí) buf 參數(shù)會(huì)被忽略,通常傳遞 NULL |
| IPC_INFO | 獲取系統(tǒng)共享內(nèi)存信息 | 獲取系統(tǒng)范圍內(nèi)的共享內(nèi)存資源信息,這些信息會(huì)被存儲(chǔ)在一個(gè)由系統(tǒng)定義的特定結(jié)構(gòu)體中(通常不是 struct shmid_ds),buf 應(yīng)指向該結(jié)構(gòu)體,用于接收信息 |
| SHM_INFO | 獲取共享內(nèi)存段信息 | 獲取系統(tǒng)中共享內(nèi)存段的相關(guān)統(tǒng)計(jì)信息,返回一個(gè)包含這些統(tǒng)計(jì)信息的結(jié)構(gòu)體,同樣,buf 要指向合適的結(jié)構(gòu)體來(lái)接收數(shù)據(jù) |
| SHM_STAT | 通過(guò)索引獲取共享內(nèi)存狀態(tài) | 類似于 IPC_STAT,但不是通過(guò) shmid 來(lái)指定共享內(nèi)存段,而是通過(guò)一個(gè)索引值,可以通過(guò)這種方式遍歷系統(tǒng)中的共享內(nèi)存段 |
buf:指向一個(gè)保存著共享內(nèi)存的模式狀態(tài)和訪問(wèn)權(quán)限的結(jié)構(gòu)體struct shmid_ds
三、開(kāi)始通信
由于共享內(nèi)存通信沒(méi)辦法做到同步和互斥,我們通過(guò)加入命名管道的方式來(lái)形成同步與互斥的效果,普通的共享內(nèi)存通信就調(diào)用完接口直接寫直接讀就行,比較簡(jiǎn)單,因?yàn)楣蚕韮?nèi)存是可以通過(guò)指針直接讀的,所以我們升級(jí)一下
1、comm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_FILE "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR,
FIFO_READ_ERR
};
using namespace std;
Log log;
const int size = 4096;
const string pathname="/home/slm";
const int proj_id = 0x6667;
//封裝一個(gè)獲取Key值的函數(shù)
key_t GetKey()
{
//使用ftok函數(shù)獲取key值
key_t k = ftok(pathname.c_str(), proj_id);
if(k < 0)
{
exit(1);
}
//返回生成的key值
return k;
}
//封裝一個(gè)建立共享內(nèi)存區(qū)的函數(shù)
int GetShareMemHelper(int flag)
{
//調(diào)用獲取key值,然后在物理內(nèi)存上申請(qǐng)共享內(nèi)存
key_t k = GetKey();
int shmid = shmget(k, size, flag);
if(shmid < 0)
{
exit(2);
}
//返回shmid
return shmid;
}
//用于創(chuàng)建一個(gè)新的共享內(nèi)存段,如果指定的共享內(nèi)存段已經(jīng)存在,函數(shù)會(huì)失敗
int CreateShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
//用于獲取一個(gè)共享內(nèi)存段,如果指定的共享內(nèi)存段不存在,函數(shù)會(huì)創(chuàng)建一個(gè)新的共享內(nèi)存段;如果已經(jīng)存在,則直接返回該共享內(nèi)存段的標(biāo)識(shí)符
int GetShm()
{
return GetShareMemHelper(IPC_CREAT);
}
class Init
{
public:
Init()
{
// 創(chuàng)建管道
int n = mkfifo(FIFO_FILE, MODE);
if (n == -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
// 銷毀管道
int m = unlink(FIFO_FILE);
if (m == -1)
{
perror("unlink");
exit(FIFO_DELETE_ERR);
}
}
};
#endif2、processa.cpp
#include "comm.hpp"
extern Log log;
int main()
{
//創(chuàng)建管道
Init init;
//調(diào)用 CreateShm 函數(shù)創(chuàng)建一個(gè)共享內(nèi)存段,并返回該共享內(nèi)存段的標(biāo)識(shí)符shmid
int shmid = CreateShm();
//調(diào)用 shmat 函數(shù)將共享內(nèi)存段附加到當(dāng)前進(jìn)程的地址空間,shmid 是共享內(nèi)存段的標(biāo)識(shí)符
//nullptr 表示讓系統(tǒng)自動(dòng)選擇附加地址,0 表示默認(rèn)附加標(biāo)志
//返回值是共享內(nèi)存段在進(jìn)程地址空間中的起始地址,將其強(qiáng)制轉(zhuǎn)換為char*類型并賦值給shmaddr
char *shmaddr = (char*)shmat(shmid, nullptr, 0);
//以只讀模式打開(kāi)命名管道文件FIFO_FILE,由于是以只讀模式打開(kāi),該操作會(huì)阻塞
//直到有其他進(jìn)程以寫模式打開(kāi)同一個(gè)命名管道
int fd = open(FIFO_FILE, O_RDONLY);
if (fd < 0)
{
exit(FIFO_OPEN_ERR);
}
//定義shmds用于存儲(chǔ)共享內(nèi)存段的狀態(tài)信息
struct shmid_ds shmds;
while(true)
{
//定義一個(gè)字符變量c,調(diào)用read函數(shù)從命名管道文件描述符fd中讀取1個(gè)字節(jié)的數(shù)據(jù)到c中
//并將實(shí)際讀取的字節(jié)數(shù)存儲(chǔ)在s中,如果能讀取到說(shuō)明通信進(jìn)程給我們發(fā)信號(hào)了,有內(nèi)容
//那我們就讀取共享內(nèi)存部分
char c;
ssize_t s = read(fd, &c, 1);
if(s == 0) break;
else if(s < 0)
{
exit(FIFO_READ_ERR);
}
//可以直接通過(guò)指針訪問(wèn)共享內(nèi)存
cout << "client say@ " << shmaddr << endl;
sleep(1);
//調(diào)用shmctl函數(shù),使用IPC_STAT命令獲取共享內(nèi)存段的狀態(tài)信息,并將其存儲(chǔ)在shmds結(jié)構(gòu)體中
//然后將結(jié)構(gòu)體shmds部分信息打印出來(lái)
shmctl(shmid, IPC_STAT, &shmds);
cout << "shm size: " << shmds.shm_segsz << endl;
cout << "shm nattch: " << shmds.shm_nattch << endl;
printf("shm key: 0x%x\n", shmds.shm_perm.__key);
cout << "shm mode: " << shmds.shm_perm.mode << endl;
}
//取消鏈接
shmdt(shmaddr);
//調(diào)用shmctl函數(shù),使用IPC_RMID命令將共享內(nèi)存段標(biāo)記為待刪除狀態(tài)
//當(dāng)所有附加進(jìn)程都分離后,系統(tǒng)會(huì)自動(dòng)刪除該共享內(nèi)存段
shmctl(shmid, IPC_RMID, nullptr);
close(fd);
return 0;
}3、processb.cpp
#include "comm.hpp"
int main()
{
//調(diào)用GetShm函數(shù)獲取一個(gè)共享內(nèi)存段的標(biāo)識(shí)符shmid
//在參數(shù)相同、無(wú)特殊情況的情況下,shmid也相同
int shmid = GetShm();
char *shmaddr = (char*)shmat(shmid, nullptr, 0);
int fd = open(FIFO_FILE, O_WRONLY);
if (fd < 0)
{
exit(FIFO_OPEN_ERR);
}
// 一旦有了共享內(nèi)存,掛接到自己的地址空間中,直接把他當(dāng)成你的內(nèi)存空間來(lái)用即可
// 不需要調(diào)用系統(tǒng)調(diào)用接口
while(true)
{
cout << "Please Enter@ ";
fgets(shmaddr, 4096, stdin);
//在管道寫入一個(gè)字符來(lái)通知服務(wù)器共享內(nèi)存有新數(shù)據(jù)
write(fd, "c", 1);
}
//調(diào)用shmdt函數(shù)將共享內(nèi)存段從當(dāng)前進(jìn)程的地址空間分離,釋放相關(guān)的資源
shmdt(shmaddr);
close(fd);
return 0;
}
四、其他問(wèn)題
key是在操作系統(tǒng)內(nèi)標(biāo)定共享內(nèi)存唯一性的,而shmid是在進(jìn)程內(nèi)標(biāo)定資源唯一性的,二者雖然都是標(biāo)定唯一性,但是使用范圍不同,并且雖然共享內(nèi)存屬于文件系統(tǒng),但是shmid和文件描述符兼容性不好,共享內(nèi)存這方面單獨(dú)搞了一套類似于文件描述符表的規(guī)則
共享內(nèi)存的生命周期隨內(nèi)核,除非用戶釋放或者內(nèi)核重啟,共享內(nèi)存才會(huì)釋放
ipcs -m命令可以查看操作系統(tǒng)中所有的共享內(nèi)存,其中perms是權(quán)限位,nattch是和當(dāng)前共享內(nèi)存關(guān)聯(lián)的進(jìn)程個(gè)數(shù)

共享內(nèi)存是沒(méi)有存在同步互斥這樣的保護(hù)機(jī)制的,它是所有進(jìn)程通信方式中最快的,因?yàn)橄噍^其他方式,它的拷貝更少
共享內(nèi)存內(nèi)部的數(shù)據(jù)由用戶自己維護(hù)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux通過(guò)設(shè)備號(hào)找到設(shè)備的方法
所有的Linux設(shè)備文件均位于/dev目錄下,是根(/)文件系統(tǒng)的一個(gè)組成部分,因?yàn)檫@些設(shè)備文件在操作系統(tǒng)啟動(dòng)過(guò)程中必須可以使用。接下來(lái)通過(guò)本文給大家介紹Linux通過(guò)設(shè)備號(hào)找到設(shè)備的方法,感興趣的朋友一起看看吧2018-07-07
Ubuntu 18.04 Server 設(shè)置靜態(tài)IP 的方法
Netplan是Ubuntu 17.10中引入的一種新的命令行網(wǎng)絡(luò)配置實(shí)用程序,用于在Ubuntu系統(tǒng)中輕松管理和配置網(wǎng)絡(luò)設(shè)置。這篇文章主要介紹了Ubuntu 18.04 Server 設(shè)置靜態(tài)IP 的方法,需要的朋友可以參考下2018-11-11
CentOS掛載lvm分區(qū)VG重名問(wèn)題及解決
文章描述了在CentOS上掛載LVM分區(qū)時(shí)遇到VG重名問(wèn)題的解決過(guò)程,通過(guò)使用pvs、lvs、vgs命令查看LVM情況,發(fā)現(xiàn)VG名稱重復(fù),無(wú)法直接通過(guò)名稱更改,解決方法包括通過(guò)UUID改名,修改VG屬性,以及掛載目錄,最終成功恢復(fù)文件2024-12-12
Linux中OpenSSL命令的應(yīng)用場(chǎng)景分析
這篇文章主要介紹了Linux中OpenSSL命令的應(yīng)用場(chǎng)景,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
詳解Xampp和wordpress在Centos7上的搭建與使用
通過(guò)實(shí)例給大家詳細(xì)分析了Xampp和wordpress在Centos7上的搭建與使用,對(duì)此有需要的朋友可以學(xué)習(xí)下。2018-02-02
Linux網(wǎng)絡(luò)啟動(dòng)問(wèn)題:Device does not seem to be present解決辦法
這篇文章主要介紹了Linux網(wǎng)絡(luò)啟動(dòng)問(wèn)題:Device does not seem to be present解決辦法的相關(guān)資料,希望通過(guò)本文能幫助到大家解決這樣的問(wèn)題,需要的朋友可以參考下2017-10-10

