linux笔记归纳9:进程间通信 进程间通信目录进程间通信一、进程间通信理解1.1.进程间通信的概念1.2.进程间通信的目的1.3.进程间通信的方式二、管道三、匿名管道3.1.匿名管道的概念3.2.pipe函数3.3.匿名管道的理解3.4.实例代码3.5.匿名管道的特性3.6.四种通信情况四、进程池4.1.管道的容量4.2.管道写入的原子性4.3.基于管道的进程池4.4.隐藏BUG五、命名管道5.1.mkfifo命令5.2.mkfifo函数5.3.命名管道 VS 匿名管道5.4.命名管道通信六、System V共享内存6.1.共享内存的原理6.2.共享内存的接口6.2.1.shmget函数6.2.2.ftok函数6.2.3.ipcs命令6.2.4.ipcrm命令6.2.5.shmctl函数6.2.6.shmat函数6.2.7.shmdt函数6.3.共享内存的实现七、System V消息队列7.1.消息队列的原理7.2.消息队列的接口7.2.1.msgget函数7.2.2.msgctl函数7.2.3.ftok函数7.2.4.msgsnd函数7.2.5.msgrcv函数7.2.6.ipcs命令7.2.6.ipcrm命令八、System V信号量8.1.关于并发编程的一些概念8.2.信号量的理解8.3.信号量的接口8.3.1.semget函数8.3.2.ftok函数8.3.3.ipcs命令8.3.4.ipcrm命令8.3.5.semctl函数8.3.6.semop函数九、内核IPC资源的组织管理一、进程间通信理解1.1.进程间通信的概念进程间通信Inter Process Communication多个进程间进行数据交换和通信的技术进程间通信的本质让不同的进程先看到同一份资源内存不能由进程申请而是由OS提供开发者会设计一套统一的通信接口1.2.进程间通信的目的多个进程之间需要数据传输多个进程之间需要共享资源多个进程之间需要通知事件多个进程之间需要进程控制1.3.进程间通信的方式基于文件的管道通信System V进程间通信POSIX进程间通信二、管道基于已有的技术通过文件直接进行通信将文件缓冲区的数据刷新给另一个进程管道从一个进程连接到另一个进程的一个数据流Unix早期进程间通信的形式三、匿名管道3.1.匿名管道的概念匿名管道内存级文件不需要文件路径也没有文件名常用作父子通信子进程通过继承父进程的文件描述符表来确认打开的是同一个管道文件3.2.pipe函数功能创建匿名管道int pipe(int fd[2]); 参数 fd⽂件描述符数组 fd[0]:表示读端 fd[1]:表示写端 返回值:成功返回0失败返回错误代码3.3.匿名管道的理解3.4.实例代码从键盘输入数据写入管道读取管道输出到屏幕实验代码#include iostream #include unistd.h #include sys/types.h #include sys/wait.h #include cstdio #include string.h //子进程写 void ChildWrite(int wfd) { char buffer[1024]; int cnt 0; while(true) { //将字符串内容写入数组 snprintf(buffer,sizeof(buffer),I am child,pid: %d,cnt: %d,getpid(),cnt); //向文件写入时不用写\0 write(wfd,buffer,strlen(buffer)); sleep(1); } } //父进程读 void FatherRead(int rfd) { char buffer[1024]; while(true) { buffer[0] 0; //将文件内容读取到数组 ssize_t n read(rfd,buffer,sizeof(buffer) - 1); if(n 0) { //读取文件内容时要加\0 buffer[n] 0; std::cout child say: buffer std::endl; } } } int main() { //1. 创建管道 int fds[2] {0};//fds[0]:读 fds[1]:写 int n pipe(fds); if(n 0) { std::cerr pipe error std::endl; return 1; } std::cout fds[0]: fds[0] std::endl; std::cout fds[1]: fds[1] std::endl; //2. 创建子进程 pid_t id fork(); if(id 0) { //关闭子进程读 close(fds[0]); //子进程写入 ChildWrite(fds[1]); //关闭子进程写 close(fds[1]); exit(0); } //3. 形成通信管道父进程读子进程写 //关闭父进程写 close(fds[1]); //父进程读取 FatherRead(fds[0]); //回收子进程 waitpid(id,nullptr,0); //关闭父进程读 close(fds[0]); return 0; }实验现象3.5.匿名管道的特性特性一只能是具有血缘关系的进程间通信特性二管道文件进行IO操作具有同步机制特性三管道是面向字节流的怎么读与怎么写没有必然关系子进程写的快父进程读的慢子进程写的慢父进程读的快特性四管道是单向通信的子进程写数据父进程读数据属于半双工半双工任何一个时刻一方发送一方接收全双工任何一个时刻双方可以同时收发特性五管道文件的生命周期由进程决定3.6.四种通信情况写慢读快读端进程阻塞等待写端进程写入写快读慢写满后写端进程阻塞等待读端写关读开read的返回值0表示文件结束写开读关写端写入无意义浪费内存空间OS发送异常信号杀掉写端进程实验代码#include iostream #include unistd.h #include sys/types.h #include sys/wait.h #include cstdio #include string.h //子进程写 void ChildWrite(int wfd) { char buffer[1024]; int cnt 0; while(true) { //将字符串内容写入数组 snprintf(buffer,sizeof(buffer),I am child,pid: %d,cnt: %d,getpid(),cnt); //向文件写入时不用写\0 write(wfd,buffer,strlen(buffer)); printf(child: %d\n,cnt); sleep(2); } } //父进程读 void FatherRead(int rfd) { char buffer[1024]; while(true) { buffer[0] 0; ssize_t n read(rfd,buffer,sizeof(buffer) - 1); if(n 0) { //读取文件内容时要加\0 buffer[n] 0; std::cout child say: buffer std::endl; } else if(n 0) { std::cout n : n std::endl; std::cout child 退出 father也退出 std::endl; break; } else { break; } break; } } int main() { //1. 创建管道 int fds[2] {0};//fds[0]:读 fds[1]:写 int n pipe(fds); if(n 0) { std::cerr pipe error std::endl; return 1; } std::cout fds[0]: fds[0] std::endl; std::cout fds[1]: fds[1] std::endl; //2. 创建子进程 pid_t id fork(); if(id 0) { //关闭子进程读 close(fds[0]); //子进程写入 ChildWrite(fds[1]); //关闭子进程写 close(fds[1]); exit(0); } //3. 形成通信管道父进程读子进程写 //关闭父进程写 close(fds[1]); //父进程读取 FatherRead(fds[0]); //关闭父进程读 close(fds[0]); sleep(5); //回收子进程 int status 0; int ret waitpid(id,status,0); if(ret 0) { printf(exit code : %d,exit signal : %d\n,(status 8) 0xff,status 0x7f); sleep(5); } return 0; }实验现象四、进程池4.1.管道的容量64KB65536 / 10244.2.管道写入的原子性单次写入数据长度≤PIPE_BUF4KBOS保证原子写入单次写入数据长度PIPE_BUF4KB为非原子写入原子写入多进程同时向同一个管道写数据时单次写入的数据不会出现穿插和拆分示例进程A写aaa进程B写“bbb”读端只会读到“aaabbb”或bbbaaa不会aababba4.3.基于管道的进程池进程池父进程通过管道暂停和唤醒子进程发送任务码让子进程执行相应任务ProcessPool.hpp//.hpp文件头文件与源文件没有分离 //.cxx文件与.cc和.cpp一样都是C源文件的后缀 //法一#pragma once //法二条件编译 #ifndef __PROCESS_POOL_HPP__ #define __PROCESS_POOL_HPP__ //头文件 #include iostream #include vector #include unistd.h #include cstdlib #include sys/wait.h #include Task.hpp //管道类先描述 class Channel { public: //构造 Channel(int fd, pid_t id) :_wfd(fd) ,_subid(id) { _name channel- std::to_string(_wfd) - std::to_string(_subid);//整型转字符串 } //发送任务码 void Send(int code) { int n write(_wfd, code, sizeof(code)); (void)n;//避免编译报警对于未使用变量的检查 } //关闭写文件描述符 void Close() { close(_wfd); } //回收子进程 void Wait() { pid_t rid waitpid(_subid,nullptr,0); (void)rid; } //析构 ~Channel(){} //Get方法 int Fd() { return _wfd; } pid_t SubId() { return _subid; } std::string Name() { return _name; } private: int _wfd;//写文件描述符 int _subid;//子进程pid std::string _name;//管道名字 }; //管道管理类再组织 class ChannelManager { public: //构造 ChannelManager() :_next(0) {} //构建管道 void Insert(int wfd, pid_t subid) { //Channel c(wfd,subid); //_channels.push_back(std::move(c)); _channels.emplace_back(wfd,subid); } //选择管道 Channel Select() { auto c _channels[_next]; _next; _next % _channels.size(); return c; } //打印管道 void PrintChannel() { for(auto channel : _channels) { std::cout channel.Name() std::endl; } } //结束子进程 void StopSubProcess() { for(auto channel : _channels) { channel.Close(); std::cout 关闭: channel.Name() std::endl; } } //回收子进程 void WaitSubProcess() { for(auto channel : _channels) { channel.Wait(); std::cout 回收: channel.Name() std::endl; } } //析构 ~ChannelManager(){} private: std::vectorChannel _channels;//管道管理数组 int _next; }; //构建管道数目 const int gdefaultnum 5; //进程池类 class ProcessPool { public: //构造 ProcessPool(int num) :_process_num(num) { //注册任务 _tm.Register(Printlog); _tm.Register(Download); _tm.Register(Upload); } //子进程工作 void Work(int rfd) { while(true) { int code 0; ssize_t n read(rfd, code, sizeof(code)); if(n 0) { //如果读取任务码错误 if(n ! sizeof(code)) { //继续读取 continue; } std::cout 子进程[ getpid() ]收到一个任务码: code std::endl; //子进程执行任务 _tm.Execute(code); } else if(n 0) { std::cout 子进程退出 std::endl; break; } else { std::cout 读取错误 std::endl; break; } } } //启动进程池 bool Start() { for(int i 0; i _process_num; i) { //创建管道文件父写子读 int pipefd[2] {0}; int n pipe(pipefd); if(n 0) { return false; } //创建子进程 pid_t subid fork(); if(subid 0) { return false; } else if(subid 0) { //关闭子进程写 close(pipefd[1]); //子进程工作 Work(pipefd[0]); //关闭子进程读 close(pipefd[0]); //子进程退出 exit(0); } else { //关闭父进程读 close(pipefd[0]); //构建管道 _cm.Insert(pipefd[1],subid); } } return true; } //调试 void Debug() { _cm.PrintChannel(); } //运行进程池 void Run() { //1. 选择一个任务 int task_code _tm.Code(); //2. 选择一个管道/子进程负载均衡地选择子进程完成任务 //方法一轮询✔ //方法二随机 //方法三channel添加负载指标 auto c _cm.Select(); std::cout 选择了一个子进程: c.Name() std::endl; //3. 发送任务 c.Send(task_code); std::cout 发送了一个任务码: task_code std::endl; } //结束进程池 void Stop() { //关闭父进程所有的写文件描述符 _cm.StopSubProcess(); //回收所有的子进程 _cm.WaitSubProcess(); } //析构 ~ProcessPool(){} private: ChannelManager _cm;//管道管理变量 TaskManager _tm;//任务管理变量 int _process_num;//进程个数 }; #endifTask.hpp#pragma once #include iostream #include vector #include time.h typedef void (*task_t)(); /////////////////////debug////////////////////////// void Printlog() { std::cout 我是一个打印日志的任务 std::endl; } void Download() { std::cout 我是一个下载的任务 std::endl; } void Upload() { std::cout 我是一个上传的任务 std::endl; } /////////////////////debug////////////////////////// class TaskManager { public: //构造 TaskManager() { srand(time(nullptr)); } //注册任务 void Register(task_t t) { _tasks.push_back(t); } //生成任务码 int Code() { return rand() % _tasks.size(); } //执行任务 void Execute(int code) { if(code 0 code _tasks.size()) { _tasks[code](); } } //析构 ~TaskManager() {} private: std::vectortask_t _tasks; };Main.cc#include ProcessPool.hpp int main() { //1. 创建进程池类型对象 ProcessPool pp(gdefaultnum); //2. 启动进程池 pp.Start(); //3. 自动派发任务 int cnt 10; while(cnt--) { //4. 运行进程池 pp.Run(); sleep(1); } //5. 回收进程池 pp.Stop(); sleep(1000); return 0; }Makefileprocess_pool:Main.cc g -o $ $^ -stdc11 .PHONY:clean clean: rm -f process_pool实验现象4.4.隐藏BUG方案一倒着关闭写端最后一个进程只有父进程的一个写端关闭后可以关闭该进程指向其他进程管道的写端class ChannelManager { public: //...... void CloseAndWait() { for(int i _channels.size() - 1; i 0; i--) { _channels[i].Close(); std::cout 关闭: _channels[i].Name() std::endl; channels[i].Wait(); std::cout 回收: _channels[i].Name() std::endl; } } //...... }方案二让父进程单独指向每个管道的写端通过管道管理数组将子进程继承下来的堂兄进程写段全都关闭class ChannelManager { public: //...... void CloseAll() { for(auto channel : _channels) { channel.Close(); } } void CloseAndWait() { for(auto channel : channels) { _channels[i].Close(); std::cout 关闭: _channels[i].Name() std::endl; channels[i].Wait(); std::cout 回收: _channels[i].Name() std::endl; } } //...... } class ProcessPool { public: //...... bool Start() { for(int i 0; i _process_num ; i) { //创建管道文件(父写子读) int pipefd[2] {0}; int n pipe(pipefd); if(n 0) { return false; } //创建子进程 pid_t subid fork(); if(subid 0) { return false; } else if(subid 0) { _cm.CloseAll(); //关闭子进程写 close(pipefd[1]); //子进程工作 Work(pipefd[0]); //关闭子进程读 close(pipefd[0]); //子进程退出 exit(0); } else { //关闭父进程读 close(pipefd[0]); //构建管道 _cm.Insert(pipefd[1],subid); } } return true; } //...... }五、命名管道匿名管道只能是具有血缘关系的进程进行进程间通信命名管道可以在不相关的进程直接交互数据命名管道文件只会被打开不会被刷新到磁盘5.1.mkfifo命令mkfifo命令创建命名管道unlink命令删除文件5.2.mkfifo函数mkfifo函数创建命名管道unlink函数删除文件5.3.命名管道 VS 匿名管道匿名管道由pipe函数创建并打开命名管道由mkfifo函数创建由open函数打开唯一的区别在于创建和打开方式不同5.4.命名管道通信comm.hpp​ #pragma once #include iostream #include string #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #define PATH . #define FILENAME fifo // 命名管道类 class NamedIpcFifo { public: NamedIpcFifo(const std::string path, const std::string name) : _path(path), _name(name) { _fifoname _path / _name; // 创建管道文件 umask(0); // 设置权限掩码 int n mkfifo(_fifoname.c_str(), 0666); if (n 0) { std::cerr mkdir fifo error std::endl; } else { std::cout mkfifo success std::endl; } } ~NamedIpcFifo() { // 删除管道文件 int n unlink(_fifoname.c_str()); if (n 0) { std::cout remove fifo success std::endl; } else { std::cout remove fifo failed std::endl; } } private: std::string _path; // 管道所在目录路径 std::string _name; // 管道文件名称 std::string _fifoname; // 管道完整路径 }; // 文件操作类 class FileOper { public: FileOper(const std::string path, const std::string name) : _path(path), _name(name), _fd(-1) { _fifoname _path / _name; } // 打开文件读 void OpenForRead() { // 写入方没有执行open的时候读取方就要在open内部阻塞 // 直到有人把管道文件打开了open才会返回 _fd open(FILENAME, O_RDONLY); if (_fd 0) { std::cerr open fifo error std::endl; return; } std::cout open fifo success std::endl; } // 打开文件写 void OpenForWrite() { _fd open(FILENAME, O_WRONLY); if (_fd 0) { std::cerr open fifo error std::endl; return; } std::cout open fifo success std::endl; } // 写文件 void Write() { std::string message; int cnt 1; pid_t id getpid(); while (true) { std::cout Please Enter# ; std::getline(std::cin, message); message (, message number: std::to_string(cnt) [ std::to_string(id) ]); write(_fd, message.c_str(), message.size()); } } // 读文件 void Read() { while (true) { char buffer[1024]; int number read(_fd, buffer, sizeof(buffer) - 1); if (number 0) { buffer[number] 0; std::cout Client Say# buffer std::endl; } else if (number 0) { std::cout client quit! me too! std::endl; break; } else { std::cout read error std::endl; break; } } } // 关闭文件 void Close() { if(_fd 0) { close(_fd); } } ~FileOper() {} private: std::string _path; // 管道所在目录路径 std::string _name; // 管道文件名称 std::string _fifoname; // 管道完整路径 int _fd; // 管道文件描述符 }; ​server.cc#include comm.hpp int main() { // 在当前目录下创建管道文件 NamedIpcFifo fifo(PATH, FILENAME); // 文件操作 FileOper readerfile(PATH, FILENAME); readerfile.OpenForRead(); readerfile.Read(); readerfile.Close(); return 0; }client.cc#include comm.hpp int main() { // 文件操作 FileOper writerfile(PATH, FILENAME); writerfile.OpenForWrite(); writerfile.Write(); writerfile.Close(); return 0; }Makefile.PHONY:all all:client server client:client.cc g -o $ $^ -stdc11 server:server.cc g -o $ $^ -stdc11 .PHONY:clean clean: rm -f client server六、System V共享内存System V一种模块标准是三种进程间通信机制的总称IPC本质让不同的进程看到同一份资源6.1.共享内存的原理多组进程使用不同的共享内存进行通信多个共享内存同时存在就需要进行管理先描述再组织共享内存由描述自己的结构体对象与物理内存进程与共享内存的关系就是内核数据结构关系6.2.共享内存的接口6.2.1.shmget函数作用在系统中创建共享内存参数1key值不是由内核形成而是在用户层构建并传入OS参数2共享内存大小必须是4KB4096的整数倍否则会向上取整参数3设置共享内存标志位设置共享内存的权限IPC_CREAT获取如果目标共享内存不存在就创建否则打开这个存在的共享内存并返回(IPC_CREAT |) IPC_EXCL创建单独使用无意义如果目标共享内存存在就出错返回key值类似命名管道约定同一个管道文件保证共享内存存在标识共享内存的唯一性返回值创建成功合法的共享内存标识符类似文件描述符创建失败-16.2.2.ftok函数作用将路径名与id整数的字符混合通过算法构建一个key值减少key值冲突6.2.3.ipcs命令ipcs -m查看共享内存6.2.4.ipcrm命令ipcrm -m shmid删除共享内存用户层使用shmid管理共享内存key只用来给内核区分唯一性6.2.5.shmctl函数作用控制共享内存删除共享内存参数1共享内存标识符参数2控制命令IPC_RMID删除共享内存IPC_STAT拷贝共享内存属性参数3共享内存属性表key值会被设置到共享内存的描述结构体中6.2.6.shmat函数作用将共享内存挂接attach到进程的虚拟地址空间中返回值挂接成功虚拟空间的起始虚拟地址挂接失败-1参数1共享内存标识符参数2采用固定地址挂接不需要传NULL参数3共享内存标志位传0使用默认设置6.2.7.shmdt函数作用将共享内存与虚拟内存去关联减少共享内存结构体的引用计数6.3.共享内存的实现读写共享内存没有使用系统调用共享区属于用户空间而非内核的所以共享内存是进程通信最快的映射后读写直接被对方看到不需要系统调用或写入内容但是通信双方没有保护机制共享内存的写端只写了一部分就会被读端读取导致数据不一致有保护机制AAAA BBAA BB CC无保护机制AAAAA BAA BB可以使用命名管道的同步机制对共享内存进行局部保护进程与进程的共享内存映射SystemV进程与磁盘文件的共享内存映射mmapshm.hpp#pragma once #include iostream #include string #include sys/ipc.h #include sys/shm.h #include sys/ipc.h #include unistd.h #include comm.hpp const int gdefaultid -1; // 错误共享内存标识符 const int gsize 4096; // 共享内存空间 const std::string pathname .; // 路径名称 const int projid 0x66; // 生成key的任意id const int gmode 0666; // 共享内存权限 #define CREATER creater #define USER user // 共享内存类 class Shm { private: void CreateHelper(int flg) { printf(key: 0x%x\n, _key); _shmid shmget(_key, gsize, flg); if (_shmid 0) { ERR_EXIT(shmget); } printf(shmid: %d\n, _shmid); } // 创建一个共享内存 void Create() { CreateHelper(IPC_CREAT | IPC_EXCL | gmode); } // 关联一个共享内存 void Attach() { _start_mem shmat(_shmid, nullptr, 0); if ((long long)_start_mem 0) { ERR_EXIT(shmat); } printf(attach success\n); } // 去关联一个共享内存 void Detach() { int n shmdt(_start_mem); if (n 0) { printf(Detach success\n); } } // 获取一个共享内存 void Get() { CreateHelper(IPC_CREAT); } // 删除一个共享内存 void Destroy() { Detach(); if (_usertype CREATER) { int n shmctl(_shmid, IPC_RMID, nullptr); if (n 0) { printf(shmctl delete shm: %d success!\n, _shmid); } else { ERR_EXIT(shmctl\n); } } } public: Shm(const std::string pathname, int projid, const std::string usertype) : _shmid(gdefaultid), _size(gsize), _start_mem(nullptr), _usertype(usertype) { _key ftok(pathname.c_str(), projid); if (_key 0) { ERR_EXIT(ftok); } if (_usertype CREATER) { Create(); } else if (_usertype USER) { Get(); } else { } Attach(); } // 返回起始虚拟地址 void *VirtualAddr() { printf(VirtualAddr: %p\n, _start_mem); return _start_mem; } // 获取共享内存大小 int Size() { return _size; } // 获取共享内存的属性 void Attr() { struct shmid_ds ds; int n shmctl(_shmid, IPC_STAT, ds); printf(shm_segsz: %ld\n, ds.shm_segsz); printf(key: 0x%x\n, ds.shm_perm.__key); } ~Shm() { std::cout _usertype std::endl; Destroy(); } private: int _shmid; // 共享空间标识符 key_t _key; // key值 int _size; // 共享空间大小 void *_start_mem; // 起始的虚拟地址 std::string _usertype; // 用户类型 };Fifo.hpp#pragma once #include iostream #include cstdio #include string #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include comm.hpp #define PATH . #define FILENAME fifo // 命名管道类 class NamedIpcFifo { public: NamedIpcFifo(const std::string path, const std::string name) : _path(path), _name(name) { _fifoname _path / _name; // 创建管道文件 umask(0); // 设置权限掩码 int n mkfifo(_fifoname.c_str(), 0666); if (n 0) { ERR_EXIT(mkfifo); } else { std::cout mkfifo success std::endl; } } ~NamedIpcFifo() { // 删除管道文件 int n unlink(_fifoname.c_str()); if (n 0) { //bug先析构fifo后进程直接退出导致shm析构没有被调用 //ERR_EXIT(unlink); } else { std::cout remove fifo failed std::endl; } } private: std::string _path; // 管道所在目录路径 std::string _name; // 管道文件名称 std::string _fifoname; // 管道完整路径 }; // 文件操作类 class FileOper { public: FileOper(const std::string path, const std::string name) : _path(path), _name(name), _fd(-1) { _fifoname _path / _name; } // 打开文件读 void OpenForRead() { // 写入方没有执行open的时候读取方就要在open内部阻塞 // 直到有人把管道文件打开了open才会返回 _fd open(FILENAME, O_RDONLY); if (_fd 0) { ERR_EXIT(openforread); } std::cout open fifo success std::endl; } // 打开文件写 void OpenForWrite() { _fd open(FILENAME, O_WRONLY); if (_fd 0) { ERR_EXIT(openforwrite); } std::cout open fifo success std::endl; } // 唤醒读端 void Wakeup() { char c c; int n write(_fd, c, 1); printf(尝试唤醒: %d\n, n); } // 等待写端 bool Wait() { char c; int number read(_fd, c, 1); if (number 0) { printf(读端醒来: %d\n, number); return true; } return false; } // 关闭文件 void Close() { if (_fd 0) { close(_fd); } } ~FileOper() { } private: std::string _path; // 管道所在目录路径 std::string _name; // 管道文件名称 std::string _fifoname; // 管道完整路径 int _fd; // 管道文件描述符 };server.cc#include Shm.hpp #include Fifo.hpp int main() { // 创建共享内存对象 Shm shm(pathname, projid, CREATER); // 获取共享内存属性 sleep(5); shm.Attr(); // 创建管道文件对象 NamedIpcFifo fifo(PATH, FILENAME); // 文件操作 FileOper readerfile(PATH, FILENAME); readerfile.OpenForRead(); // 指向共享内存的指针 char *mem (char *)shm.VirtualAddr(); while (true) { // 管道未写入时读端默认阻塞等待写端写入 if(readerfile.Wait()) { printf(%s\n, mem); } else { break; } } readerfile.Close(); return 0; }client.cc#include Shm.hpp #include Fifo.hpp int main() { // 文件操作 FileOper writerfile(PATH, FILENAME); writerfile.OpenForWrite(); // 获取共享内存对象 Shm shm(pathname, projid, USER); // 指向共享内存的指针 char *mem (char *)shm.VirtualAddr(); int index 0; for (char c A; c Z; c, index 2) { sleep(2); // 向共享内存写 mem[index] c; mem[index 1] c; mem[index 2] 0; // 唤醒读端读取 writerfile.Wakeup(); } writerfile.Close(); return 0; }comm.hpp#pragma once #include cstdio #include cstdlib #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0)Makefile.PHONY:all all:client server client:client.cc g -o $ $^ -stdc11 server:server.cc g -o $ $^ -stdc11 .PHONY:clean clean: rm -f client server七、System V消息队列7.1.消息队列的原理IPC本质让不同的进程看到同一份资源消息队列将该资源维护成一个队列消息队列的生命周期随内核消息队列提供了从一个进程向另外一个进程发送一块数据段方法操作系统要对多个消息队列进行管理先描述再组织两个进程通过约定key值保证看到同一个消息队列7.2.消息队列的接口7.2.1.msgget函数作用创建一个消息队列7.2.2.msgctl函数作用控制消息队列7.2.3.ftok函数作用生成key值7.2.4.msgsnd函数作用发送数据节点数据的结构体mtype数据类型区分进程mtext数据内容7.2.5.msgrcv函数作用接收数据7.2.6.ipcs命令ipcs -q查看消息队列7.2.6.ipcrm命令ipcrm -q msqid删除消息队列八、System V信号量8.1.关于并发编程的一些概念共享内存使两个进程看到同一份资源为通信提供前提但是没有保护机制数据不一致信号量是一个具体的方案共享资源多个执行流进程能看到的同一份公共资源临界资源被保护起来的共享资源临界区在进程中涉及到互斥资源的程序段访问临界资源对应的代码保护临界区代码就是在保护临界资源互斥任何时刻只允许一个执行流访问资源类似于ATM机每个人进入ATM机取钱之前要先开锁进去后门会上锁每次只能有一个人进入提款机取钱同步多个执行流访问临界资源时有一定的顺序性类似用共享内存加命名管道通过管道文件的读端阻塞等规定数据写入共享内存后向管道文件写入唤醒读端原子性要么做要么不做学校学习要么别学要么考全校第一锁本身也要被共享所以在申请锁的时候必须是原子的要么申请好了要么不申请8.2.信号量的理解信号量信号灯一个计数器用来表明临界资源的数量多少作用保护临界区本质对资源的预定机制信号量本身就是共享资源申请时要减减具有原子性P操作进程用完资源退出后信号量的值要加加具有原子性V操作通过PV操作完成进程对资源的预定机制多元信号量资源不是整体使用通过信号量进行计数同步二元信号量资源整体使用只有1或者0两态的信号量互斥信号量与通信的关系先访问信号量P每个进程都得先看到同一个信号量不是传递数据才是IPC通信通知、互斥、同步也是8.3.信号量的接口8.3.1.semget函数作用创建信号量或信号量集参数1key值参数2信号量的个数参数3信号量集标志位8.3.2.ftok函数作用生成key值8.3.3.ipcs命令ipcs -s查看信号量或信号量集8.3.4.ipcrm命令ipcrm -s semid删除信号量或信号量集8.3.5.semctl函数作用控制信号量或信号量集参数1信号量集标识符参数2通过SET_VAL命令设置的信号量初值参数3信号量设置命令8.3.6.semop函数作用对信号量集操作参数1信号量集标识符参数2信号量集数组参数3信号量集数组元素个数sem_num信号量集中信号量的下标sem_op-1进行P操作1进行V操作sem_flag默认为0操作系统需要对大量的信号量集进行管理需要先描述再组织九、内核IPC资源的组织管理共享内存消息队列信号量用key来区分唯一性在操作系统中共享内存消息队列信号量被当作了同一种资源叫做System V IPCIPC资源在内核中的组织形式