目录
命令 ipcs -m :
命令 ipcrm -m shmid:
共享内存的通信:
为什么共享内存更高效?
代码:
ShmClient.cc:
ShmServer.cc:
结果:
如何让共享内存实现同步?
代码:
Comm.hpp
Fifo.hpp
ShmClient.cc
ShmServer.cc
结果:
接上篇的共享内存的函数封装。
当两个不同的进程 server 和 client 的 GetShmKeyOrDie 函数传了一样的 pathname 和 pro_jid 时,ftok 就会给两个不同的进程返回一样的 key 值!即两个不同的进程看到同一个共享内存!
命令 ipcs -m :
命令 ipcs -m 可以查看共享内存的相关信息,如下图:
perms 是共享内存的权限。
- 当我们调用 GetShm 函数时,由于没有设置权限,故创建出来的共享内存的权限为 0(如上图);
- 当我们调用 CreateShm 函数时,由于设置的权限为 0666,故创建出来的共享内存的权限为 666(如下图)。
bytes 是共享内存的大小,单位为字节,共享内存的大小是用户定义的。
nattch 是共享内存挂接的进程的个数。
我们可以写个代码验证一下:我们创建出共享内存后休眠 2s,然后把共享内存挂接到进程中,再次休眠 5s,休眠结束后把共享内存从进程的地址空间中分离出来,注意只是分离出来,并没有删除共享内存。
#include"Comm.hpp"
#include<unistd.h>
int main()
{
key_t k=GetShmKeyOrDie(path,pro_jid);
cout<<" key:"<<ToHex(k)<<endl;//转为十六进制输出
int shmid=CreateShm(k,defaultsize);
cout<<" shmid:"<<shmid<<endl;
sleep(2);
char* addr=(char*)ShmAttach(shmid);
cout<<" Attach success, addr:"<<ToHex((uint64_t)addr)<<endl;
sleep(5);
ShmDetach(addr);
cout<<" Detach success "<<ToHex((uint64_t)addr)<<endl;
return 0;
}
用 while :;do ipcs -m;sleep 1;done 命令时时监控共享内存的信息:
命令 ipcrm -m shmid:
除了调用函数删除,还可以用命令 ipcrm -m shmid 来删除共享内存,如下图,删除完再次查看共享内存的信息时,已经查不到了:
共享内存的通信:
创建出共享内存之后,我们就可以通信了。
共享内存可以直接写入,不需要像管道一样调用系统调用,因为它本质上是一块内存区域,这块内存区域由操作系统映射到多个进程的地址空间中。当一个进程修改了这块共享内存中的数据时,其他进程可以立即看到这些修改,因为它们共享的是同一块物理内存。这提高了通信的效率。
为什么共享内存更高效?
共享内存之所以被认为是一种高效的进程间通信(IPC)方式,主要是因为它避免了数据复制和频繁的系统调用。以下是具体原因:
- 减少数据拷贝:在使用共享内存时,数据只需要在一个地方修改即可被所有有权访问该内存段的进程看到。相比之下,其他IPC机制如管道(pipe)等通常需要将数据从发送者的用户空间复制到内核空间,然后再从内核空间复制到接收者的用户空间。这种多次的数据拷贝过程会消耗额外的时间和CPU资源。
- 直接内存访问:共享内存使得进程可以直接对内存进行读写操作,就像操作自己的私有内存一样。这种直接访问的方式减少了中间环节,提高了数据传输的速度。
代码:
ShmClient.cc:
#include"Comm.hpp"
#include<unistd.h>
int main()
{
key_t k=GetShmKeyOrDie(path,pro_jid);
cout<<" key:"<<ToHex(k)<<endl;//转为十六进制输出
int shmid=GetShm(k,defaultsize);
cout<<" shmid:"<<shmid<<endl;
char* addr=(char*)ShmAttach(shmid);
cout<<" Attach success, addr:"<<ToHex((uint64_t)addr)<<endl;
memset(addr,0,defaultsize);//初始化共享内存
for(char ch='A';ch<='Z';ch++)//写入
{
addr[ch-'A']=ch;
sleep(1);
}
ShmDetach(addr);
cout<<" Detach success "<<ToHex((uint64_t)addr)<<endl;
return 0;
}
ShmServer.cc:
#include"Comm.hpp"
#include<unistd.h>
int main()
{
key_t k=GetShmKeyOrDie(path,pro_jid);
cout<<" key:"<<ToHex(k)<<endl;//转为十六进制输出
int shmid=CreateShm(k,defaultsize);
cout<<" shmid:"<<shmid<<endl;
char* addr=(char*)ShmAttach(shmid);
cout<<" Attach success, addr:"<<ToHex((uint64_t)addr)<<endl;
//读取数据
for(;;)
{
cout<<" shm content:"<<addr<<endl;
sleep(1);
}
ShmDetach(addr);
cout<<" Detach success "<<ToHex((uint64_t)addr)<<endl;
DeleteShm(shmid);
return 0;
}
结果:
可以看出,写端 client 还没有向共享内存写入数据,但读端 server 已经在读取了,即读端没有阻塞等待写端写入数据!也就是说共享内存没有提供协同机制!这将导致数据不一致!
如何让共享内存实现同步?
我们利用管道来实现同步。
我们并不是将要写入共享内存的数据写入管道中,而是设置一个管道,server 使用管道的读端, client 使用管道的写端。
共享内存的读端 server 因为管道中还没有写入数据,就阻塞等待,等待管道中的数据,等到了管道的数据才可以读取共享内存的数据,而共享内存的写端 client 写完数据后,向管道写入一个数据,server 的管道读端读到了这个数据,结束阻塞,server就可以读取共享内存的数据了。
代码:
Comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <cstdlib>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/shm.h>
#include <sys/types.h>
using namespace std;
const char *path = "./shm_test";
const int pro_jid = 0x66;
const int defaultsize = 4096;
key_t GetShmKeyOrDie(const char *pathname, const int pro_jid)
{
key_t k = ftok(pathname, pro_jid); // 得到key值
if (k < 0)// 获取失败
{
cerr << " ftok failed,errno:" << errno << ", errstring:" << strerror(errno) << endl;
exit(1); // 直接终止程序
}
return k; // 获取成功
}
int CreateShmOrDie(key_t key, int size, int flag)
{
// 得到共享内存的shmid
int shmid = shmget(key, size, flag);
if (shmid < 0) // 创建失败
{
cerr << " shmget failed, errno:" << errno << ", errstring:" << strerror(errno) << endl;
exit(2);
}
return shmid; // 创建成功
}
int CreateShm(key_t key, int size)
{
// 创建一个新的共享内存,并设置权限
return CreateShmOrDie(key, size, IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm(key_t key, int size)
{
return CreateShmOrDie(key, size, IPC_CREAT);
}
void DeleteShm(int shmid)
{
int n = shmctl(shmid, IPC_RMID, nullptr);
if (n < 0)
cerr << " Delete failed,errno:" << errno << ", errstring:" << strerror(errno) << endl;
else
cout << " Delete success, shmid:" << shmid << endl;
}
string ToHex(key_t k)//将 key 值转为十六进制
{
char buffer[1024];//用C语言方便用 %x 直接转为十六进制
snprintf(buffer,sizeof(buffer),"0x%x",k);
return buffer;
}
void DebugShm(int shmid)
{
struct shmid_ds shmds;
int n = shmctl(shmid, IPC_STAT, &shmds);
if (n < 0)
cerr << " shmctl failed " << endl;
else
{
std::cout << "shmds.shm_segsz: " << shmds.shm_segsz << std::endl;//共享内存的大小
std::cout << "shmds.shm_nattch:" << shmds.shm_nattch << std::endl;//有多少个进程挂接
std::cout << "shmds.shm_ctime:" << shmds.shm_ctime << std::endl;//上一次挂接或取消挂接的时间
std::cout << "shmds.shm_perm.__key:" << ToHex(shmds.shm_perm.__key) << std::endl;//对应的key
}
}
void *ShmAttach(int shmid)
{
void* addr=shmat(shmid,nullptr,0);
if((long long int)addr==-1)
{
//挂接失败
cerr<<" attach failed,errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
return nullptr;
}
else
{
//挂接成功
return addr;
}
}
void ShmDetach(void *addr)
{
int n=shmdt(addr);
if(n<0)
{
cerr<<" Detach failed,errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
}
}
Fifo.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
using namespace std;
#define Mode 0666
#define Path "./fifo"
class Fifo
{
public:
Fifo(const string &path = Path) : _path(path)
{
umask(0);
int n = mkfifo(_path.c_str(), Mode);
if (n == 0)
{
cout << "mkfifo success" << endl;
}
else
{
cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
}
}
~Fifo()
{
int n = unlink(_path.c_str());
if (n == 0)
{
cout << "remove fifo file " << _path << " success" << endl;
}
else
{
cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
}
}
private:
string _path; // 文件路径+文件名
};
class Sync
{
public:
Sync():rfd(-1),wfd(-1)
{ }
void OpenReadOrDie()
{
rfd=open(Path,O_RDONLY);
if(rfd<0)
exit(1);
}
void OpenWriteOrDie()
{
wfd=open(Path,O_WRONLY);
if(wfd<0)
exit(1);
}
bool Wait()
{
//读端
bool ret=true;
uint32_t c=0;
ssize_t n=read(rfd,&c,sizeof(uint32_t));
if(n==sizeof(uint32_t))
{
cout<<" server wakeup, begin read shm..." << endl;
}
else if(n==0)
{
return false;
}
else
{
return false;
}
return true;
}
void Wakeup()
{
//写端
uint32_t c=0;
ssize_t n=write(wfd,&c,sizeof(c));
assert(n==sizeof(uint32_t));
cout<<" Wakeup server "<<endl;
}
~Sync()
{ }
private:
int rfd;
int wfd;
};
#endif
ShmClient.cc
#include"Comm.hpp"
#include"Fifo.hpp"
#include<unistd.h>
int main()
{
key_t k=GetShmKeyOrDie(path,pro_jid);
cout<<" key:"<<ToHex(k)<<endl;//转为十六进制输出
int shmid=GetShm(k,defaultsize);
cout<<" shmid:"<<shmid<<endl;
char* addr=(char*)ShmAttach(shmid);
cout<<" Attach success, addr:"<<ToHex((uint64_t)addr)<<endl;
Sync sync;
sync.OpenWriteOrDie();//打开写端
memset(addr,0,defaultsize);//初始化共享内存
sleep(5);
for(char ch='A';ch<='Z';ch++)//写入
{
addr[ch-'A']=ch;
sleep(1);
sync.Wakeup();//写完了
}
ShmDetach(addr);
cout<<" Detach success "<<ToHex((uint64_t)addr)<<endl;
return 0;
}
ShmServer.cc
#include"Comm.hpp"
#include"Fifo.hpp"
#include<unistd.h>
int main()
{
key_t k=GetShmKeyOrDie(path,pro_jid);
cout<<" key:"<<ToHex(k)<<endl;//转为十六进制输出
int shmid=CreateShm(k,defaultsize);
cout<<" shmid:"<<shmid<<endl;
char* addr=(char*)ShmAttach(shmid);
cout<<" Attach success, addr:"<<ToHex((uint64_t)addr)<<endl;
Fifo fifo;
Sync sync;
sync.OpenReadOrDie();//打开读端
//读取数据
for(;;)
{
if(!sync.Wait()) break;
//写端写完了,读端可以读了
cout<<" shm content:"<<addr<<endl;
sleep(1);
}
ShmDetach(addr);
cout<<" Detach success "<<ToHex((uint64_t)addr)<<endl;
DeleteShm(shmid);
return 0;
}
结果:
server 运行,并没有向之前一样直接读数据,而是等待写端写入数据:
client 写入数据后,读端才开始读数据: