《Linux C编程实战》笔记:管道

从这节开始涉及进程间的通信,本节是管道。

管道是一种两个进程间进行单向通信的机制。因为管道传递数据的单向性,管道又称之为半双工管道。。管道的这一特点决定了其使用的局限性。

  • 数据只能由一个进程流向另一个进程;如果要进行全双工通信,需要建立两个管道。
  • 管道只能用于父子进程或者兄弟进程间的通信,也就是说管道只能用于具有亲缘关系的进程间的通信,无亲缘关系的进程不能使用管道。

除了以上局限性,管道还有一些不足。例如管道没有名字,管道的缓冲区大小是受限制的,管道所传送的是无格式的字节流。这就要求管道的输入方和输出方事先约定好数据的格式。虽然有这么多不足,但对于一些简单的进程间的通信,管道还是可以胜任的。

使用管道进行通信时,两端的进程向管道读写数据是通过创建管道时,系统设置的文件描述符进行的。因此对于管道两端的进程来说,管道就是一个特殊的文件,这个文件只存在于内存中。在创建 管道时,系统为管道分配一个页面作为数据缓冲区,进行管道通信的两个进程通过读写这个缓冲区来进行通信。

通过管道通信的两个进程,一个进程向管道写数据,另一个从管道的另一端读数据。写入的数据每次都添加在管道缓冲区的末尾,读数据的时候都是从缓冲区的头部读出数据。

管道的创建与读写

管道的创建

Linux下创建管道可以用函数pipe来完成。该函数如果成功调用返回0,并且数组中将包含两个新的文件描述符;如有错误发生,则返回-1.

#include<unistd.h>
int pipe(int fd[2]);

管道两端可分别用描述符fd[0]以及fd[1]来描述。需要注意的是,管道两端的任务是固定的,一段只能用来读,用描述符fd[0]表示,称其为管道读端;另一端只能用于写,由描述符fd[1]来表示,称其为管道写端。如果试图从管道写端读数据,或另一种操作都将导致出错。

管道是一种文件,因此对文件操作的I/O函数都可以用于管道,如read,write等。

注意:管道一旦创建成功,就可以作为一般的文件来使用。对一般文件操作的函数也适用于管道。

管道的一般用法是,进程在使用fork函数创建子进程前先创建一个管道,该管道用于在父子进程间的通信,然后创建子进程,之后父进程关闭管道的读端,子进程关闭管道的写端。父进程负责向管道写数据而子进程负责读数据。当然也可以反过来父进程读子进程写。

从管道中读数据

如果某进程要读取管道中的数据,那么该进程应当关闭fd1, 同时向管道写数据的进程应当关闭fd0。 因为管道只能用于具有亲缘关系的进程间的通信,在各进程进行通信时,它们共享文件描
述符。在使用前,应及时地关闭不需要的管道的另一端,以避免意外错误的发生。
进程在管道的读端读数据时,如果管道的写端不存在,则读进程认为已经读到了数据的末尾,读函数返回读出的字节数为0;管道的写端如果存在,且请求读取的字节数大于PIPE_BUF, 则返回管道中现有的所有数据;如果请求的字节数不大于PIPE_BUF,则返回管道中现有的所有数据(此时,管道中数据量小于请求的数据量),或者返回请求的字节数(此时,管道中数据量大于等于请求的数据量)。
注意: PIPE_BUF在include/linux/limits.h中定义,不同的内核版本可能会有所不同。

从管道中写数据

如果某进程希望向管道中写入数据,那么该进程应该关闭fd0文件描述符,同时管道另一端的进程关闭fd1。向管道中写入数据时,Linux不保证写入的原子性(原子性是指操作在任何时候都不能被任何原因所打断,操作要么不做要么就一定完成)。管道缓冲区一有空闲区域, 写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直被阻塞等待。
在写管道时,如果要求写的字节数小于等于PIPE_BUF,则多个进程对同一管道的写操作不会交错进行。但是,如果有多个进程同时写一个管道,而且某些进程要求写的字节数超过PIPE_BUF所能容纳时,则多个写操作的数据可能会交错。

注意:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号。应用程序可以处理也可以忽略该信号,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错,错误码为EPIPE。

示例程序1

演示管道的创建和读写

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
void read_from_pipe(int fd){char message[100];read(fd,message,100);printf("read from pipe:%s",message);
}
void wtire_to_pipe(int fd){const char *message="hello pipe!\n";write(fd,message,strlen(message)+1);//加1是确保'\0'也写进去了
}
int main(int argc,char **argv){int fd[2];pid_t pid;int stat_val;if(pipe(fd)!=0){//必须在fork前创建管道printf("create pipe failed!\n");exit(1);}pid=fork();switch (pid){case -1:printf("fork error!\n");break;case 0:close(fd[1]);//子进程是读数据,所以要关闭fd1read_from_pipe(fd[0]);exit(0);default://default是父进程执行的部分close(fd[0]);//父进程是写,所以要关闭fd0wtire_to_pipe(fd[1]);wait(&stat_val);break;}return 0;}

执行结果:

对管道的操作和对一般文件没什么区别。对fork,read,write和wait不了解的可以看我以前的文章。

在管道里,默认read是阻塞的,也就是说如果管道没有数据可读,read函数会一直等待。这样就没有说子进程先执行读父进程再执行写的问题了,因为子进程会一直等到父进程把数据写到管道再读。

示例程序2

管道是半双工的,可以用两个管道来实现全双工通信。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
void child_rw_pipe(int readfd,int writefd){const char *message1="from child process!\n";write(writefd,message1,strlen(message1)+1);char message2[100];read(readfd,message2,100);printf("child process read from pipe:%s",message2);
}
void parent_rw_pipe(int readfd,int writefd){const char *message1="from parent process!\n";write(writefd,message1,strlen(message1)+1);char message2[100];read(readfd,message2,100);printf("parent process read from pipe:%s",message2);
}
int main(int argc,char **argv){int pipe1[2],pipe2[2];pid_t pid;int stat_val;printf("realize full-duplex communication:\n\n");if(pipe(pipe1)){printf("pipe1 failed\n");exit(1);}if(pipe(pipe2)){printf("pipe2 failed\n");exit(1);}pid=fork();switch (pid){case -1:printf("fork error!\n");exit(1);case 0:close(pipe1[1]);close(pipe2[0]);child_rw_pipe(pipe1[0],pipe2[1]);exit(0);default:close(pipe1[0]);close(pipe2[1]);parent_rw_pipe(pipe2[0],pipe1[1]);wait(&stat_val);exit(0);}
}

运行结果:

代码就是多了一个管道,和上一个几乎一样。

dup()和dup2()

前面的例子,子进程可以直接共享父进程的文件描述符,但是如果子进程调用exec去执行另外一个应用程序时,就不能再共享了。这种情况可以将子进程中的文件描述符重定向到标准输入,当新执行的程序从标准输入获取数据时实际上是从父进程中获取数据。

这两个函数则是提供了复制文件描述符的功能,在《Linux C编程实战》笔记:一些系统调用-CSDN博客已经介绍过。

具体使用如下所示

//用dup
pid=fork();if(pid==0){//关闭子进程标准输出close(1);//复制管道写端到标准输出,这样像printf就会输出到管道dup(fd[1]);execve("your_process",argv,environ);}
//用dup2的例子
pid=fork();if(pid==0){close(1);dup2(fd[1],1);execve("your_process",argv,environ);}

管道的应用实例

管道的一种常见的用法,在父进程创建子进程后向子进程传递参数。例如,一个应用软件有一个主进程和很多个不同的子进程。主进程创建子进程后,在子进程调用exec函数执行一个新程序之前,通过管道给即将执行的程序传递命令行参数,子进程根据床来的参数进行初始化或其他操作

示例程序3

首先是子进程之后要执行的代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
int main(int argc,char **argv){int n;char buffer[1024];while (1){//从标准输入中读,父进程会修改子进程的标准输入if((n=read(STDIN_FILENO,buffer,1024))>0){buffer[n]='\0';printf("ctrlprocess receive:%s\n",buffer);if(!strcmp(buffer,"exit"))exit(0);if(!strcmp(buffer,"getpid")){printf("My pid:%d\n",getpid());sleep(3);exit(0);}}}}

然后是主进程

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include <sys/wait.h>
#include<unistd.h>int main(int argc,char **argv,char **environ){int fd[2];pid_t pid;int stat_val;if(argc<2){printf("wrong parameters");exit(0);}if(pipe(fd)){perror("pipe failed");exit(1);}pid=fork();switch (pid){case -1:perror("fork failed!\n");exit(1);case 0://子进程先关闭自己的标准输入close(0);//标准输入重定向到管道的读入端dup(fd[0]);execve("ctrlprocess",argv,environ);exit(0);default://这里是父进程close(fd[0]);write(fd[1],argv[1],strlen(argv[1]));break;}wait(&stat_val);exit(0);
}

执行结果:

顺带一提,如果直接执行./ctrlprocess 的话,输入getpid或者exit都是进不去if(strcmp...)的,因为这时候的标准输入还是命令行,命令行里输入getpid,实际上读入的是"getpid\n",这会导致strcmp比较不准。而通过父进程的argv参数,这个参数是不会带\n的,写入管道也不会带\n,能确保子进程通过标准输入(也就是管道)读入的是完整的字符串,只需要在最后加\0就行了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/656449.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理学习| Day1

学习目标&#xff1a; 博主介绍: 27dCnc 专题 : 计算机组成原理 &#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d; ☆*: .&#xff61;. o(≧▽≦)o .&#x…

分享4款不能错过的修改照片尺寸的软件!

在当今这个数字化时代&#xff0c;照片已经成为我们分享生活、表达观点的重要方式。但是&#xff0c;你是否曾遇到过这样的问题&#xff1a;一张精美的照片因为尺寸不合适而无法在朋友圈中展现出最佳效果&#xff1f;不用担心&#xff0c;今天我们就来聊聊那些可以帮助你轻松修…

获取鼠标点击图片时候的坐标,以及利用html 中的useMap 和area 实现图片固定位置的点击事件

一 编写原因 应项目要求&#xff0c;需要对图片的固定几个位置分别做一个点击事件&#xff0c;响应不同的操作&#xff0c;如下图&#xff0c;需要点击红色区域&#xff0c;弹出不同的提示框&#xff1a; 二 获取点击图片时候的坐标 1. 说明 实现这以上功能的前提是需要确定需…

JVM-类的生命周期

类的生命周期概述 类的生命周期描述了一个类加载、使用、卸载的整个过程。整体可以分为&#xff1a; 加载 连接&#xff0c;其中又分为验证、准备、解析三个子阶段 初始化 使用 卸载 加载阶段 加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方…

【JavaScript】fetch

fetch Response Headers ajax&axios&fetch的关系: ajax&#xff1a;ajax 是一种基于原生 JavaScript 的异步请求技术。它使用 XMLHttpRequest 对象来发送请求和接收响应。 axios&#xff1a;axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;可以在浏览器和 Node…

STM32——中断系统和外部中断EXTI

一、中断 1.1中断系统 中断系统是管理和执行中断的逻辑结构&#xff1b; 1.2中断 系统在执行主程序过程中&#xff0c;出现了特定的触发条件&#xff08;触发源&#xff09;&#xff0c;系统停止执行当前程序&#xff0c;转而去执行中断程序&#xff0c;执行完毕后&#xf…

什么是原型链?如何继承?

原型&#xff1a; 每个对象都可以有一个原型_proto_&#xff0c;这个原型还可以有它自己的原型&#xff0c;以此类推&#xff0c;形成一个原型链。查找特定属性的时候&#xff0c;我们先去这个对象里去找&#xff0c;如果没有的话就去它的原型对象里面去&#xff0c;如果还是没…

OllyDebug的使用方法. IDA Pro分析程序的控制流图,可以找到不同的函数入口点. 在汇编代码中定位特定函数可能是一个耗时且复杂的过程

实战 “OllyDbg” 是一个流行的Windows平台上的汇编级调试器&#xff0c;用于调试和分析二进制程序&#xff0c;尤其是用于逆向工程目的。使用OllyDbg的基本步骤如下&#xff1a; 安装和打开OllyDbg&#xff1a;首先&#xff0c;您需要在您的计算机上安装OllyDbg。完成安装后&…

HCIA学习作业五

拓扑图&#xff1a; PC端 PC1>ipconfig PC2>ipconfig PC3>ipconfig PC4>ipconfig PC>ping PC1>ping 192.168.1.125 PC1>ping 192.168.1.254 PC1>ping 192.168.1.253 PC2>ping 192.168.1.125 PC2>ping 192.168.1.253 PC3>ping 192.168.1.126…

java程序员怎么完善自己各个方面的能力?

java程序员怎么完善自己各个方面的能力? 在开始前我分享下我的经历&#xff0c;刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;两年时间从3k薪资涨到18k的&#xff0c; 我师父给了一些java学习方法和资料&#xff0c;让我不断提升自己&#xff0c;感谢帮助…

Python计算机二级/Python期末考试 刷题(一)

收集了一些经典Python计算机二级和Python期末考试题库 整理不易&#xff0c;大家点赞收藏支持一下 祝大家计算机二级和期末考试都高分过 目录 一、填空 二、选择 三、程序设计 一、填空 1.序列元素的编号称为索引&#xff0c;索引值从【1】开始&#xff0c;访问序列元素时将…

vue 使用 v-viewer 用于图片浏览的Vue组件,支持旋转、缩放、翻转等操作,基于viewer.js。

作者连接 npm&#xff1a; npm install v-viewerlegacy viewerjs main.js 引入&#xff1a; // 引入Viewer插件 import VueViewer, { directive as viewerDirective } from v-viewer; // 引入Viewer插件的图片预览器的样式 import viewerjs/dist/viewer.css; // 使用Viewer图片…

Pull模式和Push模式

Pull模式是一种消息消费模式&#xff0c;其中客户端主动从服务端拉取数据。 优点&#xff1a;客户端可以根据自己的消费能力来消费数据&#xff0c;不存在消息堆积的情况。 缺点&#xff1a;消息处理可能不及时&#xff0c;可能存在大量无效请求&#xff0c;客户端需要考虑拉取…

python对图片或文件的操作

一. base64 与图片的相互转换 1. base64 转图片 import base64 from io import BytesIO from PIL import Image# base64 编码的图像数据&#xff08;示例&#xff09; base64_data "iVBn9DHASKJDjDsdSADSf8lgg"# 将 base64 编码的字符串解码为二进制数据 binary_d…

不同的强化学习模型适配与金融二级市场的功能性建议

DQN ES DDPG A2C TD3 SAC QMIX MADDPG PPO CQL IMPALA 哪个模型适合进行股票操作 在考虑使用哪种模型进行股票操作时&#xff0c;需要考虑模型的特点、适用场景以及实现复杂度等因素。以下是对您列出的几种强化学习模型的简要概述&#xff0c;以帮助您做出选择&#xff1a; DQ…

【C++】类与对象(二)特殊成员函数

前言 类与对象&#xff08;二&#xff09; 文章目录 一、特殊成员函数二、构造函数三、析构函数四、拷贝构造函数五、拷贝赋值运算符 一、特殊成员函数 如果在类的声明中未显式提供某个成员函数的定义&#xff0c;编译器会自动生成一个默认实现。 这包括默认构造函数、默认析构…

Android studio打包apk比较大

1.遇到的问题 在集成linphone打包时发现有118m&#xff0c;为什么如此之大额。用studio打开后发现都是c不同的pu架构。 2.解决办法 增加ndk配置&#xff0c;不选配置那么多的cpu结构&#xff0c;根据自己需要调整。 defaultConfig { applicationId "com.matt.linphoneca…

备战蓝桥杯---数据结构与STL应用(基础3)

今天我们主要介绍的是pair,string,set,map pair:我们可以把它当作一个结构体&#xff1a; void solve(){pair<int int> a;//创建amake_pair(1,2);//添加元素cout<<a.first<<endl<<a.second<<endl;}//输出 当然&#xff0c;它也可以嵌套&#…

51单片机点灯

51单片机点灯 1.点亮LED灯 #include "reg52.h"sbit ledOne P3^7;void main() {//灯亮&#xff0c;给一个P3.7低电平ledOne 0; }给LED1对应标号的P3^7一个低电平&#xff0c;就能点亮LED灯2.LED灯闪烁 #include "reg52.h"sbit ledOne P3^7;void Delay…

ai电销机器人的优势

随着电销机器人的不断深入研究&#xff0c;电销机器人得到了很大的发展&#xff0c;逐渐走入了许多公司中。 机器人不像人一样会闹情绪会累&#xff0c;可以在客户开发上一如既往的开发客户&#xff0c;使用真心录音的方式能让客户听着像和人沟通一样&#xff0c;完成一些流程…