Linux——进程间通信之SystemV共享内存
前言
SystemV通信一般包括三种:共享内存、消息队列和信号量。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存通信
1)共享内存通信的原理
共享内存通信的原理是,两个进程的页表通信映射同一份物理内存完成通信,共享内存在系统中可以存在多份,实现多对进程进行通信。
2)创建共享内存
linux提供了创建共享内存接口shmget,其中key值在操作系统中是唯一的,可以使用ftok系统调用获取,为了两个进程能够顺利读取到同一份共享内存,所以key值必须用户手动传参。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//key为共享内存的唯一标识符,用于区分不同的共享内存,其值需要两个进程约定
//size共享内存的大小,单位是字节,一般是4kB的整数倍
//shmflg为标志位传参,可以同时传多个参数使用|连接
//IPC_CREAT:创建新的共享内存,如果已经存在该key值,直接返回对应的共享内存。如果不使用该参数,则检查是否存在该key对应的共享内存
//IPC_EXCL:与IPC_CREAT搭配使用,如果key对应的共享内存已经存在,直接报错,用于确保共享内存是新创建的。
//perms:指定是内存的权限,三位八进制数字
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
//作用是根据用户传入的参数生成一个唯一的key值
//pathname和proj_id可以随意指定,只有两个参数完全相同时,生成的key值才会相同
//返回值:生成的key,为一个数字
3)管理共享内存
因为操作系统中存在多份共享内存,操作系统必须对共享内存进行描述组织管理。
查看共享内存资源
ipcs -m
ipsc表示查看进程间通信,可以用于查看消息队列,共享内存和信号量
-m用于指定查看共享内存
控制共享内存资源
内核接口shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid:共享内存的id
//cmd:指定操作,常用操作有
IPC_STAT:将与shmid对应内核数据结构复制到buf中
IPC_SET:将shmid对应的共享内部分值设置为buf对应值
IPC_RMID:删除指定id的共享内存
//shmid_ds*buf:shmid_ds是操作系统内部对共享内存管理的数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
挂接共享内存
共享内存创建出来不属于进程,而属于操作系统,挂接的作用是将共享内存首地址挂接到进程的页表当中,使用shmat接口实现挂接。如果挂接成功,就意味着进程的页表中创建了与物理地址的映射,并且可以直接通过虚拟地址访问共享内存地址。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmid是共享内存的id
//shmaddr是用户指明的挂接位置,默认为nullptr即可
//shmflg是标志位,默认可读写挂接设置为0即可
//返回值:如果挂接成功,返回挂接成功的地址,否则返回(void*)(-1)
去关联共享内存
和挂接相反,去关联就是解除页表中虚拟地址和共享内存的关联信息,系统调用为shmdt
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
//shmaddr为共享内存的虚拟内存地址
//返回值为0表示解除关联成功,-1表示失败
4)销毁共享内存
创建共享内存后,进程关闭,共享内存不会主动释放,会一直存在,下一次创建提示共享内存已经存在,除非重启系统,即共享内存声明周期不随进程而随内核。
删除共享命令
ipcrm -m shmid
删除指定id的共享内存
shmctl(shmid,IPC_RMID,nullptr);
共享内存的特性
1)共享内存不提供进程协同的任何特性,即使内存中还未写入数据,也可以直接读内存。可能导致两个进程数据不一致,需要用户提供同步机制,可以使用管道或者是信号量来完成两个进程的同步。
2)共享内存通信的速度是最快的,因为进程读写数据是对共享内存直接读写,共享内存同时被两个进程映射,通信过程中不需要系统调用。
综合以上两个特性,可以使用管道和共享内存结合的方式,完成进程间通信,管道为共享内存提供协同机制,而共享内存用于实际数据通信。
共享内存与命名管道结合通信实例
//common_fifo
#ifndef __COMMON_FIFO_HPP__
#define __COMMON_FIFO_HPP__
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
using namespace std;
// 管道路径
string path = "./fifo";
// 创建mode
#define Mode 0666
class fifo
{
public:
fifo(const string &path) : _path(path)
{
umask(0);
int n = mkfifo(_path.c_str(), Mode);
// 管道创建失败,打印失败信息
if (n < 0)
{
cerr << "mkfifo fail" << " ,errorno " << errno << " , errstring: " << strerror(errno) << endl;
}
else
{
cout<<"mkfifo sucess"<<endl;
}
}
~fifo()
{
// 通信结束,删除管道
int n = unlink(_path.c_str());
if (n == 0)
{
cout << "remove fifo file" << _path << " sucess " << endl;
}
else
{
cout << "remove fifo file" << _path << " fail " << endl;
}
}
private:
string _path;
};
class Sync
{
public:
Sync()
: _wfd(-1), _rfd(-1)
{
cout<<"Sync"<<endl;
}
~Sync()
{
if(_wfd != -1)
{
close(_wfd);
}
if(_rfd != -1)
{
close(_rfd);
}
}
//以只读方式打开
void openRead()
{
int rfd = open(path.c_str(),O_RDONLY);
if(rfd < 0)
{
cerr<<"openRead fail,errno is"<<errno<<"error string is"<<strerror(errno)<<endl;
exit(1);
}
else
{
cout<<"openRead sucess"<<endl;
}
_rfd = rfd;
}
//以只写方式打开
void openWrite()
{
int wfd = open(path.c_str(),O_WRONLY);
if(wfd < 0)
{
cout<<"openWrite fail,errno is"<<errno<<" ,error string is"<<strerror(errno)<<endl;
exit(1);
}
else
{
cout<<"openWrite sucess"<<endl;
}
_wfd = wfd;
}
bool wait()
{
char c = 0;
ssize_t n = read(_rfd,&c,sizeof(c));
if(n <= 0)
{
cout<<"wait error"<<endl;
return false;
}
return true;
}
void wakeUp()
{
char wake = 'A';
ssize_t n = write(_wfd,&wake,sizeof(wake));
if(n < 0)
{
cout<<"wake up error"<<endl;
}
}
private:
int _wfd;
int _rfd;
};
#endif
//Common_shm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
#define SIZE 4096
const char* pathName = "/home/yw";
const int proj_id = 0x6666;
key_t getShmKey()
{
key_t key = ftok(pathName,proj_id);
if(key < 0)
{
cerr<<"ftok error,errno: "<<errno<<", error string: "<<strerror(errno)<<endl;
exit(-1);
}
return key;
}
class shmIPC
{
public:
shmIPC(key_t key)
:_key(key),_shmid(0),_size(SIZE),_address(nullptr)
{}
~shmIPC()
{
//解除挂接
int n = shmdt(_address);
if(n < 0)
{
exit(-1);
cout<<"shm Deattach fail, errno: "<<errno<<" ,error: "<<strerror(errno)<<endl;
}
_address = nullptr;
//删除共享内存
n = shmctl(_shmid,IPC_RMID,nullptr);
if(n < 0)
{
exit(-1);
cerr<<"shm del fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
}
else
{
cout<<"shm del sucess"<<endl;
}
}
void createShm(size_t size = SIZE)
{
_size = size;
int shmid = shmget(_key,_size,IPC_CREAT | IPC_EXCL);
if(shmid < 0)
{
cerr<<"shm create fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
exit(-1);
}
_shmid = shmid;
}
void getShm()
{
int shmid = shmget(_key,_size,IPC_CREAT);
if(shmid < 0)
{
cerr<<"shm get fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
exit(-1);
}
_shmid = shmid;
}
void print()
{
struct shmid_ds shmds;
int n = shmctl(_shmid,IPC_STAT,&shmds);
if(n < 0)
{
cerr<<"shm print fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
exit(-1);
}
cout<<"shmds.shm_segsz: "<<shmds.shm_segsz<<endl;
cout<<"shmds.shm_nattch: "<<shmds.shm_nattch<<endl;
cout<<"shmds.shm_ctime: "<<shmds.shm_ctime<<endl;
cout<<"shmds.shm_perm.__key: "<<shmds.shm_perm.__key<<endl;
cout<<"shmid: "<<_shmid<<endl;
}
void *shmAttach()
{
void *address = shmat(_shmid,nullptr,0);
if(address == (void*) (-1))
{
cout<<"shm attach fail, errno: "<<errno<<" ,error: "<<strerror(errno)<<endl;
exit(-1);
}
_address = address;
return address;
}
private:
key_t _key;
int _shmid;
size_t _size;
void* _address;
};
//shmClient.cpp
#include "Common_shm.hpp"
#include "Common_fifo.hpp"
int main()
{
//0.创建监视对象
Sync sync;
sync.openRead();
//1.创建对象
shmIPC shmipc(getShmKey());
//2.创建共享内存
shmipc.createShm(SIZE);
//3.挂接共享内存
char *address = (char*) shmipc.shmAttach() ;
cout<< address <<endl;
//TO DO
while(true)
{
bool isWakeUp = sync.wait();
if(isWakeUp)
cout<<"shm content: "<<address<<endl;
else
break;
}
return 0;
}
//Common_shmServer.cpp
#include "Common_shm.hpp"
#include "Common_fifo.hpp"
int main()
{
//0.创建监管对象
fifo f1(path);
Sync sync;
sync.openWrite();
//1.创建对象
shmIPC shmipc(getShmKey());
//2.获取共享内存
shmipc.getShm();
//3.挂接共享内存
char *address = (char*) shmipc.shmAttach() ;
cout<< address <<endl;
//TO DO
int i=10;
memset(address,0,SIZE);
for(int i=0;i<10;i++)
{
address[i] = i + 'A';
sleep(2);
sync.wakeUp();
}
return 0;
}
在上面的代码中,shmServer每隔1s向共享内存中写入一个字符并且唤醒shmClient进程,shmClient进程每次唤醒打印共享内存中的数据。