
--------------------------------------------------------------------------------------------------------------------------------
每日鸡汤:一旦相爱下去,就应共有一株向日葵。不管谁是太阳,都要时时辐射温暖;不管谁是向日葵,都要不渝地仰望。
--------------------------------------------------------------------------------------------------------------------------------
目录
[4.1 获取共享内存中的属性](#4.1 获取共享内存中的属性)
[4.2 使用管道对共享内存实现同步机制](#4.2 使用管道对共享内存实现同步机制)
一:直接原理
进程间通信的本质是:先让不同的进程,看到同一份资源。
1.1、申请共享内存
三步申请法:
- 操作系统在内存上去申请一段空间
- 将申请到的内存挂接到进程地址空间的共享区
- 返回起始虚拟地址
1.2、释放共享内存
释放共享内存:去关联,释放内存。
这些一系列操作(申请、挂载、去关联、释放)并不是进程自己做的,而是由操作系统完成的。即我们用户(进程)通过调用一些系统调用接口让操作系统来做这些事情。因为OS中肯定有很多进程都在相互交互、通信的,那么操作系统内肯定存在多个共享内存。所以操作系统就要把这些共享内存管理起来(先描述,再组织)。故在操作系统内核中肯定有结构体来描述共享内存。
二:直接代码展示
2.1、创建共享内存---shmget
cpp
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- **参数 key:**决定保证让不同的进程看到同一个共享内存;让我们知道这个共享内存是否存在。
- **参数 size:**要创建的共享内存的大小,单位是字节。
- 参数 shmflg:一个标记位。选项 IPC_CREAT:创建一个共享内存,若不存在直接创建,存在直接获取并返回,一般单独使用;选项 IPC_EXCL:若申请的共享内存存在,错误返回,一般不单独使用。选项 IPC_CREAT | IPC_EXCL:创建共享内存,若不存在则创建,存在,出错返回,确保我们申请成功的共享内存一定是一个新的。
- 返回值:创建共享内存成功,返回共享内存标识符;失败返回-1
再谈参数 key:
- key是一个数字,数字是几不重要,关键在于它必须在内核中具有唯一性,能够让不同进程进行唯一性标识。
- 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了。
- 对于一个已经创建好的共享内存,key哎共享内存中的描述对象中。
- 第一个创建共享内存的时候,必须已经有一个 key 了。
- "key 就类似于路径,一定要具有唯一性"
使用 ftok 函数调用来创建一个 key

key VS shmid(共享内存标识符)
- key:在操作系统内标定唯一性
- shmid:只在你的进程内,用来表示资源的唯一性
查看共享内存:ipcs -m
共享内存的生命周期是随内核的,只要用户不主动关闭,共享内存会一直存在,除非内核重启或者用户关闭释放。
删除共享内存:ipcrm -m shmid共享内存的大小,一般建议是 4096 的整数倍,而假设申请 4097 字节的共享内存,实际上操作系统给你的是 4096*2的大小,就会造成资源的浪费。
2.2、共享内存挂载---shmat
cpp
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- **参数 shmid:**申请共享内存返回的共享内存标识符
- 参数 shmaddr:由OS决定,一般设为 nullptr
- **参数 shmflg:**一般设为0即可
- **返回值:**返回共享内存挂接在地址空间上虚拟的起始地址
cpp
// comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "log.hpp"
Log lg;
const std::string pathname = "/home/alin";
const int proj_id = 0x8888;
const int size = 4096;
// 获取key值
key_t GetKey()
{
key_t key = ftok(pathname.c_str(), proj_id);
if (key < 0)
{
lg(Fatal, "ftok error: %d, strerror: %s", errno, strerror(errno));
exit(1);
}
lg(Info, "ftok success, key is : 0x%x", key);
return key;
}
// 由flag 来标记怎样申请共享内存
int GetShareMemHelper(int flag)
{
key_t k = GetKey();
int shmid = shmget(k, size, flag);
if(shmid < 0)
{
lg(Fatal, "create share memory error: %d, strerror", errno, strerror(errno));
exit(2);
}
lg(Info, "create share memory success, shmid is : %d", shmid);
return shmid;
}
// 创建共享内存
int CreateShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
// 获取共享内存
int GetShm()
{
return GetShareMemHelper(IPC_CREAT);
}
cpp
// prcessa.cc
#include "comm.hpp"
int main()
{
// 创建共享内存
int shmid = CreateShm();
// 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
return 0;
}
2.3、共享内存去挂载(关联)---shmdt

- 参数 shmaddr:挂接时获取的起始虚拟地址
- 返回值:成功返回0,失败返回-1
cpp
#include "comm.hpp"
int main()
{
// 创建共享内存
int shmid = CreateShm();
// 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
// 共享内存去挂载
shmdt(shmaddr);
return 0;
}
2.4、控制共享内存---shmctl

- 参数 cmd:决定做什么操作。IPC_STAT:将内核中共享内存中的属性拷贝到 buff 中;IPC_RMID:删除共享内存,将该共享内存标记为0。
- 参数 struct shmid_ds:一个共享内存的结构体,内部保存了共享内存的属性
cpp
// processa.cc
#include "comm.hpp"
int main()
{
// 创建共享内存
int shmid = CreateShm();
// 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
// 共享内存去挂载
shmdt(shmaddr);
// 释放共享内存
shmctl(shmid, IPC_RMID, nullptr);
return 0;
}
2.5、通信代码
cpp
// processa.cc
#include "comm.hpp"
int main()
{
// 创建共享内存
int shmid = CreateShm();
// 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
while (true)
{
std::cout << "client say# " << shmaddr << std::endl;
sleep(1);
}
// 共享内存去挂载
shmdt(shmaddr);
// 释放共享内存
shmctl(shmid, IPC_RMID, nullptr);
return 0;
}
cpp
// processb.cc
#include "comm.hpp"
int main()
{
// 获取共享内存
int shmid = GetShm();
// 挂载
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
while(true)
{
std::cout << "Please Enter@ ";
fgets(shmaddr, 4094, stdin);
}
shmdt(shmaddr);
return 0;
}
一旦有了共享内存,并且已经挂接到自己的地址空间中了,直接把它当成你的内存空间来使用即可,不需要调用用系统调用接口了,一旦有人把数据写到共享内存,其实我们立马就能看见了,不需要经过系统调用直接就能看到数据了。
三:共享内存的特性
- 共享内存没有同步互斥之类的保护机制
- 共享内存是所有的进程间通信中,速度最快的。(数据拷贝次数少)
- 共享内存中内部的数据,由用户自己维护
四:扩展内容代码
4.1 获取共享内存中的属性
通过 shmctl 函数来获取共享内存中属性。

cpp
#include "comm.hpp"
int main()
{
// 创建共享内存
int shmid = CreateShm();
// 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
struct shmid_ds shmds;
while (true)
{
std::cout << "client say# " << shmaddr << std::endl;
sleep(1);
shmctl(shmid,IPC_STAT, &shmds); // shmds输出型参数
std::cout << "shm nattch: " << shmds.shm_nattch << std::endl;
std::cout << "shm size: " << shmds.shm_segsz << std::endl;
std::cout << "shm mode: " << shmds.shm_perm.mode << std::endl;
printf("shm key: 0x%x\n", shmds.shm_perm.__key);
sleep(5);
}
// 共享内存去挂载
shmdt(shmaddr);
// 释放共享内存
shmctl(shmid, IPC_RMID, nullptr);
return 0;
}

4.2 使用管道对共享内存实现同步机制
cpp
// comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "log.hpp"
Log lg;
const std::string pathname = "/home/alin";
const int proj_id = 0x6666;
// const int proj_id = 1024 ^ getpid();
const int size = 4096;
// 获取key值
key_t GetKey()
{
key_t key = ftok(pathname.c_str(), proj_id);
if (key < 0)
{
lg(Fatal, "ftok error: %d, strerror: %s", errno, strerror(errno));
exit(1);
}
lg(Info, "ftok success, key is : 0x%x", key);
return key;
}
// 由flag 来标记怎样申请共享内存
int GetShareMemHelper(int flag)
{
key_t k = GetKey();
int shmid = shmget(k, size, flag);
if (shmid < 0)
{
lg(Fatal, "create share memory error: %d, strerror: %s", errno, strerror(errno));
exit(2);
}
lg(Info, "create share memory success, shmid is : %d", shmid);
return shmid;
}
// 创建共享内存
int CreateShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
// 获取共享内存
int GetShm()
{
return GetShareMemHelper(IPC_CREAT);
}
// 使用有名管道来实现同步机制
#define FIFO_FILE "./myfifo"
#define MODE 0666
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR
};
class Init
{
public:
Init()
{
// 创建管道
int n = mkfifo(FIFO_FILE, MODE);
if (n < -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
int m = unlink(FIFO_FILE);
if (m < -1)
{
perror("unlink");
exit(FIFO_DELETE_ERR);
}
}
};
cpp
// processa.cc
#include "comm.hpp"
int main()
{
Init init;
// 创建共享内存
int shmid = CreateShm();
// 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
int fd = open(FIFO_FILE, O_RDONLY);
if(fd < 0)
{
lg(Fatal, "err code: %d, error string: %s", errno, strerror(errno));
exit(FIFO_OPEN_ERR);
}
// ipc code
struct shmid_ds shmds;
while (true)
{
char c;
ssize_t s = read(fd, &c, 1);
if(s <= 0) break;
std::cout << "client say# " << shmaddr << std::endl;
// sleep(1);
// shmctl(shmid,IPC_STAT, &shmds); // shmds输出型参数
// std::cout << "shm nattch: " << shmds.shm_nattch << std::endl;
// std::cout << "shm size: " << shmds.shm_segsz << std::endl;
// std::cout << "shm mode: " << shmds.shm_perm.mode << std::endl;
// printf("shm key: 0x%x\n", shmds.shm_perm.__key);
// sleep(5);
}
// 共享内存去挂载
shmdt(shmaddr);
// 释放共享内存
shmctl(shmid, IPC_RMID, nullptr);
return 0;
}
cpp
// processb.cc
#include "comm.hpp"
int main()
{
// 获取共享内存
int shmid = GetShm();
// 挂载
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
int fd = open(FIFO_FILE, O_WRONLY);
if(fd < 00)
{
lg(Fatal, "err code: %d, error string: %s", errno, strerror(errno));
exit(FIFO_OPEN_ERR);
}
// ipc code
while(true)
{
std::cout << "Please Enter@ ";
fgets(shmaddr, 4094, stdin);
write(fd, "c", 1);
}
shmdt(shmaddr);
return 0;
}
原理:没有管道时,a进程会一直读取共享内存中的数据,有了管道后,a进程在读取共享内存中的数据之前,会先从管道中读取,这个数据是随便的,只是为了读到特定信号,读取信号之后才可从共享内存中读取,否则一直阻塞在管道那里。当b进程向共享内存写入数据,写入数据之后,会给管道发送一个写入信号,而a进程阻塞在管道那里就是为了等待这一信号,当等待成功该信号中,a进程会立即读取共享内存中的数据,此时b进程已经向共享内存中写入了。这样就能实现共享内存中的同步与互斥。
五:结语
今天的分享到这里就结束了,如果觉得文章还可以的话,就一键三连支持一下欧。各位的支持就是捣蛋鬼前进的动力。
