Linux进程间通信 - 匿名管道(1)

news/2024/11/30 18:55:05/

之间我们学习了基础IO中有关文件,动静态库等知识,后面我们将讲述进程间通信的内容,在本文中就将来展示匿名管道。

进程间通信

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

我们知道进程是具有独立性的,因此想要进程间进行通信需要增加通信的成本。为了解决进程间通信的问题,首要的就是要让两个进程看到同一份资源。然后让一方进行读取,另一方进行写入,完成通信。

管道

什么是管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。之前我们在学习Linux的基本指令的时候就使用过 | 这个指令,这被我们称为管道,例如这个指令 who | wc -l 就是将本来应该打印到显示器文件的信息传入到了wc文件中,再由wc文件进行处理输出。

匿名管道

#include <unistd.h>
功能:创建一无名管道
原型
        int pipe(int fd[2]); // int fd[2] 这是一个输出型参数用管道的时候需要有读端和写端的记录
参数
        fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码 

匿名管道原理

进程在内存中创建PCB,然后通过进程所对应的文件描述符表打开对应的文件,匿名管道就是OS提供的一个内存级文件,进程的文件描述附表以读写的方式打开这个文件。然后进行fork()创建子进程,子进程会复制父进程相关的数据结构包括文件描述符表,不会打开父进程曾经打开的文件,这样父子进行就都会指向同一个文件,目标文件就会被多个进程看到。未来如果父进程向文件写入信息,子进程就可以接收到相关的信息,另外这种管道只支持单向通信,因此需要确定数据的流向,关闭不需要的fd。

简单的小例子

创建管道

    // 让不同的进程看到同一份资源// 任何一种进程间通信,一定要 先 保证不同的进程看到同一份资源int pipefd[2] = {0};// 1、创建管道int n = pipe(pipefd);    if (n < 0){std::cout << "pipe error," << errno << ":" << strerror(errno) << std::endl;return 1;}std::cout << "pipefd[0]:" << pipefd[0] << std::endl; // 读端std::cout << "pipefd[1]:" << pipefd[1] << std::endl; // 写端

 

可以看到创建管道之后文件描述符3和4确实连接到了匿名管道文件的读端与写端。

子进程写入

// 2、创建子进程pid_t id = fork();assert(id != -1); // 正常应该使用判断,这里使用断言:意料之外使用if,意料之中使用assert        if (id == 0){// Scene1(pipefd); // 正常通信场景// Scene2(pipefd); // 让子进程不断地去写入,但是父进程每隔10秒读一次// Scene3(pipefd); // 让子进程写慢一点,父进程读取不受限制// Scene4(pipefd); // 关闭写端// Scene5(pipefd); // 关闭读端}
void Scene1(int pipefd[])
{// child// 3、关闭不需要的文件描述符,让父进程进行读取,子进程进行写入close(pipefd[0]);// 4、开始通信 -- 结合某种场景const std::string namestr = "hello, 我是子进程";int cnt = 1;char buffer[1024];while (true){snprintf(buffer, sizeof(buffer), "%s, 计数器: %d, 我的PID: %d\n", namestr.c_str(), cnt++, getpid());write(pipefd[1], buffer, strlen(buffer));sleep(1);}close(pipefd[1]);exit(0);
}
void Scene2(int pipefd[])
{// child// 3、关闭不需要的文件描述符,让父进程进行读取,子进程进行写入close(pipefd[0]);// 4、开始通信 -- 结合某种场景const std::string namestr = "hello, 我是子进程";int cnt = 1;char buffer[1024];while (true){char x = 'X';write(pipefd[1], &x, 1);std::cout << "Cnt:" << cnt++ << std::endl;}close(pipefd[1]);exit(0);
}void Scene3(int pipefd[])
{// child// 3、关闭不需要的文件描述符,让父进程进行读取,子进程进行写入close(pipefd[0]);// 4、开始通信 -- 结合某种场景const std::string namestr = "hello, 我是子进程";int cnt = 1;char buffer[1024];while (true){snprintf(buffer, sizeof(buffer), "%s, 计数器: %d, 我的PID: %d", namestr.c_str(), cnt++, getpid());write(pipefd[1], buffer, strlen(buffer));sleep(10);}close(pipefd[1]);exit(0);
}void Scene4(int pipefd[])
{// child// 3、关闭不需要的文件描述符,让父进程进行读取,子进程进行写入close(pipefd[0]);// 4、开始通信 -- 结合某种场景int cnt = 0;while (true){char x = 'X';write(pipefd[1], &x, 1);std::cout << "Cnt:" << cnt++ << std::endl;sleep(1);break;}close(pipefd[1]);exit(0);
}void Scene5(int pipefd[])
{// child// 3、关闭不需要的文件描述符,让父进程进行读取,子进程进行写入close(pipefd[0]);// 4、开始通信 -- 结合某种场景int cnt = 0;while (true){char x = 'X';write(pipefd[1], &x, 1);std::cout << "Cnt:" << cnt++ << std::endl;sleep(2);}close(pipefd[1]);exit(0);
}

父进程读取

// parent// 3、关闭不需要的文件描述符,让父进程进行读取,子进程进行写入close(pipefd[1]);// 4、开始通信 -- 结合某种场景char buffer[1024];int cnt = 0;while (true){// sleep(10); // Scene2 父进程每隔10秒进行读取 65535 2^16 // sleep(1); // Scene5int n = read(pipefd[0], buffer, sizeof(buffer) - 1); // 最多读1023个字符,需要预留出一个空位来赋给'\0'if (n > 0){buffer[n] = '\0';std::cout << "我是父进程: child give me message: " << buffer << std::endl;}else if (n == 0){std::cout << "我是父进程, 读到了文件结尾"<< std::endl;break;}else{std::cout << "我是父进程, 读异常了" << std::endl;break;}// if (cnt++ > 3) break; // Scene5 读端读一次就退出}close(pipefd[0]);int status = 0;waitpid(id, &status, 0);std::cout << "sig:" << (status &  0x7F) << std::endl; // 获取信号编号

关闭写端场景 

关闭读端场景 

 

匿名管道的特点

1、单向通信 -- 半双工

2、管道的本质是文件,因为fd的生命周期随进程,管道的生命周期也是随进程的

3、管道通信,通常用来进行具有血缘关系的进程,进行进程间通信。通常用与父子通信 -- pipe打开管道,并不清楚管道的名字,匿名管道

4、在管道通信中写入的次数,和读取的次数,不是严格匹配的 读写次数多少没有强相关 --- 表现 --- 字节流

5、具有一定的协同能力,让reader和writer能够按照一定的步骤进行通信 --- 自带同步机制

匿名管道通信的4种场景:

1、如果我们read读取完毕了所有的管道数据,如果对方不发,我们就只能等待

2、如果我们writer将管道写满了,我们还能写吗?不能

3、如果我们关闭了写端,读取完毕管道数据,再读就会read返回0

4、写端一只写读端关闭,会发生什么? 没有意义。OS不会维护无意义,低效率,或者浪费资源的事情。OS会杀死一直在写入的进程。OS会通过信号来终止进程 13)SIGPIPE


http://www.ppmy.cn/news/400543.html

相关文章

acl3000和acl2000

转载至&#xff1a;https://www.csdn.net/tags/MtjaQg2sODExOTctYmxvZwO0O0OO0O0O.html ACL&#xff08;access control list&#xff09; 定义&#xff1a;由一系列规则组成的集合。设备可以通过这些规则对数据包进行分类&#xff0c;并对不同类型的报文进行不同的处理。 分…

numpy模块之axis

axis的作用即如何理解 numpy是python进行科学计算必不可少的模块&#xff0c;随着深度学习越来越火&#xff0c;numpy也越来越流行。了解numpy的人知道&#xff0c;在numpy中&#xff0c;有很多的函数都涉及到axis&#xff0c;很多函数根据axis的取值不同&#xff0c;得到的结…

图形界面操作系统发展史

Alto 1973年4月&#xff0c;第一个可操作的Alto电脑在Xerox PARC完成。Alto是第一个把计算机所有元素结合到一起的图形界面操作系统。它使用3键鼠标、位运算显示器、图形窗口、以太网络连接。 Perq 1980年&#xff0c;Three Rivers Computer Corporation推出Perq图形工作站…

fiddler的坑--手机无法安装fiddler证书

解决办法&#xff1a; 1、使用手机自带的浏览器&#xff0c;下载fiddler证书后进行安装。 2、使用第三方浏览器&#xff0c;下载fiddler证书后进行安装。 3、从手机的文件管理器进入&#xff0c;找到下载的fiddler证书后进行安装。 4、使用设置中的“从设备存储空间安装”&…

图新地球加载倾斜模型看不清楚--一次Quadro显卡的问题排查记录

序&#xff1a; 同一份数据&#xff0c;在一台电脑上用图新地球加载查看没有任何问题&#xff0c;在另外一台电脑上加载&#xff0c;就很模糊 1.表现效果 倾斜模型加载不清晰。 2.问题分析与解决 一般遇到这种情况&#xff0c;经验判断&#xff1a; 1.首先反应就是模型数据…

centos7 防火墙设置

centos7 防火墙设置 1 概述2 防火墙服务操作2.1 查看防火墙服务状态2.2 开启防火墙2.3 关闭防火墙2.4 重启防火墙2.5 设置开机自启动2.6 查看防火墙开机启动是否成功 3 防火墙操作3.1 查看防火墙状态3.2 查看规则3.3 查看所有开放端口3.4 查看服务器操作系统端口3.5 开启端口3.…

Matlab的多维数组操作

MATLAB中的多维数组是指具有两个以上维度的数组。在矩阵中&#xff0c;两个维度由行和列表示。 每个元素由两个下标&#xff08;即行索引和列索引&#xff09;来定义。多维数组是二维矩阵的扩展&#xff0c;并使用额外的下标进行索引。例如&#xff0c;三维数组使用三个下标。前…