1、System V 共享内存
原理如下图
系统调用接口介绍
int shmget(key_t key, size_t size, int shmflg)
功能:用来创建共享内存
参数
- key:这个共享内存段名字,内核用key来标识共享内存
- size:共享内存大小
- shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的(这里介绍两个:IPC_CREAT和IPC_EXCL,其中IPC_CREAT表示共享内存存在就返回,不存在就创建,IPC_EXCL不单独使用,IPC_EXCL | IPC_CREAT表示不存在就创建,存在就出错返回,作用是确保创建出来的共享内存一定是全新的)
返回值:成功返回一个非负整数,即该共享内存段的标识码(作用类似文件描述符fd);失败返回-1
void* shmat(int shmid, const void* shmaddr, int shmflg)
功能:将共享内存段连接到进程地址空间(在页表中创建映射关系)
参数
- shmid:共享内存标识
- shmaddr:指定连接的地址(可以直接传nullptr,让OS帮你分配)
- shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节(类比malloc);失败返回-1
int shmdt(const void *shmaddr)
功能:将共享内存段与当前进程脱离(删除页表中的映射关系)
参数
- shmaddr:由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
功能:用于控制共享内存
参数
- shmid:由shmget返回的共享内存标识码
- cmd:将要采取的动作(IPC_STAT---获取共享内存的相关信息、IPC_SET---设置共享内存的相关信息、IPC_RMID---释放共享内存)
- buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
(共享内存的生命周期是随内核的,即进程结束,共享内存不会释放,需要手动释放空间)
共享内存的通信代码如下
//comm.hpp
#include <string>
#include <iostream>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cstring>
using namespace std;
// 路径和项目id可以随便写,但是建议写和代码相关的
string pathname = "/shm";
int process_id=0x11223344;
const int size = 4096; // 建议设置为n*4096,Linux以4096为单位进行分配共享内存
string filename = "fifo"; //管道名,下面代码用管道是利用管道的同步机制,共享内存没有同步机制
key_t Getkey()
{
key_t key = ftok(pathname.c_str(), process_id);// 该系统调用相当于一个hash函数,用两个参数通过算法获得一个hash值 key
if(key < 0)
{
cout << "errno: " << errno << ",errstring: " << strerror(errno) << endl;
exit(1);
}
return key;
}
string ToHex(int id) // 返回16进制表示形式
{
char buffer[1024];
snprintf(buffer,sizeof(buffer),"0x%x",id);
return buffer;
}
int CreateShmHelper(key_t key, int flag)
{
int shmid = shmget(key, size, flag);
if(shmid < 0)
{
cout << "errno: " << errno << ",errstring: " << strerror(errno) << endl;
exit(2);
}
return shmid;
}
int GetShm(key_t key)
{
return CreateShmHelper(key, IPC_CREAT);
}
int CreateShm(key_t key)
{
return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0666);// 设置创建共享内存并设置权限0666---八进制
}
//server.cc
#include "comm.hpp"
//用一个类来初始化资源和释放资源
class Init
{
public:
Init()
{
int n = mkfifo(filename.c_str(), 0666);
if(n < 0) exit(1);
key_t key = Getkey();
cout << "key:" << ToHex(key) << endl;
// sleep(5);
// key vs shmid
// key:内核中使用,标识共享内存的唯一性
// shmid:应用这个共享内存的时候,我们使用shmid来使用操作共享内存
// 即key是给内核看的,shmid是给用户看的
shmid = CreateShm(key);
cout << "shmid:" << shmid << endl;
cout << "创建内存" << endl;
p = (char*)shmat(shmid, nullptr, 0);
cout << "将内存挂接到虚拟地址空间" << endl;
sleep(3);
//注意资源的初始化顺序,管道的打开要放在恰当的地方,不然会出bug,这边建议放到最后
fd = open(filename.c_str(), O_RDONLY);// 读端
}
~Init()
{
close(fd);
unlink(filename.c_str());// 删除管道文件
shmdt(p);
cout << "删除页表映射" << endl;
sleep(3);
shmctl(shmid, IPC_RMID, nullptr);
cout << "删除内存" << endl;
}
public:
int fd;
int shmid;
char*p;
};
int main()
{
Init init;
int code = 0;
while(1)
{
ssize_t n = read(init.fd, &code, sizeof(code));
if(n > 0)
{
cout << "client:" << init.p << endl;
}
else if(n == 0)
{
break;
}
}
return 0;
}
//client.cc
#include "comm.hpp"
int main()
{
key_t key = Getkey();
cout << "key:" << ToHex(key) << endl;
int shmid = GetShm(key);
cout << "获取shmid:" << shmid << endl;
char *p = (char *)shmat(shmid, nullptr, 0); // 开出来的空间当数组用即可
cout << "挂接" << endl;
int fd = open(filename.c_str(), O_WRONLY);
int code = 1;
cout << "write start" << endl;
for (int i = 0; i < 26; i++)
{
p[i] = 'a' + i;
sleep(2);
write(fd, &code, sizeof(code));
cout << "write:" << (char)('a' + i) << endl;
}
shmdt(p);
cout << "取消映射" << endl;
return 0;
}
在看完上面这段代码的前提下,我们就能解释两个进程如何得到同一个共享内存的标识符:本质是我们事先约定了标识符key的值(在上面的代码中,我们是用ftok函数生成的key,使用的相同的参数,所以返回值也相同。这里使用ftok只是让系统帮助我们生成key,其实只要key这个值相对其他共享内存的key是唯一的即可),放在头文件中,然后两个程序通过key就能找到同一个共享内存
特点:
- 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有的使用者的,一定要注意共享内存的使用安全问题
- 共享内存可以提供较大的空间
- 共享内存是所有进程间通信速度最快的(因为管道底层是对文件操作的复用,所以数据需要在用户缓冲区和内核文件缓冲区间来回拷贝,但是共享内存不需要,共享内存可以理解为用malloc申请了一块空间,两个进程都能看见,没有用户缓冲区和内核文件缓冲区来回拷贝的过程,所以更快)
查看管理ipc资源的指令介绍
ipcs -m
ipcrm -m shmid 删除共享内存
2、System V 消息队列---简单介绍
消息队列提供一个进程给另一个进程发送数据块的能力
3、System V信号量---简单介绍
背景介绍
当我们在进行进程间通信的时候,可能会出现A进程和B进程通过同一份资源在进行通信,突然C进程也开始往该资源中写数据/读数据,导致数据传输出现问题的情况,换句话说,当多执行流访问同一份资源时,我们需要保护该资源,所以我们有了信号量这个概念
在介绍信号量的概念之前,我们先来看看信号量(semaphore)的系统调用接口
信号量的概念和理解