进程间通信的本质是:先让不同的进程,看到同一份资源
共享内存区是最快的IPC形式
🚀 共享内存是一种进程间通信(IPC)机制,它允许多个进程直接访问同一块内存区域,从而实现高效的数据交换。
特点如下:
共享内存的工作原理基于操作系统的内存管理系统。具体步骤如下:
🔥 所以根据动态库加载的原理,操作系统可以在内存中创建一个共享内存空间,再通过页表映射到两个进程的共享区中,这样两个进程就可以看到同一份资源了
注意:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:创建一个新的共享内存段,或者获取一个已存在的共享内存段
参数:
返回值:成功返回一个非负整数,即共享内存段的标识码(shmid);失败返回 -1
在理解这个函数之前,我们先来想一想为什么共享内存的 key 值要用户传递?就不能让内核自动生成嘛?
由于不能让内核生成,因此那就只能自己创建,并且需要让这两个进程都能看到
ftok 函数 原型
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id)
功能:用于根据文件的属性(如inode编号)生成一个唯一的键值
参数:
返回值:成功返回 key 唯一键值;失败返回 -1,并设置 errno 来指示错误
共享内存被删除后,则其他线程直接无法通信 ❓
⭕shmid vs key
key 保证了操作系统内的唯一性,shmid 只在你的进程内,用来表示资源的唯一性 只有在 shmget() 函数 时候用 key,大部分情况用户访问共享内存,都用的是 shmid
void *shmat(int shmid, const void *shmaddr, int shmflg)
功能:将共享内存段连接到进程地址空间 参数
返回值:成功返回指向共享内存的起始地址;失败返回-1
shmaddr 说明:
void *shmat(int shmid, const void *shmaddr, int shmflg)
功能:将共享内存段与当前进程脱离
参数:
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离 不等于 删除共享内存段
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制共享内存段
参数:
返回值:成功返回 0;失败返回 -1
🐸 共享内存和文件不同,不会随着进程的结束而自动释放,需要我们手动释放(指令或者其他系统调用),否则会一直存在内存中,直到系统重启。
① 系统命令 ipcs -m 查看已存在的共享内存
② 系统命令 ipcrm -m 共享内存标识符(shmid) 删除指定的共享内存
ShareMemory.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdalign.h>
#include <unistd.h>
const std::string gpath = "/home/lighthouse/112";
int gprojId = 0x6666;
// 操作系统,申请空间,是按照块为单位:4kb 1kb
int gshmsize = 4096; // 共享内存大小,建议使用 4096 的整数倍
mode_t gmode = 0600;
class ShareMemory
{
private:
int CreateShmHelper(int shmflg)
{
key_t k = ::ftok(gpath.c_str(), gprojId);
if(k < 0)
{
std::cerr << "ftok error" << std::endl;
return -1;
}
int shmid = ::shmget(k, gshmsize, shmflg);
if(shmid < 0)
{
std::cerr << "shmget error" << std::endl;
return -2;
}
std::cout << "shmid: " << shmid << std::endl;
return shmid;
}
public:
ShareMemory(){}
~ShareMemory(){}
int CreateShm()
{
return CreateShmHelper(IPC_CREAT | IPC_EXCL | gmode);
}
int GetShm()
{
return CreateShmHelper(IPC_CREAT);
}
void* AttachShm(int shmid)
{
void *ret = shmat(shmid, nullptr, 0);
if((long long) ret == -1)
{
return nullptr;
}
return ret;
}
void DetachShm(void *ret)
{
::shmdt(ret);
std::cout << "detach done " << std::endl;
}
void DeleteShm(int shmid)
{
shmctl(shmid, IPC_RMID, nullptr);
}
void ShmMeta()
{
}
};
ShareMemory shm;
Server.cc -- 读取
#include <iostream>
#include <unistd.h>
#include <string.h>
#include "ShareMemory.hpp"
int main()
{
int shmid = shm.CreateShm();
void *addr = shm.AttachShm(shmid);
sleep(5);
std::cout << "server attach done" << std::endl;
shm.DetachShm(addr);
std::cout << "server detach done" << std::endl;
sleep(5);
shm.DeleteShm(shmid);
std::cout << "server delete done" << std::endl;
return 0;
}
Client.cc -- 写入
#include <iostream>
#include "ShareMemory.hpp"
int main()
{
int shmid = shm.GetShm();
void *addr = shm.AttachShm(shmid);
sleep(5);
std::cout << "client attach done" << std::endl;
// addr -> 写入
shm.DetachShm(addr);
std::cout << "client detach done" << std::endl;
sleep(5);
return 0;
}
演示结果如下:
共享内存演示
演示一种错误情况,代码如下:
共享演示错误情况
ShareMemory.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdalign.h>
#include <unistd.h>
const std::string gpath = "/home/lighthouse/112";
int gprojId = 0x6666;
// 操作系统,申请空间,是按照块为单位:4kb 1kb
int gshmsize = 4096; // 共享内存大小,建议使用 4096 的整数倍
mode_t gmode = 0600;
class ShareMemory
{
private:
void CreateShmHelper(int shmflg)
{
_key = ::ftok(gpath.c_str(), gprojId);
if(_key < 0)
{
std::cerr << "ftok error" << std::endl;
return ;
}
_shmid = ::shmget(_key, gshmsize, shmflg);
if(_shmid < 0)
{
std::cerr << "shmget error" << std::endl;
return ;
}
std::cout << "shmid: " << _shmid << std::endl;
}
public:
ShareMemory():_shmid(-1), _key(0), _addr(nullptr)
{
}
~ShareMemory()
{
}
void CreateShm()
{
if(_shmid == -1)
CreateShmHelper(IPC_CREAT | IPC_EXCL | gmode);
}
void GetShm()
{
CreateShmHelper(IPC_CREAT);
}
void AttachShm()
{
_addr = shmat(_shmid, nullptr, 0);
if((long long) _addr == -1)
{
std::cout << "attach error" << std::endl;
}
}
void DetachShm()
{
if(_addr != nullptr)
::shmdt(_addr);
std::cout << "detach done " << std::endl;
}
void DeleteShm()
{
shmctl(_shmid, IPC_RMID, nullptr);
}
void *GetAddr()
{
return _addr;
}
void ShmMeta()
{
}
private: //写时拷贝,系统上会自己去区分的
int _shmid;
key_t _key;
void *_addr;
};
ShareMemory shm;
Server.cc -- 读取
int main()
{
shm.CreateShm();
shm.AttachShm();
// 进行IPC
char *strinfo =(char*)shm.GetAddr();
//检测: Server 和 client 映射虚拟地址不同
// sleep(5);
// printf("server 虚拟地址:%p\n", strinfo);
// sleep(5);
while(true)
{
printf("%s\n", strinfo);
sleep(1);
}
shm.DetachShm();
shm.DeleteShm();
return 0;
}
Client.cc -- 写入
int main()
{
shm.GetShm();
shm.AttachShm();
// 进行 IPC 通信
char *strinfo = (char*)shm.GetAddr();
// sleep(5);
// printf("client 虚拟地址:%p\n", strinfo);
// sleep(5);
char ch = 'A';
while(ch <= 'Z')
{
sleep(3);
strinfo[ch - 'A'] = ch; // 这里操作共享内存没用系统调用
ch++;
}
shm.DetachShm();
return 0;
}
演示结果如下:
共享内存通信演示
注意: server 和 client 映射虚拟地址不同
给情况二中的ShareMemory.hpp文件 增加如下代码
struct data
{
char status[32];
char lasttime[48];
char image[4000];
};
Time.hpp
#pragma once
#include <iostream>
#include <string>
#include <ctime>
// 拿到当前的时间
std::string GetCurrTime()
{
time_t t = time(nullptr);
struct tm *curr = ::localtime(&t);
char currtime[32];
snprintf(currtime, sizeof(currtime), "%d-%d-%d %d:%d:%d",
curr->tm_year + 1900,
curr->tm_mon + 1,
curr->tm_mday,
curr->tm_hour,
curr->tm_min,
curr->tm_sec);
return currtime;
}
Server.cc -- 读取
int main()
{
shm.CreateShm();
shm.AttachShm();
// 进行IPC
struct data *image =(struct data*)shm.GetAddr();
while(true)
{
printf("status: %s\n", image->status);
printf("lasttime: %s\n", image->lasttime);
printf("image: %s\n", image->image);
strcpy(image->status, "过期");
sleep(1);
}
shm.DetachShm();
shm.DeleteShm();
return 0;
}
Client.cc -- 写入
int main()
{
shm.GetShm();
shm.AttachShm();
struct data *image = (struct data*)shm.GetAddr();
int cnt = 2;
while(cnt--)
{
strcpy(image->status, "最新");
strcpy(image->lasttime, GetCurrTime().c_str());
strcpy(image->image, "IsLand1314");
sleep(3);
}
shm.DetachShm();
return 0;
}
演示结果如下:
共享内存结构化信息演示
Fifo.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 模拟进程间同步的过程!
const std::string gpipeFile = "./fifo";
const mode_t gfifomode = 0600;
const int gdefultfd = -1;
const int gsize = 1024;
const int gForRead = O_RDONLY;
const int gForWrite = O_WRONLY;
class Fifo
{
private:
void OpenFifo(int flag)
{
// 如果读端打开文件时,写端还没打开,读端对用的open就会阻塞
_fd = ::open(gpipeFile.c_str(), flag);
if (_fd < 0)
{
std::cerr << "open error" << std::endl;
}
}
public:
Fifo() : _fd(-1)
{
umask(0);
int n = ::mkfifo(gpipeFile.c_str(), gfifomode);
if (n < 0)
{
// std::cerr << "mkfifo error" << std::endl;
return;
}
std::cout << "mkfifo success" << std::endl;
}
bool OpenPipeForWrite()
{
OpenFifo(gForWrite);
if (_fd < 0)
return false;
return true;
}
bool OpenPipeForRead()
{
OpenFifo(gForRead);
if (_fd < 0)
return false;
return true;
}
int Wait()
{
int code = 0;
ssize_t n = ::read(_fd, &code, sizeof(code));
if (n == sizeof(code))
{
return 0;
}
else if (n == 0)
{
return 1;
}
else
{
return 2;
}
}
void Signal()
{
int code = 1;
::write(_fd, &code, sizeof(code));
}
~Fifo()
{
if (_fd >= 0)
::close(_fd);
int n = ::unlink(gpipeFile.c_str());
if (n < 0)
{
std::cerr << "unlink error" << std::endl;
return;
}
std::cout << "unlink success" << std::endl;
}
private:
int _fd;
};
Fifo gpipe;
Server.cc -- 读取
int main()
{
std::cout << "time: " << GetCurrTime() << std::endl;
shm.CreateShm();
shm.AttachShm();
gpipe.OpenPipeForRead();
// 进行IPC
struct data *image =(struct data*)shm.GetAddr();
while(true)
{
gpipe.Wait();
printf("status: %s\n", image->status);
printf("lasttime: %s\n", image->lasttime);
printf("image: %s\n", image->image);
strcpy(image->status, "过期");
//sleep(1);
}
shm.DetachShm();
shm.DeleteShm();
return 0;
}
Client.cc -- 写入
int main()
{
shm.GetShm();
shm.AttachShm();
gpipe.OpenPipeForWrite();
struct data *image = (struct data*)shm.GetAddr();
//int cnt = 2;
while(true)
{
strcpy(image->status, "最新");
strcpy(image->lasttime, GetCurrTime().c_str());
strcpy(image->image, "IsLand1314");
gpipe.Signal();
sleep(3);
}
shm.DetachShm();
return 0;
}
演示结果如下:
共享内存和管道演示
client.c 文件 (写入)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#define SHM_KEY 1234 // 共享内存的键值
#define SHM_SIZE 1024 // 共享内存的大小
int main()
{
// 1. 建立
int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget failed");
exit(1);
}
// 2. 挂接
char *shm_ptr = (char *)shmat(shmid, NULL, 0);
if (shm_ptr == (char *)-1) {
perror("shmat failed");
exit(1);
}
// 3. 写数据
const char *message = "i am process A";
strcpy(shm_ptr, message);
printf("Process A: Data write to shared memory %s\n", message);
sleep(5); // 等待进程B读取数据
// 4. 去挂接
if (shmdt(shm_ptr) == -1) {
perror("shmdt failed");
exit(1);
}
return 0;
}
server.c 文件 (写出)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#define SHM_KEY 1234 // 共享内存的键值
#define SHM_SIZE 1024 // 共享内存的大小
int main()
{
// 1. 建立
int shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget failed");
exit(1);
}
// 2. 将共享内存附加到当前进程的地址空间
char *shm_ptr = (char *)shmat(shmid, NULL, 0);
if (shm_ptr == (char *)-1) {
perror("shmat failed");
exit(1);
}
// 3. 从共享内存中读取数据并打印
printf("Process B: Data read from shared memory: %s\n", shm_ptr);
// 4. 断开共享内存
if (shmdt(shm_ptr) == -1) {
perror("shmdt failed");
exit(1);
}
// 5. 删除共享内存
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl failed");
exit(1);
}
return 0;
}
运行结果如下:
优点
缺点
共享内存是一种高效的进程间通信方式,允许多个进程访问同一块物理内存区域。它比管道和消息队列更快,因为数据不需要复制。使用共享内存时,进程间需同步访问,防止数据竞争和不一致性问题
【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!