【Linux笔记】进程间通信——system v 共享内存
🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux笔记】进程间通信——命名管道
🔖流水不争,争的是滔滔不
- 一、共享内存简介
- 二、使用共享内存的接口
- 三、简单用共享内存实现进程间的通信
一、共享内存简介
共享内存是Linux系统中进程间通信(IPC)的一种方式,允许多个进程直接访问同一块物理内存区域。它的核心特点是零拷贝——数据直接在内存中共享,无需通过内核缓冲区中转,因此成为速度最快的IPC机制。共享内存是system v的一种标准,Linux内核支持这种标准,专门设计了这个模块。
两个进程访问同一块物理内存,如上图共享内存通过页表映射到两个进程的地址空间,两个进程就可以看到同一块资源,进而实现进程间的通信。
二、使用共享内存的接口
在物理内存中创建块共享内存的区域
int shmget(key_t key, size_t size, int shmflg);
第一个参数的key是标识共享内存的唯一性,不同的进程通过共享内存来进行通信,key来区分是不是对应的共享内存,key不是内核直接形成的而是在用户层构建并传入给操作系统的。
第二个参数是共享内存的大小。
第三个参数传 IPC_CREAT和IPC_EXCL
IPC_CREAT表示创建共享内存不存在就创建,否则打开已经存在的共享内存并返回。
IPC_CREAT | IPC_EXCL表示如果要创建的共享内存不存在,就创建它,如果已经存在,shmflg就会出错返回。只有shmflg成功返回一定是一个全新的共享内存。
创建key值
key_t ftok(const char *pathname, int proj_id);
第一个参数是路径,第二个参数随便写
删除管道
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
第一个参数是创建共享内存时的返回值。
第二个参数传入IPC_RMID,表示标记要销毁的段。
第三个参数传入null就可以
让共享内存与进程建立联系
void *shmat(int shmid, const void *shmaddr, int shmflg);
第一个参数也是创建共享内存时的返回值。
第二个参数指定了共享内存段要附加到的地址,若 shmaddr 为 nullptr(也就是 NULL),系统会自动选择一个合适的地址来附加共享内存段,这是最常用的做法。
第三个参数是一组标志位,用于控制共享内存段的附加方式
将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);
参数传入共享内存与进程建立连接时返回的指针。
三、简单用共享内存实现进程间的通信
私用成员变量
private:
int _gsize;
int _shmid;
void *_start_mem;
key_t _k;
string _usertype;
全局变量
const string pathname = ".";
const int projid = 0x66;
const int gdefaultid = -1;
const int gsize = 4096;
#define CREATE "create"
#define GET "get"
Shm(string pathname, int projid, string usertype) // 构造
: _shmid(gdefaultid)
, _gsize(gsize)
, _start_mem(nullptr)
, _usertype(usertype)
{
_k = ftok(pathname.c_str(), projid);
if (_k < 0)
{
ERR_EXIT("ftok");
}
if (_usertype == CREATE)
{
Create();
}
else if (_usertype == GET)
{
Get();
}
Attach();
}
首先要构造这个共享内存,用户调用的时候传入路径和projid以及选择时创建共享内存还是从共享内存中读取数据。
void CreateHelper(int flg) // 创建共享内存
{
printf("key: 0x%x
", _k);
_shmid = shmget(_k, _gsize, flg);
if (_shmid < 0)
{
ERR_EXIT("shmat");
}
printf("shmid: %d
", _shmid);
}
void Create()
{
CreateHelper(IPC_CREAT | IPC_EXCL | 0666); // 创建共享内存
}
void Get()
{
CreateHelper(IPC_CREAT); // 读取共享内存
}
void Destroy() // 删除共享内存
{
Detach();
if (_usertype == CREATE)
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
if (n < 0)
{
ERR_EXIT("shmctl");
}
else if (n > 0)
{
printf("deleat success:%d", n);
}
}
}
创建共享内存通过调用相应的函数,创建共享内存,注意创建共享内存和读取共享内存的内容只用shmgt函数的第三个参数是不同的。删除共享内存调用相应的函数。
void Attach() // 让进程与共享内存建立联系
{
_start_mem = shmat(_shmid, nullptr, 0);
if ((long long)_start_mem < 0)
{
ERR_EXIT("shmat");
}
cout << "attch success" << endl;
}
void *VirtualAddr()//打印
{
printf("VirtualAddr: %p
", _start_mem);
return _start_mem;
}
void Detach()//将共享内存段与当前进程脱离
{
int n = shmdt(_start_mem);
if (n == 0)
{
printf("detach success
");
}
}
创建出共享内存了要想让进程基于共享内存进行通信,就需要共享内存与进程建立联系。通过shmat函数让进程与共享内存建立联系,结束之后通过shmdt函数让共享内存与进程脱离联系。
共享内存没有同步机制,也就是说,客户端写客户端的服务端写服务端的。也就是说共享内存没有保护机制。
正经的应该是通过互斥锁信号量来实现同步机制。但是下面通过之前写过的命名管道这种方法(效率太低了)来简单实现。
通过命名管道的管道,这里进行通信的管道不用做数据的通信使用,而是用作通知使用。当共享内存的客户端进程写操作满足用户需求的时候(比方说想打印AA,当满足这一条件的时候),向命名管道里放一个字符唤醒进程B服务端读操作。
void wakeup()//写激活共享内存
{
char c;
int n=write(_fd,&c,1);
printf("尝试唤醒: %d
",n);
}
bool wait()//服务端读
{
char c;
int number=read(_fd,&c,1);
if(number>0)
{
printf("醒来: %d
",number);
return true;
}
return false;
}
上面代码就是之气的命名管道的写操作和读操作,稍微改一下,让写端往里面写一个字符,激活服务端读。
//client.cc
#include "shm.hpp"
#include "Fifo.hpp"
int main()
{
FillOper wf(PATH,FILENAME);
wf.OpenForWrite();
Shm shm(pathname,projid,GET);
char* mem=(char*)shm.VirtualAddr();
int index=0;
for(char c='A';c <='Z';c++,index+=2)
{
// 才是向共享内存写入
sleep(1);
mem[index] = c;
mem[index + 1] = c;
sleep(1);
mem[index+2] = 0;
wf.wakeup();
}
wf.Close();
return 0;
}
//server.cc
#include "shm.hpp"
#include "Fifo.hpp"
int main()
{
Shm shm(pathname,projid,CREATE);
shm.Attr();
NamedFifo fifo(PATH,FILENAME);
FillOper rd(PATH,FILENAME);
rd.OpenForRead();
char* mem=(char*)shm.VirtualAddr();
while(true)
{
if(rd.wait())
{
printf("%s
",mem);
}
else
break;
}
rd.Close();
std::cout << "server end normal!" << std::endl; // server段的析构函数没有被成功调用!
return 0;
}
客户端写操作,打印26个字母,通过命名管道当满足条件打印两个字母,让命名管道的写端激活服务端进行读操作
comm.hpp
#pragma once
#include
#include
#define ERR_EXIT(m)
do
{
perror(m);
exit(EXIT_FAILURE);
} while (0)
shm.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include "comm.hpp"
using namespace std;
const string pathname = ".";
const int projid = 0x66;
const int gdefaultid = -1;
const int gsize = 4096;
#define CREATE "create"
#define GET "get"
class Shm
{
private:
void CreateHelper(int flg) // 创建共享内存
{
printf("key: 0x%x
", _k);
_shmid = shmget(_k, _gsize, flg);
if (_shmid < 0)
{
ERR_EXIT("shmat");
}
printf("shmid: %d
", _shmid);
}
void Create()
{
CreateHelper(IPC_CREAT | IPC_EXCL | 0666); // 创建共享内存
}
void Detach()
{
int n = shmdt(_start_mem);
if (n == 0)
{
printf("detach success
");
}
}
void Get()
{
CreateHelper(IPC_CREAT); // 读取共享内存
}
void Destroy() // 删除共享内存
{
Detach();
if (_usertype == CREATE)
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
if (n < 0)
{
ERR_EXIT("shmctl");
}
else if (n > 0)
{
printf("deleat success:%d", n);
}
}
}
void Attach() // 让进程与共享内存建立联系
{
_start_mem = shmat(_shmid, nullptr, 0);
if ((long long)_start_mem < 0)
{
ERR_EXIT("shmat");
}
cout << "attch success" << endl;
}
public:
Shm(string pathname, int projid, string usertype) // 构造
: _shmid(gdefaultid), _gsize(gsize), _start_mem(nullptr), _usertype(usertype)
{
_k = ftok(pathname.c_str(), projid);
if (_k < 0)
{
ERR_EXIT("ftok");
}
if (_usertype == CREATE)
{
Create();
}
else if (_usertype == GET)
{
Get();
}
Attach();
}
void *VirtualAddr()
{
printf("VirtualAddr: %p
", _start_mem);
return _start_mem;
}
int size()
{
return _gsize;
}
void Attr()
{
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds);
printf("shm_segsz: %ld
", ds.shm_segsz);
printf("key: 0x%x
", ds.shm_perm.__key);
}
~Shm()
{
std::cout << _usertype << std::endl;
if (_usertype == CREATE)
Destroy();
}
private:
int _gsize;
int _shmid;
void *_start_mem;
key_t _k;
string _usertype;
};
Fifo.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include "comm.hpp"
using namespace std;
#define PATH "."
#define FILENAME "fifo"
class NamedFifo
{
public:
NamedFifo(const string& path,const string& name)
:_path(path)
,_name(name)
{
_fifoname=_path+"/"+_name;
//创建管道
umask(0);
int n=mkfifo(_fifoname.c_str(),0666);
if(n<0)
{
cout<<"管道创建失败"<<endl;
}
else
{
cout<<"管道创建成功"<<endl;
}
}
~NamedFifo()
{
int n=unlink(_fifoname.c_str());
if(n<0)
{
cout<<"管道删除失败"<<endl;
}
else
{
cout<<"管道删除成功"<<endl;
}
}
private:
string _path;
string _name;
string _fifoname;
};
class FillOper
{
public:
FillOper(const string& path,const string& name)
:_path(path)
,_name(name)
,_fd(-1)
{
_fifoname=_path+"/"+_name;
}
void OpenForRead()//打开管道文件进行读操作
{
_fd=open(_fifoname.c_str(),O_RDONLY);
if(_fd<0)
{
cout<<"管道文件打开失败"<<endl;
}
else
{
cout<<"管道文件打开成功"<<endl;
}
}
void OpenForWrite()
{
_fd=open(_fifoname.c_str(),O_WRONLY);
if(_fd<0)
{
cout<<"管道文件打开失败"<<endl;
}
else
{
cout<<"管道文件打开成功"<<endl;
}
}
void wakeup()//写激活共享内存
{
char c;
int n=write(_fd,&c,1);
printf("尝试唤醒: %d
",n);
}
bool wait()//服务端读
{
char c;
int number=read(_fd,&c,1);
if(number>0)
{
printf("醒来: %d
",number);
return true;
}
return false;
}
void Close()
{
if(_fd>0)
{
close(_fd);
}
}
~FillOper(){}
private:
string _path;
string _name;
string _fifoname;
int _fd;
};
server.cc
#include "shm.hpp"
#include "Fifo.hpp"
int main()
{
Shm shm(pathname,projid,CREATE);
shm.Attr();
NamedFifo fifo(PATH,FILENAME);
FillOper rd(PATH,FILENAME);
rd.OpenForRead();
char* mem=(char*)shm.VirtualAddr();
while(true)
{
if(rd.wait())
{
printf("%s
",mem);
}
else
break;
}
rd.Close();
std::cout << "server end normal!" << std::endl; // server段的析构函数没有被成功调用!
return 0;
}
client.cc
#include "shm.hpp"
#include "Fifo.hpp"
int main()
{
FillOper wf(PATH,FILENAME);
wf.OpenForWrite();
Shm shm(pathname,projid,GET);
char* mem=(char*)shm.VirtualAddr();
int index=0;
for(char c='A';c <='Z';c++,index+=2)
{
// 才是向共享内存写入
sleep(1);
mem[index] = c;
mem[index + 1] = c;
sleep(1);
mem[index+2] = 0;
wf.wakeup();
}
wf.Close();
return 0;
}
Makefile
.PHONY:all
all:client server
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f client server