System V
当我们需要不同进程进行通信,首先要让进程看见同一份资源,即物理内存中的———共享内存
共享内存原理图:
1、创建:申请一块共享内存
2、关联进程:将共享内存映射到进程的地址空间,也就是将共享内存的地址告诉进程。
3、取消关联:未来不想通信时,将进程中的映射取消掉
3、释放共享内存:最后释放共享内存,结束通信
系统中可以用shm(share memory)来进行通信,且同时可以存在多对进程通信,可能存在多个共享内存,所以系统中一定存在很多shm,OS对这些共享内存通过先组织,再描述进行管理。
依此可知,共享内存不只是开辟空间即可,系统也要为了管理shm,构建对应的描述共享内存的结构体对象(伪代码:struct_shm)
共享内存没有保护机制(同步互斥),管道通过接口通信,而共享内存直接通信
共享内存=内存空间+共享内存的数据结构(struct_shm)
创建
ftok——获取一个共享内存的唯一标识符(key_t)
key_t ftok(const char *pathname, int proj_id);
功能:
获取一个共享内存的唯一标识符key
函数参数:
pathname:可以传入任何文件名
proj_id:项目ID
返回值:
成功返回key值,失败返回-1
shmget——创建共享内存
int shmget(key_t key, size_t size, int shmflg);
功能:
创建共享内存
函数参数:
key:传入ftok函数获取的共享内存唯一标识符
size:共享内存的大小(页:4KB的整数倍),size如果不是整数倍,系统会给你开整数倍,但是实际大小还是你所开的大小
shmflg:权限,由9个权限标准构成
这里介绍两个常用基础权限
IPC_CREAT
: 如果底层存在这个标识符的共享内存空间,就打开返回,不存在就创建
IPC_EXCL
: 如果底层存在这个标识符的共享内存空间,就出错返回(必须和IPC_CREAT一起使用)
两个选项合起来用就可以创建一个全新的共享内存空间
返回值:
成功返回共享内存标识码值(给用户看的),失败返回-1
shmat——将共享内存空间关联到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
将共享内存空间关联到进程地址空间
参数:
shmid:共享内存标识符(shmat的返回值)
shmaddr:指定连接地址。shmaddr为NULL,核心自动选择一个地址
shmflg:两个可能取值是SHM_RND和SHM_RDONLY
返回值: 成功返回一个指针(虚拟地址空间中共享内存的地址,是一个虚拟地址),失败返回-1(返回值的意思类似于malloc)
shmdt——取消关联
int shmdt(const void *shmaddr);
功能:
取消共享内存空间和进程地址空间的关联
参数:
shmaddr:共享内存的起始地址(shmat获取的指针)
返回值: 成功返回0,失败返回-1
shmctl——控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
控制共享内存
参数:
shmid:共享内存标识符(shmat的返回值)
cmd:命令,有三个
IPC_STAT: 把shmid_ds结构中设置为共享内存当前关联值
IPC_SET: 把共享内存的当前关联值设置为shmid_ds数据结构中的值
IPC_RMID:删除共享内存段
buf:指向一个报错这共享内存的模式状态和访问权限的数据结构(通常为nullptr)
返回值:
成功返回0,失败返回-1
删除共享内存
1、指令删除ipcrm -m shmid
共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除
为什么用shmid不用key呢?
key:类比inode,唯一性的区分,只在创建时使用
shmid:类比文件fd,对shm未来所有的操作,在用户层,都用shmid共享内存的生命周期不随进程结束而消失,是跟随OS的,OS重启才结束生命周期
2、调用系统调用删除
共享内存通信
comm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
#include <iostream>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
// IPC_CREAT and IPC_EXCL
// 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回
// IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回 -- 如果创建成功,对应的shm,一定是最新的!
#define PATHNAME "."
#define PROJID 0x6666
const int gsize = 4096; //暂时
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJID);
if(k == -1)
{
cerr << "error: " << errno << " : " << strerror(errno) << endl;
exit(1);
}
return k;
}
// string toHex(int x)
// {
// char buffer[64];
// snprintf(buffer, sizeof buffer, "0x%x", x);
// return buffer;
// }
static int createShmHelper(key_t k, int size, int flag)//封装创建shm
{
int shmid = shmget(k, gsize, flag);
if(shmid == -1)
{
cerr << "error: " << errno << " : " << strerror(errno) << endl;
exit(2);
}
return shmid;
}
int createShm(key_t k, int size)//创建shm
{
umask(0);
return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);//带上IPC_EXCL,如果存在shm,出错
}
int getShm(key_t k, int size)//获取shm,不带 IPC_EXCL单独一个 IPC_CREAT,存在shm,就返回,不存在就创建。
{
return createShmHelper(k, size, IPC_CREAT);
}
char* attachShm(int shmid)//关联
{
char *start = (char*)shmat(shmid, nullptr, 0);
return start;
}
void detachShm(char *start)//取消关联
{
int n = shmdt(start);//取消关联
assert(n != -1);
(void)n;
}
void delShm(int shmid)//删除shm空间
{
int n = shmctl(shmid, IPC_RMID, nullptr);//IPC_RMID删除共享内存段
assert(n != -1);
(void)n;
}
#define SERVER 1
#define CLIENT 0
class Init
{
public:
Init(int t):type(t)//依据传值初始化 客户端/服务端
{
key_t k = getKey();
if(type == SERVER) shmid = createShm(k, gsize);//如果是服务端,创建shm
else shmid = getShm(k, gsize);//如果是客户端,获取shm
start = attachShm(shmid);//把shm映射到客户端/服务端中的进程地址空间
}
char *getStart(){ return start; }
~Init()
{
detachShm(start);//取消关联,从进程地址空间中删除映射
if(type == SERVER) delShm(shmid);//由服务端删除shm
}
private:
char *start;
int type; //server or client
int shmid;
};
#endif
server.cc
#include "comm.hpp"
#include <unistd.h>
int main()
{
Init init(SERVER);
char *start = init.getStart();
int n = 0;
while(n <= 30)
{
cout <<"client -> server# "<< start << endl;
sleep(1);
n++;
}
return 0;
}
client.cc
#include "comm.hpp"
#include <unistd.h>
int main()
{
Init init(CLIENT);
char *start = init.getStart();
char c = 'A';
while(c <= 'Z')
{
start[c - 'A'] = c;
c++;
start[c - 'A'] = '\0';
sleep(1);
}
return 0;
}
ipcs -m
查看共享内存信息
perms是权限
bytes是共享空间大小
nattch是关联这个共享内存的进程个数
Tips:如果bytes大小给了4097 用户用的是4097 底层OS分配还是对齐4KB给8KB,page页4KB对齐
共享内存的优点
在通信时,没有使用任何接口,一旦共享内存映射到进程地址空间,该共享内存就被所有进程直接看到,因为共享内存这种特性,可以在通信时减少拷贝次数,所以共享内存是所有进程间通信最快的。