共享內(nèi)存是System V版本的最后一個(gè)進(jìn)程間通信方式。共享內(nèi)存,顧名思義就是允許兩個(gè)不相關(guān)的進(jìn)程訪問同一個(gè)邏輯內(nèi)存,共享內(nèi)存是兩個(gè)正在運(yùn)行的進(jìn)程之間共享和傳遞數(shù)據(jù)的一種非常有效的方式。不同進(jìn)程之間共享的內(nèi)存通常為同一段物理內(nèi)存。進(jìn)程可以將同一段物理內(nèi)存連接到他們自己的地址空間中,所有的進(jìn)程都可以訪問共享內(nèi)存中的地址。如果某個(gè)進(jìn)程向共享內(nèi)存寫入數(shù)據(jù),所做的改動(dòng)將立即影響到可以訪問同一段共享內(nèi)存的任何其他進(jìn)程。
1、共享內(nèi)存的通信原理?
在Linux中,每個(gè)進(jìn)程都有屬于自己的進(jìn)程控制塊(PCB)和地址空間(Addr Space),并且都有一個(gè)與之對(duì)應(yīng)的頁表,負(fù)責(zé)將進(jìn)程的虛擬地址與物理地址進(jìn)行映射,通過內(nèi)存管理單元(MMU)進(jìn)行管理。兩個(gè)不同的虛擬地址通過頁表映射到物理空間的同一區(qū)域,它們所指向的這塊區(qū)域即共享內(nèi)存。
共享內(nèi)存原理圖:
?上圖:當(dāng)兩個(gè)進(jìn)程通過頁表將虛擬地址映射到物理地址時(shí),在物理地址中有一塊共同的內(nèi)存區(qū),即共享內(nèi)存,這塊內(nèi)存可以被兩個(gè)進(jìn)程同時(shí)看到。這樣當(dāng)一個(gè)進(jìn)程進(jìn)行寫操作,另一個(gè)進(jìn)程讀操作就可以實(shí)現(xiàn)進(jìn)程間通信。但是,我們要確保一個(gè)進(jìn)程在寫的時(shí)候不能被讀,因此我們使用信號(hào)量來實(shí)現(xiàn)同步與互斥。
對(duì)于一個(gè)共享內(nèi)存,實(shí)現(xiàn)采用的是引用計(jì)數(shù)的原理,當(dāng)進(jìn)程脫離共享存儲(chǔ)區(qū)后,計(jì)數(shù)器減一,掛架成功時(shí),計(jì)數(shù)器加一,只有當(dāng)計(jì)數(shù)器變?yōu)榱銜r(shí),才能被刪除。當(dāng)進(jìn)程終止時(shí),它所附加的共享存儲(chǔ)區(qū)都會(huì)自動(dòng)脫離。
2、為什么共享內(nèi)存速度最快?
借助上圖說明:Proc A 進(jìn)程給內(nèi)存中寫數(shù)據(jù), Proc B 進(jìn)程從內(nèi)存中讀取數(shù)據(jù),在此期間一共發(fā)生了兩次復(fù)制
(1)Proc A 到共享內(nèi)存?????? (2)共享內(nèi)存到 Proc B
因?yàn)橹苯釉趦?nèi)存上操作,所以共享內(nèi)存的速度也就提高了。
最簡單的共享內(nèi)存的使用流程
- ①ftok函數(shù)生成鍵值
- ②shmget函數(shù)創(chuàng)建共享內(nèi)存空間
- ③shmat函數(shù)獲取第一個(gè)可用共享內(nèi)存空間的地址
- ④shmdt函數(shù)進(jìn)行分離(對(duì)共享存儲(chǔ)段操作結(jié)束時(shí)的步驟,并不是從系統(tǒng)中刪除共享內(nèi)存和結(jié)構(gòu))
- ⑤shmctl函數(shù)進(jìn)行刪除共享存儲(chǔ)空間
3、ftok函數(shù)生成鍵值?
每一個(gè)共享存儲(chǔ)段都有一個(gè)對(duì)應(yīng)的鍵值(key)相關(guān)聯(lián)(消息隊(duì)列、信號(hào)量也同樣需要)。
//使用此函數(shù),需導(dǎo)入此頭文件
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- const char *pathname: 一個(gè)以null結(jié)尾的字符串,表示文件系統(tǒng)中文件的路徑。文件應(yīng)該在調(diào)用ftok()時(shí)存在。在多個(gè)進(jìn)程中生成相同的鍵時(shí),需要使用相同的文件路徑。
- int proj_id: 一個(gè)非零的整數(shù),作為生成鍵的種子。在多個(gè)進(jìn)程中生成相同的鍵時(shí),需要使用相同的proj_id。
?
返回值:成功返回鍵值(相當(dāng)于32位的int)。出錯(cuò)返回-1
總之,ftok() 是一個(gè)標(biāo)準(zhǔn)C庫函數(shù),用于為System V IPC對(duì)象生成鍵。它需要提供文件系統(tǒng)中文件的路徑和一個(gè)非零整數(shù)作為參數(shù)。通過使用相同的文件路徑和非零整數(shù),可以在多個(gè)進(jìn)程之間生成相同的鍵,以便共享IPC對(duì)象。?
例如:key_t key = ftok( “/tmp”, 66);
?4、shmget函數(shù)創(chuàng)建共享存儲(chǔ)空間并返回一個(gè)共享存儲(chǔ)標(biāo)識(shí)符
所需頭文件:#include<sys/shm.h>
函數(shù)原型: int shmget(key_t key, size_t size, int shmflg);
- [參數(shù)key]:由ftok生成的key標(biāo)識(shí),標(biāo)識(shí)系統(tǒng)的唯一IPC資源。
- [參數(shù)size]:需要申請(qǐng)共享內(nèi)存的大小。在操作系統(tǒng)中,申請(qǐng)內(nèi)存的最小單位為頁,一頁是4k字節(jié),為了避免內(nèi)存碎片,我們一般申請(qǐng)的內(nèi)存大小為頁的整數(shù)倍。
- [參數(shù)shmflg]:如果要?jiǎng)?chuàng)建新的共享內(nèi)存,需要使用IPC_CREAT,IPC_EXCL,如果是已經(jīng)存在的,可以使用IPC_CREAT或直接傳0。
- [返回值]:成功時(shí)返回一個(gè)新建或已經(jīng)存在的的共享內(nèi)存標(biāo)識(shí)符,取決于shmflg的參數(shù)。失敗返回-1并設(shè)置錯(cuò)誤碼。
例如:int id = shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);創(chuàng)建一個(gè)大小為4096個(gè)字節(jié)的權(quán)限為0666(所有用戶可讀可寫,具體查詢linux權(quán)限相關(guān)內(nèi)容)的共享存儲(chǔ)空間,并返回一個(gè)整形共享存儲(chǔ)標(biāo)識(shí)符,如果key值已經(jīng)存在有共享存儲(chǔ)空間了,則出錯(cuò)返回-1。
???? int id = shmget(key,4096,IPC_CREAT|0666);創(chuàng)建一個(gè)大小為4096個(gè)字節(jié)的權(quán)限為0666(所有用戶可讀可寫,具體查詢linux權(quán)限相關(guān)內(nèi)容)的共享存儲(chǔ)空間,并返回一個(gè)共享存儲(chǔ)標(biāo)識(shí)符,如果key值已經(jīng)存在有共享存儲(chǔ)空間了,則直接返回一個(gè)共享存儲(chǔ)標(biāo)識(shí)符。
?
?5、shmat 函數(shù):掛接共享內(nèi)存
//使用此函數(shù),需導(dǎo)入此頭文件
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- int shmid: 一個(gè)整數(shù),表示共享內(nèi)存段的標(biāo)識(shí)符(ID)。這個(gè)值通常是通過調(diào)用 shmget() 函數(shù)獲得的。
- const void *shmaddr: 一個(gè)指針,表示附加共享內(nèi)存段的首選地址。通常將此參數(shù)設(shè)置為NULL,讓系統(tǒng)自動(dòng)選擇一個(gè)合適的地址。
- int shmflg: 一個(gè)整數(shù),表示附加共享內(nèi)存段的標(biāo)志。常用標(biāo)志包括:
- ??? SHM_RDONLY: 以只讀方式附加共享內(nèi)存段。
- ??? 0: 以讀寫方式附加共享內(nèi)存段。
返回值:
- 成功時(shí),shmat() 返回一個(gè)非空指針,表示共享內(nèi)存段在當(dāng)前進(jìn)程地址空間的起始地址。
- 失敗時(shí),返回 (void *)-1,并設(shè)置相應(yīng)的 errno。
總之,shmat() 是一個(gè)Linux系統(tǒng)調(diào)用函數(shù),用于將共享內(nèi)存段附加到當(dāng)前進(jìn)程的地址空間。它需要提供共享內(nèi)存段的標(biāo)識(shí)符、首選地址和標(biāo)志作為參數(shù)。成功時(shí),它會(huì)返回一個(gè)指向共享內(nèi)存段起始地址的指針,用于后續(xù)的內(nèi)存訪問操作。?
6、shmctl ( ):銷毀共享內(nèi)存?
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- [參數(shù)shmid]:共享存儲(chǔ)段標(biāo)識(shí)符。
- [參數(shù)cmd]:指定的執(zhí)行操作,設(shè)置為IPC_RMID時(shí)表示可以刪除共享內(nèi)存。
- [參數(shù)*buf]:設(shè)置為NULL即可。
- [返回值]:成功返回0,失敗返回-1。
7、shmdt函數(shù)進(jìn)行分離?
- 當(dāng)不需要對(duì)此共享內(nèi)存進(jìn)行操作時(shí)候,調(diào)用shmdt函數(shù)進(jìn)行分離,不是刪除此共享存儲(chǔ)空間喲。
- 所需頭文件:#include<sys/shm.h>
- 函數(shù)原型: int shmdt(const void *addr);
- addr為shmat函數(shù)返回的地址指針
- 返回值:成功返回0;錯(cuò)誤返回-1
例如:int ret = shmdt(addr);
下面是一個(gè)例子,希望對(duì)你對(duì)上面的內(nèi)容理解有所幫助。
comm.hpp:
#pragma once
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"
using namespace std; //不推薦
#define PATH_NAME "/home/whb"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享內(nèi)存的大小,最好是頁(PAGE: 4096)的整數(shù)倍
#define FIFO_NAME "./fifo"
class Init
{
public:
Init()
{
umask(0);
int n = mkfifo(FIFO_NAME, 0666);
assert(n == 0);
(void)n;
Log("create fifo success",Notice) << "\n";
}
~Init()
{
unlink(FIFO_NAME);
Log("remove fifo success",Notice) << "\n";
}
};
#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenFIFO(std::string pathname, int flags)
{
int fd = open(pathname.c_str(), flags);
assert(fd >= 0);
return fd;
}
void Wait(int fd)
{
Log("等待中....", Notice) << "\n";
uint32_t temp = 0;
ssize_t s = read(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
}
void Signal(int fd)
{
uint32_t temp = 1;
ssize_t s = write(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
Log("喚醒中....", Notice) << "\n";
}
void CloseFifo(int fd)
{
close(fd);
}
Log.hpp:
#ifndef _LOG_H_
#define _LOG_H_
#include <iostream>
#include <ctime>
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream &Log(std::string message, int level)
{
std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
return std::cout;
}
#endif
Makefile:
.PHONY:all
all:shmClient shmServer
shmClient:shmClient.cc
g++ -o $@ $^ -std=c++11
shmServer:shmServer.cc
g++ -o $@ $^ -std=c++11
.PHONNY:clean
clean:
rm -f shmClient shmServer
shmServer.cc:文章來源:http://www.zghlxwxcb.cn/news/detail-793799.html
#include "comm.hpp"
// 是不是對(duì)應(yīng)的程序,在加載的時(shí)候,會(huì)自動(dòng)構(gòu)建全局變量,就要調(diào)用該類的構(gòu)造函數(shù) -- 創(chuàng)建管道文件
// 程序退出的時(shí)候,全局變量會(huì)被析構(gòu),自動(dòng)調(diào)用析構(gòu)函數(shù),會(huì)自動(dòng)刪除管道文件
Init init;
string TransToHex(key_t k)
{
char buffer[32];
snprintf(buffer, sizeof buffer, "0x%x", k);
return buffer;
}
int main()
{
// 我們之前為了通信,所做的所有的工作,屬于什么工作呢:讓不同的進(jìn)程看到了同一份資源(內(nèi)存)
// 1. 創(chuàng)建公共的Key值
key_t k = ftok(PATH_NAME, PROJ_ID);
assert(k != -1);
Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;
// 2. 創(chuàng)建共享內(nèi)存 -- 建議要?jiǎng)?chuàng)建一個(gè)全新的共享內(nèi)存 -- 通信的發(fā)起者
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, 0);
Log("attach shm done", Debug) << " shmid : " << shmid << endl;
// sleep(10);
// 這里就是通信的邏輯了
// 將共享內(nèi)存當(dāng)成一個(gè)大字符串
// char buffer[SHM_SIZE];
// 結(jié)論1: 只要是通信雙方使用shm,一方直接向共享內(nèi)存中寫入數(shù)據(jù),另一方,就可以立馬看到對(duì)方寫入的數(shù)據(jù)。
// 共享內(nèi)存是所有進(jìn)程間通信(IPC),速度最快的!不需要過多的拷貝!?。ú恍枰獙?shù)據(jù)給操作系統(tǒng))
// 結(jié)論2: 共享內(nèi)存缺乏訪問控制!會(huì)帶來并發(fā)問題 【如果我想一定程度的訪問控制呢? 能】
int fd = OpenFIFO(FIFO_NAME, READ);
for(;;)
{
Wait(fd);
// 臨界區(qū)
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);
(void)n;
Log("detach shm done", Debug) << " shmid : " << shmid << endl;
// sleep(10);
// 5. 刪除共享內(nèi)存,IPC_RMID即便是有進(jìn)程和當(dāng)下的shm掛接,依舊刪除共享內(nèi)存
n = shmctl(shmid, IPC_RMID, nullptr);
assert(n != -1);
(void)n;
Log("delete shm done", Debug) << " shmid : " << shmid << endl;
CloseFifo(fd);
return 0;
}
shmClient.cc:文章來源地址http://www.zghlxwxcb.cn/news/detail-793799.html
#include "comm.hpp"
int main()
{
Log("child pid is : ", Debug) << getpid() << endl;
key_t k = ftok(PATH_NAME, PROJ_ID);
if (k < 0)
{
Log("create key failed", Error) << " client key : " << k << endl;
exit(1);
}
Log("create key done", Debug) << " client key : " << k << endl;
// 獲取共享內(nèi)存
int shmid = shmget(k, SHM_SIZE, 0);
if(shmid < 0)
{
Log("create shm failed", Error) << " client key : " << k << endl;
exit(2);
}
Log("create shm success", Error) << " 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", Error) << " client key : " << k << endl;
// sleep(10);
int fd = OpenFIFO(FIFO_NAME, WRITE);
// 使用
// client將共享內(nèi)存看做一個(gè)char 類型的buffer
while(true)
{
ssize_t s = read(0, shmaddr, SHM_SIZE-1);
if(s > 0)
{
shmaddr[s-1] = 0;
Signal(fd);
if(strcmp(shmaddr,"quit") == 0) break;
}
}
CloseFifo(fd);
// 去關(guān)聯(lián)
int n = shmdt(shmaddr);
assert(n != -1);
Log("detach shm success", Error) << " client key : " << k << endl;
// sleep(10);
// client 要不要chmctl刪除呢?不需要!!
return 0;
}
到了這里,關(guān)于(26)Linux 進(jìn)程通信之共享內(nèi)存(共享儲(chǔ)存空間)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!