🤖个人主页:晚风相伴-CSDN博客
💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧
🙏如果内容有误的话,还望指出,谢谢!!!
目录
✨理解进程间通信的本质
共享内存
✨共享内存如何实现进程间通信
🔥共享内存示意图
🔥认识共享内存函数
shmget函数
ftok函数
shmat函数
shmdt函数
代码实现共享内存
🔥查看共享内存的信息的命令
✨结论
完整代码链接
✨理解进程间通信的本质
因为进程具有独立性,所以每个进程都只知道自己,而不知道有另外的进程存在,所以要实现不同进程间的通信,就要让不同的进程都能看到同一块资源,这块资源不属于任意一个进程,而是强调共享,利用这块资源就可以实现进程间通信了。
总结一下要点
- 进程间通信的前提是要让不同的进程看到同一块资源
- 这一块资源不隶属于任何一个进程,而是被这些进程所共享
System V分类
- 消息队列
- 共享内存
- 信号量
共享内存
进程间通信的方式之一是System V,共享内存是System V通信中的一种。共享内存是最快的进程间通信的方式。
✨共享内存如何实现进程间通信
通信的前提:要让不同进程间实现通信就需要让不同的进程都能看到同一块资源。
实现过程如下:
- 一个进程在物理内存中申请一块空间,并且通过页表映射到自己的地址空间中的共享区
- 不同的进程也将这块空间通过页表映射到各自的地址空间中的共享区,这就实现了不同的进程看到了同一块资源
- 那么进程就可以找到自己的地址空间中的共享区访问共享内存,当一个进程往当一个进程往这块共享内存写入数据时,其它进程访问这块共享内存就可以读取到数据,从而实现了进程间的通信。
🔥共享内存示意图
🔥认识共享内存函数
shmget函数
功能:用来创建共享内存
参数:
- key:是一个整形,key值相同的进程才能看到同一块共享内存,就好比共享内存是一把锁,而key是钥匙,只有拿到相同钥匙的进程才能打开这把共享内存这把锁。
- size:是这个共享内存的大小。
- shmflg:创建方式。和open函数里的flags用法是类似的,一共有9个。主要介绍IPC_CREAT和IPC_EXCL。IPC_CREAT单独使用,创建共享内存时,如果底层已经存在,获取之;如果底层不存在,创建之并返回。IPC_EXCL单独使用没有意义。IPC_CREAT和IPC_EXCL一起使用,创建共享内存时,如果底层已经存在,出错返回;如果底层不存在创,建之并返回。
返回值:成功返回一个非负整数即该共享内存的标识码(shmid),和文件描述符类似;失败返回-1
ftok函数
功能:利用给定的pathname和proj_id通过底层的算法来生成key值
参数:
- pathname:给定的一个路径(你必须有这个路径的权限)
- proj_id:有效的8位二进制数
返回值:成功返回key值,失败返回-1
shmat函数
功能:将指定的共享内存挂接到自己的地址空间(也就是建立映射)
参数:
- shmid:共享内存的标识码
- shmaddr:指定挂接的地址,一般设为nullptr,让操作系统自动选择
- shmflg:暂时用不上设置为0
返回值:返回一个指向共享内存的指针,失败返回-1
shmdt函数
功能:将指定的共享内存,从自己的地址空间中去关联
参数:由shmat返回的指针
返回值:成功返回0;失败返回-1
shmctl函数
功能:控制共享内存
参数:
- shmid:共享内存标识码
- cmd:设置为IPC_RMID表示删除共享内存
- buffer:指向一个保存着共享内存的模式状态和访问控制的数据结构,暂时用不到设置为nullptr
返回值:成功返回0,失败返回-1
代码实现共享内存
log.hpp
#pragma once#include <iostream>
#include <string>
#include <ctime>
#include <cstdio>
#include <cstdarg>// 日志级别
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4const char *gLevelMap[] = {"DEBUG","NORMAL","WARNING","ERROR","FATAL"
};#define LOGFILE "./threadpool.log"void logMessage(int level, const char *format, ...)
{//调试小技巧
#ifndef DEBUG_SHOWif (level == DEBUG)return;
#endifchar stdBuffer[1024]; // 标准部分time_t timestamp = time(nullptr);snprintf(stdBuffer, sizeof stdBuffer, "[%s][%ld]", gLevelMap[level], timestamp);char logBuffer[1024]; // 自定义部分va_list args;va_start(args, format);// vprintf(format, args);vsnprintf(logBuffer, sizeof logBuffer, format, args);va_end(args);//写入文件中//FILE* fp = fopen(LOGFILE, "a");//fprintf(fp, "%s%s\n", stdBuffer, logBuffer);//fclose(fp);printf("%s%s\n", stdBuffer, logBuffer);
}
comm.hpp
#pragma once#include <iostream>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "log.hpp"using namespace std;#define PATH_NAME "/home/hjx"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页的整数倍
shmclient.cc
#include "comm.hpp"int main()
{logMessage(NORMAL, "client pid: %d", getpid());//日志信息,可去掉//获取key值key_t key = ftok(PATH_NAME, PROJ_ID);if (key < 0){logMessage(ERROR, "create key fail");exit(1);}logMessage(NORMAL, "create key success, client key: %d", key);// 创建共享内存int shmid = shmget(key, SHM_SIZE, IPC_CREAT); // 0666权限if (shmid < 0){logMessage(ERROR, "create shmget fail");exit(2);}logMessage(NORMAL, "create shmget success, shmid: %d", shmid);// 与地址空间建立关联char *shmaddr = (char *)shmat(shmid, nullptr, 0);if (shmaddr == nullptr){logMessage(ERROR, "create shmat fail");exit(3);}logMessage(NORMAL, "attach shmat success, shmid: %d", shmid);// 写入数据while(true){char buffer[1024];cout << "say >";cin >> buffer;if(strcmp(buffer, "quit") == 0) break;snprintf(shmaddr, SHM_SIZE - 1, "hello server, 我正在和你通信, pid: %d, client say > %s\n", getpid(), buffer);}strcpy(shmaddr, "quit");// 去除与地址空间的关联int n = shmdt(shmaddr);assert(n != -1);(void)n;logMessage(NORMAL, "detach shmdt success, shmid: %d", shmid);return 0;
}
shmserver.cc
#include "comm.hpp"int main()
{// 创建公共的key值key_t key = ftok(PATH_NAME, PROJ_ID);assert(key != -1);logMessage(NORMAL, "create key finish, server key: %d", key);// 创建一个全新的共享内存int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); // 0666权限if (shmid == -1){perror("shmget");exit(1);}logMessage(NORMAL, "create shmid finish, shmid: %d", shmid);// 将指定的共享内存挂接到自己的地址空间(建立映射)char *shmaddr = (char *)shmat(shmid, nullptr, 0);logMessage(NORMAL, "attach shm finish, shmid: %d", shmid);// sleep(10);// 读取数据for (;;){printf("%s\n", shmaddr);if(strcmp(shmaddr, "quit") == 0) break;sleep(1);}// 将指定的共享内存,从自己的地址空间中去关联int n = shmdt(shmaddr);assert(n != -1);(void)n;logMessage(NORMAL, "detach shm finish, shmid: %d", shmid);// 删除共享内存int m = shmctl(shmid, IPC_RMID, nullptr);assert(m != -1);(void)m;logMessage(NORMAL, "create shmid finish, shmid: %d", shmid);return 0;
}
结果演示:
🔥查看共享内存的信息的命令
可以使用ipcs -m命令查看共享内存的信息
- key:我们代码打印出来的key值是十进制,转换为十六进制就是上面的值,key是唯一的可用来标识一块共享内存
- shmid:共享内存的标识码,和key值一样都可以标识一块共享内存
- bytes:这块共享内存的大小,可以把共享内存看做是一个很大的数组或字符串
- nattch:表示有多少个进程和这块地址空间进行了关联
也可以使用ipcrm -m命令删除共享内存
✨结论
- 只要是使用了共享内存进行通信,一个进程向共享内存中写入数据,另一个进程立马就可以看到对方写入的数据,共享内存是所有进程间通信中速度最快的,因为它不经过系统调用也就不需要过多的拷贝
- 在上面的结果演示中,可以看到当我们client端还未跑起来时,我们的server端就已经在那里读取数据了,因此可以得出结论,共享内存缺乏访问控制,并且会带来并发问题,但是如果我们想让它拥有访问控制也是可以的,只需要把它和管道结合起来就行了。
加入管道之后结果演示:
完整代码链接
共享内存