《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)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方…

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

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

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;感谢帮助…

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图片…

【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;它也可以嵌套&#…

python笔记10

1、继承 继承是面向对象编程中的一个重要概念&#xff0c;它允许一个类&#xff08;子类&#xff09;继承另一个类&#xff08;父类&#xff09;的属性和方法。通过继承&#xff0c;子类可以重用父类的代码&#xff0c;并且有机会添加新的属性和方法&#xff0c;或者重写父类的…

使用PowerBI 基于Adventure Works案例分析

Adventure Works案例分析 前言 数据时代来临&#xff0c;但一个人要顺应时代的发展是真理。 数据分析的核心要素 那数分到底是什么&#xff1f; 显然DT 并不等同于 IT&#xff0c;我们需要的不仅仅是更快的服务器、更多的数据、更好用的工具。这些都是重要的组成部分&…

堆宝塔

L2-1 堆宝塔 分数 25 作者 陈越 单位 浙江大学 堆宝塔游戏是让小朋友根据抓到的彩虹圈的直径大小&#xff0c;按照从大到小的顺序堆起宝塔。但彩虹圈不一定是按照直径的大小顺序抓到的。聪明…

AI特训一:为什么要学习AI

我们先了解什么是AI AI&#xff08;人工智能&#xff09;是指计算机系统经过学习和推理能够模拟人类智能行为的一种技术。AI利用机器学习、深度学习、自然语言处理等技术&#xff0c;能够分析大量的数据、识别模式、做出决策和预测 AI有哪些强大之处 处理大量数据&#xff1a…

专栏:数据库、中间件的监控一网打尽

前言 对于数据库、中间件的监控&#xff0c;目前社区里最为完善的就是 Prometheus 生态的各个 Exporter&#xff0c;不过这些 Exporter 比较分散&#xff0c;不好管理&#xff0c;如果有很多目标实例需要监控&#xff0c;就要部署很多个 Exporter&#xff0c;要是能有一个大一…

蓝桥杯嵌入式——测量两路PWM频率和占空比

原理&#xff1a;在通道1&#xff0c;TIM_CH1上会输入PWM波&#xff0c;在每个上升沿来的时候会发生三个动作&#xff0c;第一个动作会触发一个中断&#xff0c;第二个动作会把CNT计数值捕获&#xff0c;第三个动作会把CNT的值清0&#xff0c; 要测量占空比则需要打开TI1FP2&a…

经济学基础入门,从《小岛经济学》看经济的演变

一直在学习怎么赚钱&#xff0c;没有专业的人指导。于是就想着先学习一下经济学相关的知识吧&#xff01;无意间看到大家推荐的这本书籍&#xff0c;一本很适合经济学入门的经济《小岛经济学》&#xff0c;这本书以故事的方式&#xff0c;讲解了经济演变过程。而且大约只需要2到…

对嵌入式系统、GCC、的理解

1、嵌入式系统通用硬件组成 2、Linux系统的嵌入式的开发流程 3、Linux系统的嵌入式的结构 4、文件IO和标准IO&#xff1f; 5、为什么需要交叉编译 1、嵌入式系统硬件的限制&#xff08;CPU处理能力不如电脑、存储空间小、网络带宽小不利于传输、安全性不如电脑、能耗问题&…

【计算机网络】——TCP协议

&#x1f4d1;前言 本文主要是【计算机网络】——传输层TCP协议的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句…