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

操作系統(tǒng)在物理內(nèi)存上申請一塊空間,然后將申請到的空間通過頁表映射到進(jìn)程地址空間mm_struct的共享區(qū)中,然后返回虛擬地址供程序使用,如果多個(gè)進(jìn)程申請的是同一塊物理空間,那么它們就可以進(jìn)行通信由于同一時(shí)間可能有多組進(jìn)程進(jìn)行通信,所以系統(tǒng)當(dāng)中可能存在多個(gè)共享內(nèi)存塊,所以操作系統(tǒng)要把這些內(nèi)存管理起來,所以內(nèi)核中會有一個(gè)結(jié)構(gòu)體來描述共享內(nèi)存
二、系統(tǒng)調(diào)用接口
1、申請共享內(nèi)存
(一)key的獲取
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
- 返回值:成功ftok 會將
proj_id與pathname對應(yīng)的文件信息結(jié)合起來生成最終的key值,失敗返回-1 pathname:已經(jīng)存在的文件或目錄的路徑名proj_id:由用戶指定的非零整數(shù),通常是一個(gè)單字符(因?yàn)橹挥械?8 位會被使用),
只要這兩個(gè)才參數(shù)一致,那么生成的key值就一定一致
key值是我們少見的由我們自己傳參生成的固定的值,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)來分配key值
(二)共享內(nèi)存的申請
shmget函數(shù)的主要作用是在內(nèi)核中創(chuàng)建或獲取共享內(nèi)存段的標(biāo)識符
#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)識碼(shmid),失敗返回-1
key:key是一個(gè)數(shù)字,它是不同共享內(nèi)存讓自己具備唯一性的標(biāo)識size:共享內(nèi)存大小,單位為字節(jié),一般最好是4096的整數(shù)倍,它按照頁申請,即需要4097字節(jié)就申請4096*2個(gè)字節(jié)
shmflg是一個(gè)標(biāo)識符
| shmflg | 含義 | 作用 |
|---|---|---|
| IPC_CREAT | 創(chuàng)建標(biāo)志 | 如果指定的共享內(nèi)存段不存在,就創(chuàng)建一個(gè)新的共享內(nèi)存段;若已存在,則直接返回其標(biāo)識符,常用于在不確定共享內(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)程,直接返回錯誤,例如在獲取共享內(nèi)存時(shí),若當(dāng)前資源無法立即獲取,不等待而直接返回錯誤 |
| SHM_HUGETLB | 大頁標(biāo)志 | 嘗試使用大頁內(nèi)存分配共享內(nèi)存段,大頁內(nèi)存可減少頁表項(xiàng)數(shù)量,降低內(nèi)存管理開銷,提高系統(tǒng)性能,適合處理大量數(shù)據(jù) |
| SHM_NORESERVE | 不保留交換空間標(biāo)志 | 不預(yù)先為共享內(nèi)存段保留交換空間,正常創(chuàng)建共享內(nèi)存時(shí),系統(tǒng)會預(yù)留交換空間以防內(nèi)存不足,使用此標(biāo)志可節(jié)省交換空間,但可能導(dǎo)致內(nèi)存緊張時(shí)出現(xiàn)頁面交換問題 |
| 權(quán)限標(biāo)志(如 0600、0666 等) | 權(quán)限設(shè)置 | 類似文件權(quán)限設(shè)置,用于規(guī)定共享內(nèi)存段的訪問權(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)識符,但進(jìn)程還不能直接訪問該共享內(nèi)存,只有通過shmat函數(shù)將共享內(nèi)存段附加到進(jìn)程的地址空間后,進(jìn)程才能像訪問普通內(nèi)存一樣訪問共享內(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)識符,用于指定要附加的共享內(nèi)存段,即shmget的返回值shmaddr:指定共享內(nèi)存段要附加到的進(jìn)程地址空間的地址,如果為 NULL,則由系統(tǒng)自動選擇合適的地址shmflg:標(biāo)識符
| 類型 | 含義 | 作用 |
|---|---|---|
| SHM_RDONLY | 共享內(nèi)存只讀 | 以只讀模式將共享內(nèi)存段附加到進(jìn)程的地址空間,進(jìn)程只能讀取共享內(nèi)存中的數(shù)據(jù),不能進(jìn)行寫操作,增強(qiáng)數(shù)據(jù)安全性,適用于多進(jìn)程共享只讀數(shù)據(jù)的場景 |
| SHM_RND | 地址舍入 | 當(dāng) shmaddr 參數(shù)不為 NULL 時(shí),將 shmaddr 向下舍入到一個(gè)合適的內(nèi)存邊界(通常是系統(tǒng)頁面大小的整數(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í)行程序指令,不過并非所有系統(tǒng)都支持該標(biāo)志,適用于需要在共享內(nèi)存中執(zhí)行代碼的特殊場景 |
| SHM_COPY | 創(chuàng)建私有副本 | 某些系統(tǒng)支持該標(biāo)志,嘗試創(chuàng)建共享內(nèi)存段的一個(gè)私有副本,后續(xù)對該副本的修改不會影響其他進(jìn)程看到的共享內(nèi)存內(nèi)容,用于實(shí)現(xiàn)進(jìn)程對共享內(nèi)存的獨(dú)立修改 |
| SHM_ANON | 匿名共享內(nèi)存 | 部分系統(tǒng)支持該標(biāo)志,用于創(chuàng)建匿名共享內(nèi)存段,此時(shí) shmid 參數(shù)會被忽略,可結(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)會在進(jìn)程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,使得進(jìn)程可以像訪問普通內(nèi)存一樣訪問共享內(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)存
通過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:將要采取的動作
| 類型 | 含義 | 作用 |
|---|---|---|
| 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í)間、最后訪問時(shí)間等信息 |
| IPC_SET | 設(shè)置狀態(tài)信息 | 使用 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中的值來更新與 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)會真正釋放該共享內(nèi)存段所占用的資源,此時(shí) buf 參數(shù)會被忽略,通常傳遞 NULL |
| IPC_INFO | 獲取系統(tǒng)共享內(nèi)存信息 | 獲取系統(tǒng)范圍內(nèi)的共享內(nèi)存資源信息,這些信息會被存儲在一個(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)體來接收數(shù)據(jù) |
| SHM_STAT | 通過索引獲取共享內(nèi)存狀態(tài) | 類似于 IPC_STAT,但不是通過 shmid 來指定共享內(nèi)存段,而是通過一個(gè)索引值,可以通過這種方式遍歷系統(tǒng)中的共享內(nèi)存段 |
buf:指向一個(gè)保存著共享內(nèi)存的模式狀態(tài)和訪問權(quán)限的結(jié)構(gòu)體struct shmid_ds
三、開始通信
由于共享內(nèi)存通信沒辦法做到同步和互斥,我們通過加入命名管道的方式來形成同步與互斥的效果,普通的共享內(nèi)存通信就調(diào)用完接口直接寫直接讀就行,比較簡單,因?yàn)楣蚕韮?nèi)存是可以通過指針直接讀的,所以我們升級一下
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)存上申請共享內(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ù)會失敗
int CreateShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
//用于獲取一個(gè)共享內(nèi)存段,如果指定的共享內(nèi)存段不存在,函數(shù)會創(chuàng)建一個(gè)新的共享內(nèi)存段;如果已經(jīng)存在,則直接返回該共享內(nèi)存段的標(biāo)識符
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)識符shmid
int shmid = CreateShm();
//調(diào)用 shmat 函數(shù)將共享內(nèi)存段附加到當(dāng)前進(jìn)程的地址空間,shmid 是共享內(nèi)存段的標(biāo)識符
//nullptr 表示讓系統(tǒng)自動選擇附加地址,0 表示默認(rèn)附加標(biāo)志
//返回值是共享內(nèi)存段在進(jìn)程地址空間中的起始地址,將其強(qiáng)制轉(zhuǎn)換為char*類型并賦值給shmaddr
char *shmaddr = (char*)shmat(shmid, nullptr, 0);
//以只讀模式打開命名管道文件FIFO_FILE,由于是以只讀模式打開,該操作會阻塞
//直到有其他進(jìn)程以寫模式打開同一個(gè)命名管道
int fd = open(FIFO_FILE, O_RDONLY);
if (fd < 0)
{
exit(FIFO_OPEN_ERR);
}
//定義shmds用于存儲共享內(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ù)存儲在s中,如果能讀取到說明通信進(jìn)程給我們發(fā)信號了,有內(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);
}
//可以直接通過指針訪問共享內(nèi)存
cout << "client say@ " << shmaddr << endl;
sleep(1);
//調(diào)用shmctl函數(shù),使用IPC_STAT命令獲取共享內(nèi)存段的狀態(tài)信息,并將其存儲在shmds結(jié)構(gòu)體中
//然后將結(jié)構(gòu)體shmds部分信息打印出來
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)會自動刪除該共享內(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)識符shmid
//在參數(shù)相同、無特殊情況的情況下,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)存空間來用即可
// 不需要調(diào)用系統(tǒng)調(diào)用接口
while(true)
{
cout << "Please Enter@ ";
fgets(shmaddr, 4096, stdin);
//在管道寫入一個(gè)字符來通知服務(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;
}
四、其他問題
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)存才會釋放
ipcs -m命令可以查看操作系統(tǒng)中所有的共享內(nèi)存,其中perms是權(quán)限位,nattch是和當(dāng)前共享內(nèi)存關(guān)聯(lián)的進(jìn)程個(gè)數(shù)

共享內(nèi)存是沒有存在同步互斥這樣的保護(hù)機(jī)制的,它是所有進(jìn)程通信方式中最快的,因?yàn)橄噍^其他方式,它的拷貝更少
共享內(nèi)存內(nèi)部的數(shù)據(jù)由用戶自己維護(hù)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
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
詳解Xampp和wordpress在Centos7上的搭建與使用
通過實(shí)例給大家詳細(xì)分析了Xampp和wordpress在Centos7上的搭建與使用,對此有需要的朋友可以學(xué)習(xí)下。2018-02-02
Linux網(wǎng)絡(luò)啟動問題:Device does not seem to be present解決辦法
這篇文章主要介紹了Linux網(wǎng)絡(luò)啟動問題:Device does not seem to be present解決辦法的相關(guān)資料,希望通過本文能幫助到大家解決這樣的問題,需要的朋友可以參考下2017-10-10

