🏆个人主页:企鹅不叫的博客
🌈专栏
- C语言初阶和进阶
- C项目
- Leetcode刷题
- 初阶数据结构与算法
- C++初阶和进阶
- 《深入理解计算机操作系统》
- 《高质量C/C++编程》
- Linux
⭐️ 博主码云gitee链接:代码仓库地址
⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!
💙系列文章💙
【Linux】第一章环境搭建和配置
【Linux】第二章常见指令和权限理解
【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)
【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)
【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())
【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)
【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)
【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)
【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)
文章目录
- 💙系列文章💙
- 💎一、进程通信介绍
- 🏆1.进程通信概念
- 🏆2.进程通信目的
- 🏆3.进程通信本质
- 🏆4.进程通信分类
- 💎二、管道
- 🏆1.概念
- 🏆2.匿名管道
- 匿名管道原理
- pipe函数
- 匿名管道使用
- 匿名管道的特点
- 🏆3.用匿名管道实现派发任务
- 🏆3.命名管道
- 概念
- 创建命名管道
- 使用命名管道通信
- 🏆4.匿名管道和命名管道的区别
- 🏆5.命令行当中的管道
- 💎三、system V共享内存
- 🏆1.共享内存原理
- 🏆2.共享内存创建和释放
- ftok-获取标识符key
- shmget-创建共享内存
- 创建共享内存
- ipcs 命令
- ipcs -m和ipcrm -m shmid
- shmctl**-控制共享内存**
- 释放共享内存
- 🏆3.关联共享内存和去关联
- shmat-共享内存关联进程
- 关联共享内存
- shmdt-取消关联
- 取消共享内存关联
- 🏆4.使用共享内存实现进程通信
💎一、进程通信介绍
🏆1.进程通信概念
进程间通信(IPC,Interprocess communication)是一组编程接口,让不同进程相互传递、交换信息
🏆2.进程通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知某些或某个进程发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
🏆3.进程通信本质
让不同的进程看到同一份资源。各个进程是独立的,进程间通信是借助第三方资源写入或读取数据
🏆4.进程通信分类
管道
- 匿名管道
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
💎二、管道
🏆1.概念
管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个“管道”。特点是单向传输数据。
🏆2.匿名管道
概念:匿名管道用于进程间通信,这两个进程需要具有亲缘关系(父子进程等)
匿名管道原理
进程通信首先让不同的进程看到同一份资源,使用匿名管道的原理是,让父子两个进程看到同一份文件,然后父进程就可以对文件操作读或写,实现父子进程间通信。
- 父进程分别打开读和写,为了让子进程继承,这样子进程不用再打开了
- 父进程要关闭对应的读写,管道必须是单向通信的
- 用户决定关闭父子读写
- 父子进程看到的是同一份文件资源,当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝
pipe函数
#include <unistd> int pipe(int pipefd[2])
功能: 创建一个匿名管道
参数:
pipefd:文件描述符数组,这是一个输出型参数,pipefd[0]表示管道读端文件描述符,fdpipefd1]表示管道写端文件描述符
返回值:
创建管道成功返回0,失败返回-1
匿名管道使用
1.父进程调用pipe函数创建管道
2.fork()生成子进程
3.父进程关闭读端,子进程关闭写端
例如:父进程向匿名管道当中写入5行数据,子进程从匿名管道当中将数据读出
#include <cstdio> #include <iostream> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main() { //1.创建管道 int pipefd[2] = { 0 }; if (pipe(pipefd) < 0){ //使用pipe创建匿名管道 perror("pipe"); return 1; } //2.创建子进程 pid_t id = fork(); //使用fork创建子进程 if (id == 0){ //child close(pipefd[1]); //子进程关闭写端 //子进程读数据 char buff[1024]; while (1){ ssize_t s = read(pipefd[0], buff, sizeof(buff)); //读入成功 if (s > 0){ buff[s] = '\0'; cout<<"子进程向父进程发送"<<buff<<endl; } //读取完成 else if (s == 0){ cout<<"子进程读取完成"<<endl; break; } //读取失败 else{ cout<<"读取错误"<<endl; break; } } close(pipefd[0]); //子进程读取完毕,关闭文件 exit(0); } //father close(pipefd[0]); //父进程关闭读端 //父进程写数据 const char* msg = "hello child, I am father..."; int count = 5; while (count--){ write(pipefd[1], msg, strlen(msg)); sleep(1); } close(pipefd[1]); //父进程写入完毕,关闭文件 cout<<"父进程写入成功"<<endl; exit(0); pid_t ret = waitpid(id, nullptr, 0); if(ret>0) { cout<<"等待子进程退出成功"<<endl; } return 0; }
结果:
[Jungle@VM-20-8-centos:~/lesson25]$ ./mypipe 子进程向父进程发送hello child, I am father... 子进程向父进程发送hello child, I am father... 子进程向父进程发送hello child, I am father... 子进程向父进程发送hello child, I am father... 子进程向父进程发送hello child, I am father... 子进程读取完成 父进程写入成功
匿名管道的特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信,一般是父子进程
- 管道提供流式服务。也就是你想往管道里读写多少数据是根据自身来定的
- 管道是半双工的,数据只能向一个方向流动
🏆3.用匿名管道实现派发任务
父子进程通过管道通信,父进程给管道写入命令,指派给子进程,所以现在建立多个管道,由父进程通过多个管道和多个子进程建立联系
#include <iostream> #include <vector> #include <cstdio> #include <cstring> #include <unordered_map> #include <ctime> #include <cstdlib> #include <sys/wait.h> #include <sys/types.h> #include <unistd.h> #include <cassert> using namespace std; typedef void (*functor)();//函数指针 vector<functor> functors; // 方法集合 // for debug unordered_map<uint32_t, string> info; // int32_t: 进程pid, int32_t: 该进程对应的管道写端fd typedef std::pair<int32_t, int32_t> elem; int processNum = 3;//子进程数量 //三个任务 void f1() { cout << "这是一个处理日志的任务, 执行的进程 ID [" << getpid() << "]" << "执行时间是[" << time(nullptr) << "]\n" << endl; // } void f2() { cout << "这是一个备份数据任务, 执行的进程 ID [" << getpid() << "]" << "执行时间是[" << time(nullptr) << "]\n" << endl; } void f3() { cout << "这是一个处理网络连接的任务, 执行的进程 ID [" << getpid() << "]" << "执行时间是[" << time(nullptr) << "]\n" << endl; } void loadFunctor() { info.insert({functors.size(), "处理日志的任务"}); functors.push_back(f1); info.insert({functors.size(), "备份数据任务"}); functors.push_back(f2); info.insert({functors.size(), "处理网络连接的任务"}); functors.push_back(f3); } //子进程工作-读 void work(int blockFd) { cout << "进程[" << getpid() << "]" << " 开始工作" << endl; // 子进程核心工作的代码 while (true) { // a.阻塞等待 b. 获取任务信息 uint32_t operatorCode = 0; //如果有数据就读取,没有就阻塞等待 ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t)); if(s == 0) break;//子进程结束 // c. 处理任务,如果数组访问没有越界 if(operatorCode < functors.size()) functors[operatorCode]();//函数指针 } cout << "进程[" << getpid() << "]" << " 结束工作" << endl; } // [子进程的pid, 子进程的管道fd] void blanceSendTask(const vector<elem> &processFds) { srand((long long)time(nullptr)); while(true) { sleep(1); // 选择一个进程, 选择进程是随机的,没有压着一个进程给任务 // 较为均匀的将任务给所有的子进程 --- 负载均衡 uint32_t pick = rand() % processFds.size(); // 选择一个任务 uint32_t task = rand() % functors.size(); // 把任务给一个指定的进程 write(processFds[pick].second, &task, sizeof(task)); // 打印对应的提示信息 cout << "父进程指派任务->" << info[task] << "给进程: " << processFds[pick].first << " 编号: " << pick << endl; } } int main() { //加载任务列表 loadFunctor(); vector<elem> assignMap; // 创建processNum个进程 for (int i = 0; i < processNum; i++) { // 定义保存管道fd的对象 int pipefd[2] = {0}; // 创建管道 pipe(pipefd); // 创建子进程 pid_t id = fork(); //子进程 if (id == 0) { // 子进程读取, r -> pipefd[0] close(pipefd[1]); // 子进程执行 work(pipefd[0]); close(pipefd[0]); exit(0); } //父进程 else { //父进程做的事情, pipefd[1] close(pipefd[0]); elem e(id, pipefd[1]); assignMap.push_back(e); } } cout << "create all process success!" << std::endl; // 父进程, 派发任务 blanceSendTask(assignMap); // 回收资源 for (int i = 0; i < processNum; i++) { //assignMap[i]对应的子进程,first-进程PID if (waitpid(assignMap[i].first, nullptr, 0) > 0)//成功 cout << "wait for: pid=" << assignMap[i].first << " wait success!" << "number: " << i << "\n"; close(assignMap[i].second); } }
结果:父进程通过管道控制三个子进程,给子进程派发任务
[Jungle@VM-20-8-centos:~/lesson26]$ ./pipe create all process success! 进程[9453] 开始工作 进程[9452] 开始工作 进程[9454] 开始工作 父进程指派任务->处理日志的任务给进程: 9452 编号: 0 这是一个处理日志的任务, 执行的进程 ID [9452]执行时间是[1667614684] 父进程指派任务->备份数据任务给进程: 9453 编号: 1 这是一个备份数据任务, 执行的进程 ID [9453]执行时间是[1667614685] 父进程指派任务->处理日志的任务给进程: 9453 编号: 1 这是一个处理日志的任务, 执行的进程 ID [9453]执行时间是[1667614686] 父进程指派任务->备份数据任务给进程: 9452 编号: 0 这是一个备份数据任务, 执行的进程 ID [9452]执行时间是[1667614687] 父进程指派任务->处理网络连接的任务给进程: 9454 编号: 2 这是一个处理网络连接的任务, 执行的进程 ID [9454]执行时间是[1667614688] c父进程指派任务->备份数据任务给进程: 9453 编号: 1 这是一个备份数据任务, 执行的进程 ID [9453]执行时间是[1667614689]
🏆3.命名管道
概念
命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了
创建命名管道
函数创建
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
功能: 创建一个命名管道
参数:
pathname: 管道名称,默认创建在当前路径下
mode: 权限,一般设置0666,记得设置umask(文件默认掩码)
返回值: 创建成功返回0,失败返回-1命令行创建
[Jungle@VM-20-8-centos:~/lesson25]$ mkfifo fifo [Jungle@VM-20-8-centos:~/lesson25]$ ll prw-r--r-- 1 Jungle root 0 11月 3 15:44 fifo
使用命名管道通信
创建两个文件,server.c,client.c,用两个进程来模拟客户端和服务端进行通信,客户端往管道发消息,服务端读消息,共用头文件comm
comm.h
头文件当中提供这个共用的命名管道文件的文件名,客户端和服务器打开同一个命名管道文件,进而进行通信了
#pragma once #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <cerrno> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define IPC_PATH "./.fifo"//让客户端和服务端使用同一个命名管道 using namespace std;
server.c
//读 #include "comm.h" int main () { umask(0); //将文件默认掩码设置为0 if(mkfifo(IPC_PATH, 0600) != 0)//使用mkfifo创建命名管道文件 { perror(".fifo"); return 1; } //打开文件 int pipeFd = open(IPC_PATH, O_RDONLY); if(pipeFd < 0) { cerr << "open fifo error" << endl; return 2; } //通信过程 char buff[1024]; while(1) { //以读的方式打开命名管道文件 ssize_t s = read(pipeFd, buff, sizeof(buff)-1); //正常通信 if(s>0) { buff[s] = '\0';//预留\0 方便输出 cout<<"client:"<< buff<<endl; } //客户端停止 else if(s==0) { cout<<"client quit"<<endl; break; } //出错 else { cout<<"read error"<<endl; break; } } //关闭文件 close(pipeFd); cout<<"server quit"<<endl; }
client.c
因为服务端运行起来后命名管道文件就已经被创建了,所以客户端只需以写的方式打开该命名管道文件,之后客户端就可以将通信信息写入到命名管道文件当中,进而实现和服务端的通信
//写 #include "comm.h" int main () { //以写的方式打开命名管道文件 int pipeFd = open(IPC_PATH, O_WRONLY); if(pipeFd < 0) { cerr << "open: " << strerror(errno) << endl; return 1; } //通信过程 char buff[1024]; while(1) { cout<<"Please Enter:";//提示客户端输入 fflush(stdout); //从客户端的标准输入流读取信息 ssize_t s = read(0, buff, sizeof(buff)-1); if (s > 0){ buff[s - 1] = '\0'; //将信息写入命名管道 write(pipeFd, buff, strlen(buff)); } } //关闭文件 close(pipeFd); cout<<"client quit"<<endl; }
运行:
//服务器端接收 [Jungle@VM-20-8-centos:~/lesson26]$ ./server client:123 client:qwer client:你好呀 //客户端输入 [Jungle@VM-20-8-centos:~/lesson26]$ ./client Please Enter:123 Please Enter:qwer Please Enter:你好呀
🏆4.匿名管道和命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同
🏆5.命令行当中的管道
利用管道“|”同时使用cat命令和grep命令
下面同时实现打开file.txt文件和只选取带有123的数据
[Jungle@VM-20-8-centos:~/lesson26]$ cat file.txt hello 1 hello 12 hello 123 [Jungle@VM-20-8-centos:~/lesson26]$ cat file.txt | grep 123 hello 123
在命令行当中的管道“|”是**匿名管道**,有亲缘关系的进程,他们之间的进程都是同一个父进程
💎三、system V共享内存
🏆1.共享内存原理
共享内存是在物理内存上申请一块空间,再让两个进程各自在页表建立虚拟地址和这块空间的映射关系。这样两个进程看到的就是同一份资源,这一份资源就叫做共享内存。匿名管道,约定使用同一个管道,共享内存,约定使用同一个唯一的Key
共享内存数据结构
struct shmid_ds { struct ipc_perm shm_perm; /* operation perms */ int shm_segsz; /* size of segment (bytes) */ __kernel_time_t shm_atime; /* last attach time */ __kernel_time_t shm_dtime; /* last detach time */ __kernel_time_t shm_ctime; /* last change time */ __kernel_ipc_pid_t shm_cpid; /* pid of creator */ __kernel_ipc_pid_t shm_lpid; /* pid of last operator */ unsigned short shm_nattch; /* no. of current attaches */ unsigned short shm_unused; /* compatibility */ void *shm_unused2; /* ditto - used by DIPC */ void *shm_unused3; /* unused */ };
为了让要实现通信的进程能够看到同一个共享内存,我们通过一个Key值,标识共享内存,而这个key值用于标识系统中共享内存的唯一性,通过Key我们可以知道对应的共享内存是否存在
🏆2.共享内存创建和释放
ftok-获取标识符key
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
功能: 获取一个共享内存的唯一标识符key(IPC键值)
函数参数:
- pathname:可以传入任何文件名,必须存在且可存取
- proj_id:只有是一个非0的数都可以
返回值:
成功返回key值,失败返回-1
shmget-创建共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
功能: 创建共享内存
函数参数:
- key:共享内存在系统当中的唯一标识
- size:共享内存的大小(页(4kb)的整数倍)
- shmflg:权限,由9个权限标准构成
这里介绍两个选项
- IPC_CREAT: 如果底层存在这个标识符的共享内存空间,就返回标识符,不存在就创建
- IPC_EXCL: 如果底层存在这个标识符的共享内存空间,就出错返回,不存在就创建
- 使用组合IPC_CREAT,一定会获得一个共享内存的标识符,但无法确认该共享内存是否是新建的共享内存。
- 使用组合IPC_CREAT | IPC_EXCL,只有shmget函数调用成功时才会获得共享内存的标识符,并且该共享内存一定是新建的共享内存。
两个选项合起来用就可以穿甲一个权限的共享内存空间
返回值:
成功返回共享内存标识码符(给用户看的),失败返回-1
创建共享内存
使用ftok和shmget函数创建一块共享内存
#include <iostream> #include <cstring> #include <cstdlib> #include <cerrno> #include <cassert> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/stat.h> #include <fcntl.h> #define PATH_NAME "/home/Jungle/lesson27"//路径 #define PROJ_ID 0x666//ID #define MEM_SIZE 4096//共享内存大小 using namespace std; int main() { // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的) key_t key = ftok(PATH_NAME, PROJ_ID); if (key < 0){ perror("ftok"); return 1; } cout<<"key:"<< key <<endl; // 创建内存空间 // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建 // IPC_EXCL 如果底层共享内存已经存在就出错返回 // 结合使用可以创建一个全新的共享内存 int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存 if (shm < 0){ perror("shmget"); return 2; } cout<<"shm:"<<shm<<endl; return 0; }
结果:
[Jungle@VM-20-8-centos:~/lesson27]$ ./test key:1711351259 shm:39
ipcs 命令
- -q:列出消息队列相关信息。
- -m:列出共享内存相关信息。
- -s:列出信号量相关信息。
标题 含义 key 共享内存唯一标识符 shmid 共享内存的用户层id owner 共享内存的拥有者 perms 共享内存的权限 bytes 共享内存的大小 nattch 共享内存的进程数 status 共享内存的状态
ipcs -m和ipcrm -m shmid
指令
ipcs -m
查看ipc资源[Jungle@VM-20-8-centos:~/lesson27]$ ipcs -m ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态 0x660125db 39 Jungle 0 4096 0
ipcrm -m shmid
删除共享内存,IPC的声明周期随内核[Jungle@VM-20-8-centos:~/lesson27]$ ipcrm -m 39 [Jungle@VM-20-8-centos:~/lesson27]$ ipcs -m ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态
shmctl**-控制共享内存**
#include <sys/types.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能: 控制共享内存
参数:
- shmid:共享内存标识符
- cmd:具体的控制动作
- buf:获取或设置所控制共享内存的数据结构
第二个参数传入的选项:
IPC_STAT: 获取共享内存的当前关联值
IPC_SET: 将共享内存的当前关联值设置为buf所指的数据结构中的值
IPC_RMID:删除共享内存段返回值: 成功返回0,失败返回-1
释放共享内存
释放共享内存,将上面test.cpp文件修改为以下代码,创建共享内存10s后释放共享内存
#include <iostream> #include <cstring> #include <cstdlib> #include <cerrno> #include <cassert> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/stat.h> #include <fcntl.h> #define PATH_NAME "/home/Jungle/lesson27"//路径 #define PROJ_ID 0x666//ID #define MEM_SIZE 4096//共享内存大小 using namespace std; int main() { // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的) key_t key = ftok(PATH_NAME, PROJ_ID); if (key < 0){ perror("ftok"); return 1; } cout<<"key:"<< key <<endl; // 创建内存空间 // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建 // IPC_EXCL 如果底层共享内存已经存在就出错返回 // 结合使用可以创建一个全新的共享内存 int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存 if (shm < 0){ perror("shmget"); return 2; } cout<<"shm:"<<shm<<endl; sleep(10); shmctl(shm, IPC_RMID, NULL); //释放共享内存 sleep(2); return 0; }
命令行监控脚本:
while :; do ipcs -m; echo "#####################"; sleep 1;done;
结果:
##################### ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态 0x660125db 40 Jungle 0 4096 0 ##################### ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态
🏆3.关联共享内存和去关联
shmat-共享内存关联进程
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
功能: 将共享内存空间关联到进程地址空间
参数:
- shmid:共享内存标识符
- shmaddr:定共享内存映射到进程地址空间的某一地址,一般取nullptr。
- shmfig:表示关联共享内存时设置的某些属性
返回值: 成功返回一个指针(虚拟地址空间中共享内存的地址,是一个虚拟地址),失败返回-1
说明:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整> 数倍。公式:shmaddr -(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
关联共享内存
char *str = (char *)shmat(shm, nullptr, 0);//关联共享内存,记得强转
设置权限
int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存
结果:关联该共享内存的进程数由0变成了1,共享内存的权限显示是我们设置的666权限
------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态 0x660125db 40 Jungle 666 4096 1
shmdt-取消关联
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
功能: 取消共享内存空间和进程地址空间的关联
参数:
shmaddr:共享内存的起始地址(shmat获取的指针)
返回值: 成功返回0,失败返回-1
取消共享内存关联
将进程和这块共享内存关联起来,取消关联共享内存空间
char *str = (char*)shmat(shm, nullptr, 0);//关联共享内存 cout<<"attach shm : " << shm << " success\n"; shmdt(str);//取消关联 cout << "detach shm : " << shm << " success\n";
结果:共享内存的关联数由1变为0的过程,即取消了共享内存与该进程之间的关联
------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态 0x660125db 40 Jungle 666 4096 1 ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态 0x660125db 40 Jungle 666 4096 0
🏆4.使用共享内存实现进程通信
client.c和server.c两个文件,还有一个comm.h一个头文件,公共的pathname和proj_id,两个进程就可以得到相同的共享内存唯一标识符,使用服务端创建共享内存,然后连接到共享内存,让客户端也连接上这块共享内存,客户端写数据,服务端不断读
comm.h
#pragma once #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cerrno> #include <cassert> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/stat.h> #include <fcntl.h> #define PATH_NAME "/home/Jungle/lesson27" #define PROJ_ID 0x666 #define MEM_SIZE 4096 #define FIFO_FILE ".fifo"//文件名 #define READER O_RDONLY #define WRITER O_WRONLY using namespace std; key_t CreateKey() { // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的) key_t key = ftok(PATH_NAME, PROJ_ID); if (key < 0){ perror("ftok"); exit(1); } return key ; } void CreateFifo() { umask(0);//默认遮掩码 if(mkfifo(FIFO_FILE, 0666) < 0)//创建失败 { perror("mkfifo"); exit(2); } } //选择读或写 int Open(const string &filename, int flags) { return open(filename.c_str(), flags); } int Wait(int fd) { uint32_t values = 0; ssize_t s = read(fd, &values, sizeof(values)); return s; } //信号 int Signal(int fd) { uint32_t cmd = 1; write(fd, &cmd, sizeof(cmd)); } //关闭文件描述符 int Close(int fd, const string filename) { close(fd); unlink(filename.c_str());//删除指定文件 }
server.c
#include "comm.h" using namespace std; int main() { CreateFifo();//创建管道 int fd = Open(FIFO_FILE, READER);//打开文件 // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的) key_t key = CreateKey(); cout<<"key:"<< key <<endl; // 创建内存空间 // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建 // IPC_EXCL 如果底层共享内存已经存在就出错返回 // 结合使用可以创建一个全新的共享内存 int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存 if (shm < 0){ perror("shmget"); return 2; } //关联共享内存 char *str = (char*)shmat(shm, nullptr, 0); //cout<<"attach shm : " << shm << " success"<<endl; // 服务端每隔一秒在显示器上刷新共享内存段中的数据 while(1) { // 让读端进行等待 if(Wait(fd) <= 0) break; printf("%s\n",str); sleep(1); }; //取消关联 shmdt(str); //cout << "detach shm : " << shm << " success"<<endl;; //释放共享内存 shmctl(shm, IPC_RMID, nullptr); //cout<< "delete shm : " << shm << " success" << endl;; //关闭文件描述符 Close(fd, FIFO_FILE); return 0; }
client.c
#include "comm.h" #include <unistd.h> using namespace std; int main() { int fd = Open(FIFO_FILE, WRITER);//写入数据 key_t key = CreateKey(); //cout<<"key:"<< key <<endl; // 创建内存空间 // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建 // IPC_EXCL 如果底层共享内存已经存在就出错返回 // 结合使用可以创建一个全新的共享内存 int shm = shmget(key, MEM_SIZE, IPC_CREAT); //创建新的共享内存 if (shm < 0){ perror("shmget"); return 2; } //关联共享内存 char *str = (char*)shmat(shm, nullptr, 0); // 客户端在共享内存写数据 while(1) { printf("Please Enter: "); fflush(stdout); ssize_t s = read(0, str, MEM_SIZE); if(s > 0) { str[s] = '\0'; } Signal(fd);//写完数据就通知 } //取消关联 shmdt(str); return 0; }
结论: 共享内存底层不提供任何同步与互斥的机制,共享内存是所有进程中速度最快的