--------------------------------------------------------------------------------------------------------------------------------
每日鸡汤:一旦相爱下去,就应共有一株向日葵。不管谁是太阳,都要时时辐射温暖;不管谁是向日葵,都要不渝地仰望。
--------------------------------------------------------------------------------------------------------------------------------
目录
一:直接原理
1.1、申请共享内存
1.2、释放共享内存
二:直接代码展示
2.1、创建共享内存—shmget
2.2、共享内存挂载—shmat
2.3、共享内存去挂载(关联)—shmdt
2.4、控制共享内存—shmctl
2.5、通信代码
三:共享内存的特性
四:扩展内容代码
4.1 获取共享内存中的属性
4.2 使用管道对共享内存实现同步机制
五:结语
一:直接原理
进程间通信的本质是:先让不同的进程,看到同一份资源。
1.1、申请共享内存
三步申请法:
- 操作系统在内存上去申请一段空间
- 将申请到的内存挂接到进程地址空间的共享区
- 返回起始虚拟地址
1.2、释放共享内存
释放共享内存:去关联,释放内存。
这些一系列操作(申请、挂载、去关联、释放)并不是进程自己做的,而是由操作系统完成的。即我们用户(进程)通过调用一些系统调用接口让操作系统来做这些事情。因为OS中肯定有很多进程都在相互交互、通信的,那么操作系统内肯定存在多个共享内存。所以操作系统就要把这些共享内存管理起来(先描述,再组织)。故在操作系统内核中肯定有结构体来描述共享内存。
二:直接代码展示
2.1、创建共享内存—shmget
#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
#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数 shmid:申请共享内存返回的共享内存标识符
- 参数 shmaddr:由OS决定,一般设为 nullptr
- 参数 shmflg:一般设为0即可
- 返回值:返回共享内存挂接在地址空间上虚拟的起始地址
// 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);
}
// 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
#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:一个共享内存的结构体,内部保存了共享内存的属性
// 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、通信代码
// processa.cc
#include "comm.hpp"int main()
{// 创建共享内存int shmid = CreateShm();// 挂接共享内存char* shmaddr = (char*)shmat(shmid, nullptr, 0);// ipc codewhile (true){std::cout << "client say# " << shmaddr << std::endl;sleep(1);}// 共享内存去挂载shmdt(shmaddr);// 释放共享内存shmctl(shmid, IPC_RMID, nullptr);return 0;
}
// processb.cc
#include "comm.hpp"int main()
{// 获取共享内存int shmid = GetShm();// 挂载char* shmaddr = (char*)shmat(shmid, nullptr, 0);// ipc codewhile(true){std::cout << "Please Enter@ ";fgets(shmaddr, 4094, stdin);}shmdt(shmaddr);return 0;
}
一旦有了共享内存,并且已经挂接到自己的地址空间中了,直接把它当成你的内存空间来使用即可,不需要调用用系统调用接口了,一旦有人把数据写到共享内存,其实我们立马就能看见了,不需要经过系统调用直接就能看到数据了。
三:共享内存的特性
- 共享内存没有同步互斥之类的保护机制
- 共享内存是所有的进程间通信中,速度最快的。(数据拷贝次数少)
- 共享内存中内部的数据,由用户自己维护
四:扩展内容代码
4.1 获取共享内存中的属性
通过 shmctl 函数来获取共享内存中属性。
#include "comm.hpp"int main()
{// 创建共享内存int shmid = CreateShm();// 挂接共享内存char* shmaddr = (char*)shmat(shmid, nullptr, 0);// ipc codestruct 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 使用管道对共享内存实现同步机制
// 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 0666enum
{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);}}
};
// 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 codestruct 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;
}
// 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 codewhile(true){std::cout << "Please Enter@ ";fgets(shmaddr, 4094, stdin);write(fd, "c", 1);}shmdt(shmaddr);return 0;
}
原理:没有管道时,a进程会一直读取共享内存中的数据,有了管道后,a进程在读取共享内存中的数据之前,会先从管道中读取,这个数据是随便的,只是为了读到特定信号,读取信号之后才可从共享内存中读取,否则一直阻塞在管道那里。当b进程向共享内存写入数据,写入数据之后,会给管道发送一个写入信号,而a进程阻塞在管道那里就是为了等待这一信号,当等待成功该信号中,a进程会立即读取共享内存中的数据,此时b进程已经向共享内存中写入了。这样就能实现共享内存中的同步与互斥。
五:结语
今天的分享到这里就结束了,如果觉得文章还可以的话,就一键三连支持一下欧。各位的支持就是捣蛋鬼前进的动力。