网页设计与网站建设指标点,今天北京感染了多少人,30岁学前端开发是不是晚了,保定网站网站建设#x1f308;欢迎来到Linux专栏~~进程通信 (꒪ꇴ꒪(꒪ꇴ꒪ )#x1f423;,我是Scort目前状态#xff1a;大三非科班啃C中#x1f30d;博客主页#xff1a;张小姐的猫~江湖背景快上车#x1f698;#xff0c;握好方向盘跟我有一起打天下嘞#xff01;送给自己的一句鸡汤… 欢迎来到Linux专栏~~进程通信 (꒪ꇴ꒪(꒪ꇴ꒪ ),我是Scort目前状态大三非科班啃C中博客主页张小姐的猫~江湖背景快上车握好方向盘跟我有一起打天下嘞送给自己的一句鸡汤真正的大师永远怀着一颗学徒的心作者水平很有限如果发现错误可在评论区指正感谢欢迎持续关注 文章目录欢迎来到Linux专栏~~进程通信一. 进程间通信介绍二. 管道匿名管道匿名管道原理创建匿名管道pipedemo代码匿名管道通信的4种情况✨读阻塞写快读慢✨写阻塞写慢读快✨写端关闭✨读端关闭管道的大小命名管道创建命名管道基于命名管道通信 pipe vs fifo三. System V标准下的进程间通信方式共享内存共享内存的建立 创建共享内存 控制共享内存 挂接和去关联 shmid 和 key共享内存的进程间通信共享内存与管道进行对比共享内存归属谁共享内存的特征消息队列了解写在最后一. 进程间通信介绍
进程之间会存在特定的协同工作的场景
数据传输一个进程要把自己的数据交给另一个进程让其继续进行处理资源共享多个进程之间共享同样的资源。通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变 进程间通信的本质就是让不同的进程看到同一份资源 进程是具有独立性的。虚拟地址空间页表 保证了进程运行的独立性进程内核数据结构进程代码和数据
进程通信的前提首先需要让不同的进程看到同一份“内存”特定的结构组织
这块内存应该属于谁呢为了维持进程独立性它一定不属于进程A或B它属于操作系统。
综上进程间通信的前提就是由OS参与提供一份所有通信进程都能看到的公共资源。
进程间通信的发展
管道 匿名管道pipe命名管道pipe System V标准 进程间通信 System V 消息队列System V 共享内存System V 信号量 POSIX标准 进程间通信(多线程详谈) 消息队列共享内存信号量互斥量条件变量读写锁
二. 管道
什么是管道
有入口有出口都是单向传输资源的数据 所以计算机领域设计者设计了一种单向通信的方式 —— 管道
匿名管道
众所周知父子进程是两个独立进程父子通信也是进程间通信的一种基于父子间进程通信就是匿名管道。我们首先要对匿名管道有一个宏观的认识
父进程创建子进程子进程需要以父进程为模板创建自己的files_struct 而不是与父进程共用但是struct file这个结构体就不会拷贝因为打开文件也与创建进程无关文件的数据不用拷贝
因为左边是进程相关数据结构右边是文件相关结构 匿名管道原理
父进程创建管道对同一文件分别以读写方式打开 父进程fork创建子进程 因为管道是一个只能单向通信的信道父子进程需要关闭对应读写端至于谁关闭谁取决于通信方向。
于是通过子进程继承父进程资源的特性双方进程看到了同一份资源。
创建匿名管道pipe
pipe谁调用就让以读写方式打开一个文件内存级文件
#include unistd.h
int pipe(int pipefd[2]);参数pipefd输出型参数通过这个参数拿到两个打开的fd返回值成功返回0失败返回-1
数组pipefd用于返回两个指向管道读端和写端的文件描述符
数组元素含义pipefd[0]~嘴巴管道读端的文件描述符pipefd[1] ~ 钢笔管道写端的文件描述符
此处提取查一下要用到的函数
man2是获得系统linux内核调用的用法 man 3 是获得标准库标准C语言库、glibc函数的文档
//linux中用man可以查哦
#include unistd.h
pid_t fork(void);#include unistd.h
int close(int fd);#include stdlib.h
void exit(int status);下面按照之前讲的原理进行逐一操作①创建管道 ②父进程创建子进程 ③关闭对应的读写端形成单向信道
#include iostream
#include unistd.h
#include cstdio
#include cstring
#include string.h
#include assert.husing namespace std;int main()
{//1.创建管道int pipefd[2] {0};int n pipe(pipefd); //失败返回-1assert(n ! -1); //只在debug下有效(void)n; //仅此证明n被使用过#ifdef DEBUGcout pipefd[0] pipefd[0] endl; //3cout pipefd[1] pipefd[1] endl; //4
#endif//2.创建子进程 pid_t id fork();assert(id ! -1);if(id 0){//子进程//3. 构建单向通信的信道//3.1 子进程关闭写端[1]close(pipefd[1]);exit(0);}//父进程//父进程关闭读端[0]close(pipefd[0]);return 0;
}在此基础上我们就要进行通信了实际上就是对某个文件进行写入因为管道也是文件下面提提前查看要用到的函数
#include unistd.h
ssize_t write(int fd, const void *buf, size_t count);#include unistd.h
ssize_t read(int fd, void *buf, size_t count);
返回值:
- 返回写入的字节数
- 零表示未写入任何内容这里意味着对端进程关闭文件描述符#include unistd.h
unsigned int sleep(unsigned int seconds);demo代码
简单实现了管道通信的demo版本
#include iostream
#include unistd.h
#include cstdio
#include cstring
#include string.h
#include assert.h
#includesys/types.h
#includesys/wait.husing namespace std;int main()
{//1.创建管道int pipefd[2] {0};int n pipe(pipefd); //失败返回-1assert(n ! -1); //只在debug下有效(void)n; //仅此证明n被使用过#ifdef DEBUGcout pipefd[0] pipefd[0] endl; //3cout pipefd[1] pipefd[1] endl; //4
#endif//2.创建子进程 pid_t id fork();assert(id ! -1);if(id 0){//子进程 - 读//3. 构建单向通信的信道//3.1 子进程关闭写端[1]close(pipefd[1]);char buffer[1024];while(1){size_t s read(pipefd[0], buffer, sizeof(buffer)-1);if(s 0){buffer[s] 0;//因为read是系统调用没有/0此处给加上coutchild get a message[ getpid() ] 爸爸对你说 buffer endl;}}//close(pipefd[0]);exit(0);}//父进程 - 写//父进程关闭读端[0]close(pipefd[0]);string message 我是父进程我正在给你发消息;int count 0; //计算发送次数char send_buffer[1024];while(true){//3.2构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), %s[%d] : %d,message.c_str(), getpid(), count);count;//3.3写入write(pipefd[1], send_buffer, strlen(send_buffer));//此处strlen不能1//3.4 故意sleepsleep(1);}pid_t ret waitpid(id, nullptr, 0);assert(ret ! -1);(void)ret;return 0;
}此处有个问题为什么不定义一个全局的buffer来进行通信呢
因为有写时拷贝的存在无法更改通信
上面的方法就是把数据交给管道让对方通过管道进行读取
匿名管道通信的4种情况
之前父子进程同时向显示器中写入的时候二者会互斥 —— 缺乏访问控制
而对于管道进行读取的时候父进程如果写的慢子进程就会等待读取 —— 这就是说明管道具有访问控制
✨读阻塞写快读慢
父进程疯狂的进行写入子进程隔10秒才读取子进程会把这10秒内父进程写入的所有数据都一次性的打印出来
代码如非就是在父进程添加了打印conut子进程sleep(10)可以自行的在demo代码上添加 父进程写了1220次子进程一次就给你读完了读写之间没有关系这就叫做流式的服务。 也就是管道是面向字节流的也就是只有字节的概念究竟读成什么样也无法保证甚至可能读出乱码所以父子进程通信也是需要制定协议的但这个我们网络再细说。。
✨写阻塞写慢读快
管道没有数据的时候读端必须等待父进程每隔2秒才进行写入子进程疯狂的读取 ✨写端关闭
父进程写入10秒后把写端fd关闭读端会怎么样?
写入的一方fd没有关闭如果有数据就读没有数据就等写入的一方fd关闭了读取的一方read会返回0表示读到了文件结尾退出读端
#include iostream
#include unistd.h
#include cstdio
#include cstring
#include string.h
#include assert.h
#includesys/types.h
#includesys/wait.husing namespace std;int main()
{//1.创建管道int pipefd[2] {0};int n pipe(pipefd); //失败返回-1assert(n ! -1); //只在debug下有效(void)n; //仅此证明n被使用过#ifdef DEBUGcout pipefd[0] pipefd[0] endl; //3cout pipefd[1] pipefd[1] endl; //4
#endif//2.创建子进程 pid_t id fork();assert(id ! -1);if(id 0){//子进程 - 读//3. 构建单向通信的信道//3.1 子进程关闭写端[1]close(pipefd[1]);char buffer[1024*8];while(1){//sleep(10);//20秒读一次//写入的一方fd没有关闭如果有数据就读没有数据就等//写入的一方fd关闭了读取的一方read会返回0表示读到了文件结尾size_t s read(pipefd[0], buffer, sizeof(buffer)-1);if(s 0){buffer[s] 0;//因为read是系统调用没有/0此处给加上coutchild get a message[ getpid() ] 爸爸对你说 buffer endl;}else if (s 0){cout write quit(father), me quit!!! endl;break;}}//close(pipefd[0]);exit(0);}//父进程 - 写//父进程关闭读端[0]close(pipefd[0]);string message 我是父进程我正在给你发消息;int count 0; //计算发送次数char send_buffer[1024*8];while(true){//3.2构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), %s[%d] : %d,message.c_str(), getpid(), count);count;//3.3写入write(pipefd[1], send_buffer, strlen(send_buffer));//此处strlen不能1//3.4 故意sleepsleep(1);cout count endl;if(count 5){cout 父进程写端退出 endl;break;}}close(pipefd[1]);//关闭读端pid_t ret waitpid(id, nullptr, 0);assert(ret ! -1);(void)ret;return 0;
}运行结果如下 ✨读端关闭
读端关闭写端继续写入直到OS终止写进程
#include stdio.h
#include unistd.h
#include string.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h
int main()
{int fd[2] { 0 };if (pipe(fd) 0){ //使用pipe创建匿名管道perror(pipe);return 1;}pid_t id fork(); //使用fork创建子进程if (id 0){//childclose(fd[0]); //子进程关闭读端//子进程向管道写入数据const char* msg hello father, I am child...;int count 10;while (count--){write(fd[1], msg, strlen(msg));sleep(1);}close(fd[1]); //子进程写入完毕关闭文件exit(0);}//fatherclose(fd[1]); //父进程关闭写端close(fd[0]); //父进程直接关闭读端导致子进程被操作系统杀掉int status 0;waitpid(id, status, 0);printf(child get signal:%d\n, status 0x7F); //打印子进程收到的信号return 0;
}运行结果显示子进程退出时收到的是13号信号 通过kill -l命令可以查看13对应的具体信号 由此可知当发生情况四时操作系统向子进程发送的是SIGPIPE信号将子进程终止的。
总结上述的4中场景
写快读慢写满了不能再写了写慢读快管道没有数据的时候读端必须等待写关读取的一方read会返回0表示读到了文件结尾退出读端读关写继续写OS终止写进程 ——
由上总结出匿名管道的5个特点 ——
管道是一个单向通信的通信管道是半双工通信的一种特殊情况管道是用来进行具有血缘关系的进程进行进程间通信 —— 常用于父子通信管道具有通过让进程间协同提供了访问控制管道是 面向字节流 —— 协议后面详谈管道是基于文件的管道的声明周期是随进程的
管道的大小
管道的容量是有限的如果管道已满那么写端将阻塞或失败那么管道的最大容量是多少呢
ps原子性要么做了要么不做没有中间状态 方法1 man手册查询 然后我们可以使用uname -r命令查看自己使用的Linux版本 我使用的是Linux 2.6.11之后的版本因此管道的最大容量是65536字节 方法二自行测试 也就是如果读端一直不读取写端又不断的写入当管道被写满后写端进程就会被挂起。据此我们可以写出以下代码来测试管道的最大容量。
#include unistd.h
#include stdio.h
#include stdlib.h
#include sys/wait.h
int main()
{int fd[2] { 0 };if (pipe(fd) 0){ //使用pipe创建匿名管道perror(pipe);return 1;}pid_t id fork(); //使用fork创建子进程if (id 0){//child close(fd[0]); //子进程关闭读端char c a;int count 0;//子进程一直进行写入一次写入一个字节while (1){write(fd[1], c, 1);count;printf(%d\n, count); //打印当前写入的字节数}close(fd[1]);exit(0);}//fatherclose(fd[1]); //父进程关闭写端//父进程不进行读取waitpid(id, NULL, 0);close(fd[0]);return 0;
}写端进程最多写65536字节的数据就被操作系统挂起了也就是说我当前Linux版本中管道的最大容量是65536字节 命名管道
为了解决匿名管道只能在父子之间通信我们引入命名管道可以在任意不相关进程进行通信
多个进程打开同一个文件OS只会创建一个struct_file 命名管道就是一种特殊类型的文件(可以被打开但不会将数据刷新进磁盘)两个进程通过命名管道的文件名打开同一个管道文件此时这两个进程也就看到了同一份资源进而就可以进行通信了。
命名管道就是通过唯一路径/文件名的方式定位唯一磁盘文件的 ps命名管道和匿名管道一样都是内存文件只不过命名管道在磁盘有一个简单的映像所以有名字但这个映像的大小永远为0因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。 创建命名管道 make FIFOs 在命令行上创建命名管道
mkfifo (named pipes)FIFOFirst In First Out 队列呀 来个小实验 命令行上执行的命令echo和cat都是进程所以这就是通过管道文件进行的进程间通信 —— 那么如何用代码实现命名管道进程间通信的呢
//查手册man 3 mkfifo
#include sys/types.h
#include sys/stat.hint mkfifo(const char *pathname, mode_t mode);pathname管道文件路径mode管道文件权限返回值创建成功返回0创建失败返回-1并设置错误码
我touch了server.c和client.c最终希望在server和client两个进程之间相互通信先写一个Makefile ——
.PHONY:all
all:client serverclient:client.cxxg -o $ $^ -stdc11
server:server.cxxg -o $ $^ -stdc11.PHONY:clean
clean:rm -f client serverMakefile自顶向下扫描只会把第一个目标文件作为最终的目标文件。所以要一次性生成两个可执行程序需要定义伪目标.PHONY: all并添加依赖关系
基于命名管道通信
comm.h
我们创建一个共用的头文件这只是为了两个程序能有看到同一个资源的能力了
#ifndef _COMM_H_ //能避免头文件的重定义
#define _COMM_H_//hpp和.h的区别:.h里面只有声明没有实现而.hpp里声明实现都有后者可以减少.cpp的数量#include iostream
#include string
#include unistd.h
#include cstdio
#include cstring
#include sys/types.h
#include sys/stat.h
#include fcntl.husing namespace std;#define MODE 0666
#define SIZE 128
string ipcPath ./fifo.ipc;#endifserver.c
创建命名管道读信息并实现相应业务逻辑
#include comm.hppint main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(), MODE) 0){perror(mkfifo);exit(1);}//2. 正常的文件操作int fd open(ipcPath.c_str(), O_RDONLY);if(fd 0){perror(open);exit(2);}//3.编写正常的通信代码char buffer[SIZE];while(1){memset(buffer, \0, sizeof(buffer));ssize_t s read(fd, buffer, sizeof(buffer)-1);if(s 0){cout client say buffer endl;}else if(s 0){//说明写端关闭了cerr read end of file, client quit, server quit too endl;}else{//读取失败perror(read);break;}}//4. 关闭文件close(fd);unlink(ipcPath.c_str());//通信完毕删除文件return 0;
}client.c 此时不需要再创建命名管道只需要获取已打开的命名管道文件
从键盘拿到了待发送数据发送数据也就是向管道中写入
#include comm.hppint main()
{//不需要创建fifo只需获取即可int fd open(ipcPath.c_str(), O_WRONLY);if(fd 0){perror(open);exit(1);}//2.ipc通信string buffer;while(1){cout Place Enter Message:;std::getline(std::cin, buffer);write(fd, buffer.c_str(), sizeof(buffer));}//3.关闭close(fd);return 0;
}效果展示 一定要先运行服务端server创建命名管道再运行客户端实现了不相关进程通信 —— 如果我想让多个子进程来执行打印任务 当然我们就要调整一下server.c的业务逻辑
#include comm.hpp
#include sys/wait.hstatic void getMessage(int fd)
{//3.编写正常的通信代码char buffer[SIZE];while(1){memset(buffer, \0, sizeof(buffer));ssize_t s read(fd, buffer, sizeof(buffer)-1);if(s 0){cout [ getpid() ] client say buffer endl;}else if(s 0){//说明写端关闭了cerr [ getpid() ] read end of file, client quit, server quit too endl;}else{//读取失败perror(read);break;}}
}int main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(), MODE) 0){perror(mkfifo);exit(1);}//log(创建管道文件成功, Debug) step 1 endl;//2. 正常的文件操作int fd open(ipcPath.c_str(), O_RDONLY);if(fd 0){perror(open);exit(2);}//log(打开管道文件成功, Debug) step 2 endl;int nums 3;for(int i 0; i nums; i){pid_t id fork();if(id0){//子进程getMessage(fd);exit(2);}}for(int i 0; i nums; i){waitpid(-1, nullptr, 0);}//4. 关闭文件close(fd);//log(关闭管道文件成功, Debug) step 3 endl;unlink(ipcPath.c_str());//通信完毕删除文件//log(删除管道文件成功, Debug) step 4 endl;return 0;
}pipe vs fifo
为什么pipe叫做匿名管道和和fifo叫做命名管道
匿名管道文件属于内存级的文件不需要名字因为它是通过父子继承的方式看到同一份资源命名管道一定要有名字从而使不相关进程通过唯一路径定位同一个文件
三. System V标准下的进程间通信方式
下面我们要学习System V标准是在同一主机内的进程间通信方案是站在OS层面专门为进程间通信设计的方案。
进程通信的本质是先让不同进程看到同一份资源System V提供了这三个主流方案 ——
共享内存 - 传递数据消息队列(有点落伍) - 传递数据信号量 (多线程讲POSIX标准) - 实现进程同步控制详谈
共享内存
基于共享内存进行进程间通信原理 ——
首先在物理内存当中申请一块内存空间将这块内存空间分别与各个进程各自的页表之间建立映射进程虚拟地址空间当中开辟空间共享内存并将虚拟地址填充到各自页表的对应位置使得虚拟地址和物理地址之间建立起对应关系所以两个进程便看到了同一份物理内存这块物理内存就叫做共享内存 共享内存的建立
共享内存提供者是操作系统OS那么操作系统要不要管理共享内存呢 - 先描述再组织
共享内存 共享内存块 对应的共享内存的内核数据结构来描述其属性 创建共享内存
#include sys/ipc.h
#include sys/shm.hint shmget(key_t key, size_t size, int shmflg);参数 key:为了使不同进程看到同一段共享内存即让不同进程拿到同一个ID需要由用户自己设定但如何设定的与众不同好难啊就要借助下面这个函数。 所以怎么样保证两个进程拿到同一个key值呢 #include sys/types.h
#include sys/ipc.hkey_t ftok(const char *pathname, int proj_id);pathname自定义路径名proj_id自定义项目ID返回值成功后返回生成的key_t值。失败时返回1 szie共享内存的大小建议是4KB的整数倍因为共享内存在内核中申请的基本单位是页(内存页)。 shmflg标记位这一看就是宏都是只有一个比特位是1且相互不重复的数据这样|在一起就能传递多个标志位 IPC_CREAT如果单独使用IPC_CREAT或者flg为0如果创建共享内存时底层已经存在获取之如果不存在就创建之IPC_EXCL单独使用没有意义通常要搭配起来IPC_CREAT | IPC_EXCL如果底层不存在就创建并返回如果底层存在就出错返回。这样的意义在于 如果调用成功得到的一定是一个全新的共享内存。
返回值成功后将返回有效的共享内存标识符。失败了返回-1并设置errno错误码。 控制共享内存
手动查看与手动删除
ipcs -m 查看ipc资源不带选项默认查看消息队列(-q)、共享内存(-m)、信号量(-s)
ipcrm -m shmid //删除共享内存system V IPC资源生命周期随内核所以我们要手动 / 自动删除那怎么样自动删除呢 控制共享内存
#include sys/ipc.h
#include sys/shm.hint shmctl(int shmid, int cmd, struct shmid_ds *buf);参数
cmd设置IPC_RMID就行IPC_RMID即便是有进程和当下的shm挂接依旧删除共享内存强大buf这就是描述共享内存的数据结构啊 返回值失败返回-1成功返回0 挂接和去关联
attach 挂接 ——
#include sys/types.h
#include sys/shm.hvoid *shmat(int shmid, const void *shmaddr, int shmflg);shmaddr挂接到什么位置我们也不知道给NULL让操作系统来设置shmflg 给0
最重要的是返回值
这个地址一定是虚拟地址类似malloc返回申请到的起始地址失败返回-1并设置错误码
detach 去关联 ——
int shmdt(const void *shmaddr);shmaddrshmat返回的地址 注意去关联不是释放共性内存而是取消当前进程和共享内存的关系本质是去掉进程和物理内存构建映射关系的页表项去掉 返回值成功返回0失败返回-1 shmid 和 key
只有创建的时候用key大部分用户访问共享内存都用的是shmid用户层
共享内存的进程间通信
comm.h
#pragma one#include iostream
#include cstdio
#include sys/types.h
#include sys/ipc.h
#include sys/shm.h
#include log.hppusing namespace std;//不推荐#define PATH_NAME /home/ljj
#define PROJ_ID 0x66server.c 创建公共的key值 创建共享内存 - 建议创建一个全新的共享内存因为是通信的发起者 带选项IPC_CREAT | IPC_EXCL若和系统中已经存在的ID冲突则出错返回 注意到其中权限perm是0那也可以设置一下 int shmid shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); 将指定的共享内存挂接到自己的地址空间上 将指定的共享内存从自己的地址空间去关联 删除共享内存
#include comm.hppstring TransToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof(buffer), 0x%x, k);return buffer;
}int main()
{//1.创建公共的key值key_t k ftok(PATH_NAME, PROJ_ID);assert(k ! -1);Log(create key done, Debug) server key : TransToHex(k) endl;//2. 创建共享内存 - 建议创建一个全新的共享内存因为是通信的发起者int shmid shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid -1){perror(shmget);exit(1);}Log(creat shm done, Debug) shmid : shmid endl;sleep(10);//3.将指定的共享内存挂接到自己的地址空间上char *shmaddr (char*)shmat(shmid, nullptr, 0);Log(attach shm done, Debug) shmid : shmid endl;sleep(10); //这里就是通信的代码//4.将指定的共享内存从自己的地址空间去关联int n shmdt(shmaddr);assert(n ! -1);(void)n;Log(detach shm done, Debug) shmid : shmid endl;sleep(10); //5.删除共享内存n shmctl(shmid, IPC_RMID, nullptr);assert(n ! -1);(void)n;Log(delete shm done, Debug) shmid : shmid endl;return 0;
}关于申请共享内存的大小size我们说建议是4KB的整数倍因为共享内存在内核中申请的基本单位是页(内存页)4KB。如果我申请4097Byte大小的空间内核会向上取整给我4096* 2Byte诶那我监视到的↑怎么还是4097啊虽然在底层申请到的是4096*2但不会多给你这样也可能引起错误~
client.c
只需获取共享内存不用删除
#include comm.hppint main()
{key_t k ftok(PATH_NAME, PROJ_ID);if(k 0){Log(create key failed, Error) client key : k endl;exit(1);}Log(create key done, Debug) client key : k endl;//获取共享内存int shmid shmget(k, SHM_SIZE, IPC_CREAT);if(shmid 0){Log(create shm failed, Error) client key : k endl;exit(2);}Log(attach shm success, Error) client key : k endl;sleep(10);//挂接地址char* shmaddr (char*)shmat(shmid, nullptr, 0);if(shmaddr nullptr){Log(attach shm failed, Error) client key : k endl;exit(3);}Log(attach shm success, Error) client key : k endl;sleep(10);//使用//去关联int n shmdt(shmaddr);assert(n ! -1);Log(datach shm success, Error) client key : k endl;sleep(10);//你只管用不需要删除共享内存return 0;
}效果展示 写一个命令行脚本来监视共享内存 ——
while :; do ipcs -m; echo _________________________________________________________________; sleep 1; done注意观察nattch这个参数的变化0-1-2-1-0 上面的框架都搭建好了之后接下来就是通信部分 1️⃣客户端不断向共享内存写入数据
//client将共享内存看成一个char类型的buffer
char a a;
for(; a z; a)
{//每一次都想共享内存shmaddr的起始地址snprintf(shmaddr, SHM_SIZE - 1,\hello server, 我是其他进程, 我的pid: %d, inc: %c\n,\getpid(), a);sleep(2);
}2️⃣服务端不断读取共享内存当中的数据并输出
//将共享内存当成一个大字符串
for(;;)
{printf(%s\n, shmaddr);sleep(1);
}结果如下
ps我们发现即使我们没有向server端发消息server也是不断的在读取信息的
共享内存与管道进行对比
共享内存是所有进程间通信方式中最快的一种通信方式。 将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作
我们再来看看共享内存通信 键盘写入shm另一端可以直接获取到哪里还需要什么拷贝最多两次拷贝键盘输入一次输出到外设一次
共享内存归属谁
共享内存的区域是在OS内核还是在用户空间
用户空间
其中文本、初始化数据区、未初始化数据区、堆、栈、环境变量、命令行参数、再 往上就是1G的OS内核其中剩余3G都是用户自己支配的
用户空间不用经过系统调用直接进行访问 所以双方进程如果要进行通信直接进行内存级的读和写减少了许多拷贝 那为什么之前将的pipe和fifo都要通过read、write进行通信为什么呢 因为管道双方看到的资源都属于内核级的文件我们无权直接进行访问必须调用系统接口
共享内存的特征
共享内存的生命周期随内核共享内存是所有进程中速度最快的只需要经过页表映射不需来回拷贝不经过OS共享内存没有提供访问控制读写双方根本不知道对方的存在会带来并发问题
消息队列了解
严重过时接口与文件不对应
创建消息队列与创建共享内存极其相似
#include sys/types.h
#include sys/ipc.h
#include sys/msg.hint msgget(key_t key, int msgflg);删除消息队列
#include sys/types.h
#include sys/ipc.h
#include sys/msg.hint msgctl(int msqid, int cmd, struct msqid_ds *buf);我们可以通过key找到同一个共享内存。
我们发现共享内存、消息队列、信号量的 ——
接口都类似数据结构的第一个结构类型struct ipc_perm是完全一致的
我们由shmid申请到的都是01234… 大胆推测在内核中所有的ipc资源都是通过数组组织起来的。可是描述它们的结构体类型并不相同啊但是~ System V标准的IPC资源xxxid_ds结构体的第一个成员都是ipc_perm都是一样的。
写在最后
应该是我写过最长的一篇博客了