Linux管道学习(无名管道)

news2025/1/10 14:22:14

目录

1、概述

2、管道的创建

3、管道读写行为

3.1、管道读

3.2、管道写

4、管道用于兄弟进程之间的通讯


        在linux中管道有两种,一是无名管道(匿名管道),第二种是有名管道;无名管道主要用于有血缘关系的父子进程间通信,有名管道则不受该限制,可用于任意进程之间的通信;这里我们主要学习无名管道。

1、概述

        创建无名管道的函数如下:

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

        调用失败返回-1,成功返回0;调用成功时该函数会创建一个单向的管道用于进程之间的通讯,返回的管道包含读端和写端,其中fd[0]用于读,fd[1]用于写,写到fd[1]的数据会被内核保存到缓冲区,直到fd[0]读走数据。

2、管道的创建

        代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret;

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}

	printf("Hello pipe, fd[0] = %d, fd[1] = %d\n", fd[0], fd[1]);


	return 0;
}

        很简单,直接调用pipe并传入一个文件描述符数组,如果出错则退出,调用成功则返回两个可用的文件描述符,其中fd[0]用于读,fd[1]用于写。

        gcc编译:gcc pipe.c -o pipe,运行./pipe得出如下运行结果:

3、管道读写行为

        由于管道是单向通讯的,所以在使用的时候会有一些限制。

  • 管道读

如果管道中有数据,则读取实际能读到的数据;

如果管道中无数据,此时有两种情况:一是有管道写端,此时会阻塞直到写端写入数据;二是没有管道写端(写端被关闭),因为没有写端,永远不会有数据写入,此时返回0。

  • 管道写

如果管道满了,则写阻塞,直到能写入。

如果管道未满,此时有两种情况:一是没有管道读端,此时异常终止(SIGPIPE导致);二是有读端,则返回实际写入的字节数。

        下面简单列举几个例子,主要是父子进程之间通讯。

3.1、管道读

         代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret;
	char buf[1024];
	char *info = "Hello Pipe(from read)\n";

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}
	ret = fork();

	if (0 == ret) {
		printf("I'm child\n");
		close(fd[1]);
		
		while ((ret = read(fd[0], &buf, 1024)) > 0) {
			write(STDOUT_FILENO, &buf, ret);
		}
		close(fd[0]);
	} else if (ret > 0) {
		printf("I'm parent\n");
		close(fd[0]);

		write(fd[1], info, strlen(info));
		
		close(fd[1]);
	} else {
		perror("fork error!\n");
		exit(1);
	}


	return 0;
}

         首先调用pipe创建了读写管道的读端和写端,然后调用fork创建了一个子进程,如果ret 为0,说明是在子进程,如果ret大于0,说明是在父进程,如果ret小于0则说明fork出错,退出程序。

        代码中申明了一个字符串"Hello Pipe(from read)",由于父进程用于写,子进程用于读,所以父进程首先关闭了fd[0],然后用fd[1]往管道中写入"Hello Pipe(from read)",之后关闭fd[1]。

        由于子进程用于读,所以一开始关闭了管道的写端fd[1],然后循环从fd[0]中读入数据存储到buf中并打印到屏幕,直到读完管道中的数据,最后关闭管道读端fd[0]。

        运行结果如下:

        上面演示的是管道有数据的情况,立刻就读出了管道中的数据;下面考虑以下两种情况

  1. 管道无数据 有写端
  2. 管道无数据 无写端

        第一种情况,管道将会读阻塞,可以运行如下实例看看:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret;
	char buf[1024];
	char *info = "Hello Pipe(from read)\n";

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}
	ret = fork();

	if (0 == ret) {
		printf("I'm child\n");
		close(fd[1]);
		
		while ((ret = read(fd[0], &buf, 1024)) > 0) {
			write(STDOUT_FILENO, &buf, ret);
		}
		close(fd[0]);
		printf("After read\n");
	} else if (ret > 0) {
		printf("I'm parent\n");
		close(fd[0]);

		//write(fd[1], info, strlen(info));
		
		sleep(10);
		printf("After sleep\n");
		close(fd[1]);
	} else {
		perror("fork error!\n");
		exit(1);
	}


	return 0;
}

        代码结构和之前一样,为了构造管道无数据的情况,父进程中没有写入数据,而是sleep(10),然后10s后输出"After sleep"且关闭管道写端fd[1];子进程保持不变,由于管道中一直没有数据,会导致子进程管道的读端一直阻塞直到父进程关闭管道写端fd[1]。

        程序输出如下:

        第二种情况,管道无数据无写端,此时读管道将直接返回0,因为确实没有数据可以读;其实第一种情况演示里面也包含了这种情况,管道关闭的时候,也就是没有写端,读端就返回了。

3.2、管道写

        代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret;
	char buf[1024];
	char *info = "Hello Pipe(from read)\n";

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}
	ret = fork();

	if (0 == ret) {
		printf("I'm child\n");
		close(fd[1]);
		
		while ((ret = read(fd[0], &buf, 1024)) > 0) {
			write(STDOUT_FILENO, &buf, ret);
		}
		close(fd[0]);
		printf("After read\n");
	} else if (ret > 0) {
		printf("I'm parent\n");
		close(fd[0]);

		int n = write(fd[1], info, strlen(info));
		
		printf("parent write: %d byte\n", n);
		close(fd[1]);
	} else {
		perror("fork error!\n");
		exit(1);
	}


	return 0;
}

        逻辑结构与管道读一致,这里仅仅是在写管道的时候返回写入的管道字节数,运行结果如下:

        上面演示了管道未满,且有读端的情况;那么没有读端的时候表现如何呢?前面我们说过管道未满,没有读端的时候写入管道将会报错,现在来看看这种情况,将代码改为如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret;
	char buf[1024];
	char *info = "Hello Pipe(from read)\n";

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}
	ret = fork();

	if (0 == ret) {
		printf("I'm child\n");
		close(fd[1]);
		/*	
		while ((ret = read(fd[0], &buf, 1024)) > 0) {
			write(STDOUT_FILENO, &buf, ret);
		}
		*/
		close(fd[0]);
		printf("After close\n");
	} else if (ret > 0) {
		printf("I'm parent\n");
		close(fd[0]);
		sleep(2);

		int n = write(fd[1], info, strlen(info));
		
		printf("parent write: %d byte\n", n);
		close(fd[1]);
	} else {
		perror("fork error!\n");
		exit(1);
	}
	printf("end of process\n");

	return 0;
}

        子进程一开始就关闭了管道的读端和写端,父进程则一开始关闭读端,睡眠2s等待子进程关闭管道读端后再写入数据,此时就营造了有写端没读端的情况,在命令行模式输出如下:

        只输出了一行"end of process",明显是有一个进程没有运行完整就退出了,我们在gdb下运行看看,输出如下:

         在gdb下以及把进程的错误输出了,SIGPIPE导致了进程退出,其实也可以理解,因为没读端,此时写入的数据相当于是无用数据,内核干脆把问题抛了出来;开发者其实可以捕获这个信号然后进行自定义处理,此处不再展开。 

        管道写还有一种情况是,管道已经满了,这种情况如何演示呢?很遗憾,没法演示这种情况,因为内核会在管道快要满的时候动态扩容,此时管道会恢复到正常状况,对于应用来说就是管道可以一直写入,所以我们看不到这种情况。

4、管道用于兄弟进程之间的通讯

        前面介绍的是用于父子进程之间的通讯,现在来看看用于兄弟进程之间如何通讯,也是在前面例子的基础上,我们在父进程中创建两个子进程,然后在两个子进程之间使用管道进行通讯,修改后的代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret, i;
	char buf[1024];
	pid_t status;
	char *info = "Hello Pipe(from read)\n";

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}
	for (i = 0; i < 2; i++) {
		ret = fork();
		if (0 == ret) {
			break;
		} else if (ret < 0) {
			perror("fork error!\n");
			exit(1);
		}
	}

	if (0 == i) { // child 1
		printf("I'm child 1\n");
		close(fd[1]);
		
		while ((ret = read(fd[0], &buf, 1024)) > 0) {
			write(STDOUT_FILENO, &buf, ret);
		}

		close(fd[0]);
	} else if (1 == i) { // child 2
		printf("I'm child 2\n");
		close(fd[0]);

		int n = write(fd[1], info, strlen(info));
		close(fd[1]);
	} else if (2 == i) {
		printf("I'm parent\n");
		//close(fd[0]);
		//close(fd[1]);

		for (int j = 0; j < 2; j++) {
			status = wait(NULL);
			printf("parent wait %d\n", status);
		}
	}

	printf("end of process\n");

	return 0;
}

        子进程2写入,子进程1读出,所以子进程2一开始先关闭管道读端fd[0],然后往管道的写端fd[1]写入数据;子进程负责1读取数据,所以子进程1刚开始就关闭管道写入端fd[1],然后从管道读入端fd[0]读取数据,下面是运行结果:

        从运行结果来看是有问题的,两个子进程加上一个父进程,应该输出3次"end of process",而这里只有一次,同时父进程也只回收了一个子进程;代码在父子进程通讯的时候正常,变成兄弟进程之后就不正常了,为什么会这样呢?

         前面我们说过管道数据是单向流动的,从写端到读端;在当前我们的这个场景下,其实出除了两个子进程之间从子进程2向子进程1的数据流动,父进程同样也持有了管道的读写端,从而破坏了管道的单向流动,所以我们需要关闭父进程中的管道读写端,保证数据的单向流动,修改后的代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

int main(int argc, char *argv[]) {
	pid_t fd[2];
	int ret, i;
	char buf[1024];
	pid_t status;
	char *info = "Hello Pipe(from read)\n";

	ret = pipe(fd);	
	if (-1 == ret) {
		perror("pipe error!\n");	
		exit(1);
	}
	for (i = 0; i < 2; i++) {
		ret = fork();
		if (0 == ret) {
			break;
		} else if (ret < 0) {
			perror("fork error!\n");
			exit(1);
		}
	}

	if (0 == i) { // child 1
		printf("I'm child 1\n");
		close(fd[1]);
		
		while ((ret = read(fd[0], &buf, 1024)) > 0) {
			write(STDOUT_FILENO, &buf, ret);
		}

		close(fd[0]);
	} else if (1 == i) { // child 2
		printf("I'm child 2\n");
		close(fd[0]);

		int n = write(fd[1], info, strlen(info));
		close(fd[1]);
	} else if (2 == i) {
		printf("I'm parent\n");
		close(fd[0]);
		close(fd[1]);

		for (int j = 0; j < 2; j++) {
			status = wait(NULL);
			printf("parent wait %d\n", status);
		}
	}

	printf("end of process\n");

	return 0;
}

        运行结果如下:

        如结果所示,此时所有进程都能正常终止了。 

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

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

相关文章

八、Kotlin 反射

1. 什么是反射 反射是允许在运行时期访问 程序结构 的一类特性&#xff08;程序结构包括&#xff1a;类、接口、方法、属性等&#xff09;。 2. 反射的依赖库 Kotlin 中不仅自己实现了一套 Kotlin 反射的 API&#xff0c;还可以使用 Java 反射的 API。 Kotlin 反射的 API 是…

day33WEB 攻防-通用漏洞文件上传中间件解析漏洞编辑器安全

目录 一&#xff0c;中间件文件解析漏洞-IIS&Apache&Nginx -IIS 6 7 文件名 目录名 -Apache 换行解析 配置不当 1、换行解析-CVE-2017-15715 2、配置不当-.htaccess 配置不当 -Nginx 文件名逻辑 解析漏洞 1、文件名逻辑-CVE-2013-4547 2、解析漏洞-nginx.conf …

ECharts 中 Legend自定义可以使用svg标签

效果图&#xff1a; legend图例加载svg标签 在ECharts中&#xff0c;图例(legend)组件的formatter属性允许你自定义图例文本的格式。但是&#xff0c;formatter属性不支持直接加载SVG标签或Html。它接受一个字符串或者一个函数作为输入&#xff0c;并不能解析或渲染SVG。 如果…

探索 DevOps 中的自动化技术

DevOps 是一种强调开发与 IT 运营之间合作的软件开发范式&#xff0c;主要依靠自动化来优化流程、提高生产力并确保及时、可靠的软件交付。以下是对 DevOps 不可或缺的关键自动化技术的探索&#xff1a; 1.持续集成/持续部署&#xff08;CI/CD&#xff09; 在 DevOps 领域&…

WebSocket服务端数据推送及心跳机制(Spring Boot + VUE):

文章目录 一、WebSocket简介&#xff1a;二、WebSocket通信原理及机制&#xff1a;三、WebSocket特点和优点&#xff1a;四、WebSocket心跳机制&#xff1a;五、在后端Spring Boot 和前端VUE中如何建立通信&#xff1a;【1】在Spring Boot 中 pom.xml中添加 websocket依赖【2】…

Java程序设计(猜拳、猜数字、猜硬币)

前言 Java实现简单的程序设计&#xff0c;包含猜拳、猜数字和猜硬币&#xff0c;实现玩家和电脑之间的互动&#xff0c;电脑每次出的结果实现随机&#xff0c;玩家选择需要玩的游戏&#xff08;猜拳、猜数字、猜硬币&#xff09;&#xff0c;选择需要进行的局数。 界面设计 程…

pcl+vtk(十四)vtkCamera相机简单介绍

一、vtkCamera相机 人眼相当于三维场景下的相机&#xff0c; VTK是用vtkCamera类来表示三维渲染场景中的相机。vtkCamera负责把三维场景投影到二维平面&#xff0c;如屏幕、图像等。 相机位置&#xff1a;即相机所在的位置&#xff0c;用方法vtkCamera::SetPosition()设置。 相…

k8s的安全机制

k8s是分布式集群管理工具&#xff0c;k8s作用是容器编排 1、安全机制核心&#xff1a;API server。API server作为整个集群内部通信的中介&#xff0c;也是外部控制的入口&#xff0c;所有的安全机制都是围绕api sserver来进行设计的。请求api server资源要满足3个条件&#x…

数据结构篇-02:最小栈

对于这道题&#xff0c;除了 getMin 外的功能&#xff0c;传统的 栈 结构中都有&#xff0c;所以重点在于如何实现 getMin 方法。 有两类方法&#xff1a;使用辅助栈/不使用辅助栈 使用辅助栈的解法一 定义一个 栈 来实现常规功能&#xff0c;另外定义一个栈&#xff08;最小…

如何配置点击抖音直播小风车跳转到微信公众号?

随着抖音直播间的普及&#xff0c;越来越多的品牌选择通过直播进行宣传推广。然而&#xff0c;直播间主播的氛围营造是一项极具挑战性的任务。如果观众的热情无法被调动起来&#xff0c;直播间很容易陷入沉寂&#xff0c;难以吸引流量。 为了最大化利用流量&#xff0c;许多品牌…

智能充电桩,机器人 wifi蓝牙 解决方案

新联鑫威低功耗高性价比sdio wifi/蓝牙combo的模块单频2.4g的CYWL6208&#xff0c;双频2.4g/5g CYWL6312可以应用到一些低延时 高性能 低功耗 联网需求的交流直流充电桩&#xff0c;扭力扳手&#xff0c;agv机器人&#xff0c;目前支持主流的stm32F4/GD32F4 瑞萨 psoc的主控&am…

道合顺:一站式电子元器件采购商城

欢迎来到道合顺&#xff0c;您专属的电子元器件采购商城。我们为您提供广泛的元器件选择&#xff0c;包括各类芯片、传感器、电容电阻、连接器等&#xff0c;以满足您项目的需求。 最新价格实时查询 通过道合顺电子网&#xff0c;您可以随时随地查询各类电子元器件的最新价格…

外汇天眼:QoinTech误信假老师话术投资外汇,惨遭黑平台滑点爆仓拒出金

去年11月与12月&#xff0c;外汇天眼先后发布了「钓鱼广告诱加投资群组&#xff0c;限制出金逼迫缴分成费」与「假投顾诱导投资黄金获利&#xff0c;黑平台操作爆仓狠诈700万」这2篇文章&#xff0c;曝光黑平台QoinTech的诈骗手法&#xff0c;呼吁投资人不要上当&#xff0c;没…

你对 TypeScript 中枚举类型的理解?应用场景?

文章目录 一、是什么二、使用数字枚举字符串枚举异构枚举本质 三、应用场景参考文献 一、是什么 枚举是一个被命名的整型常数的集合&#xff0c;用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型 通俗来说&#xff0c;枚举就是一个对象的所有可能…

LeetCode 热题 100 | 普通数组

目录 1 53. 最大子数组和 2 56. 合并区间 3 189. 轮转数组 4 238. 除自身以外数组的乘积 5 41. 缺失的第一个正数 菜鸟做题第二周&#xff0c;语言是 C 1 53. 最大子数组和 题眼&#xff1a;“子数组是数组中的一个连续部分。” 遍历数组&#xff0c;问每一个元素…

EIGRP实验

实验大纲 一、基本配置 1.构建网络拓扑结构图 2.路由器基本配置 3.配置PC 4.测试连通性 5.保存配置文件 二、配置EIGRP 1.查看路由表 2.配置EIGRP动态路由 3.查看路由器路由表 4.测试网络连通性 5.查看所有路由器的路由协议 6.保存配置文件 三、配置OSPF 1.配置…

【基于电商履约场景的 DDD 实战】DDD领域驱动设计的优势分析以及与MVC架构对比

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复…

LeetCode.2859. 计算 K 置位下标对应元素的和

题目 题目链接 分析 这道题的题意很明确。就是求每一个下标的二进制中1的个数为k的下标所对应的元素值之和。 Java 中有 库函数 Integer.bitCount(num)&#xff0c;这个函数的返回值就是 num 中 1 的个数。 代码 class Solution {public int sumIndicesWithKSetBits(List…

如何训练和导出模型

介绍如何通过DI-engine使用DQN算法训练强化学习模型 一、什么是DQN算法 DQN算法&#xff0c;全称为Deep Q-Network算法&#xff0c;是一种结合了Q学习&#xff08;一种价值基础的强化学习算法&#xff09;和深度学习的算法。该算法是由DeepMind团队在2013年提出的&#xff0c;…

开发板连接错误: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

1.出现错误 scp rkmedia_vi_venc_rtsp_test_sp root192.168.10.198:/home/sunpeng出现错误 2.错误原因&#xff1a;警告&#xff1a;远程主机标识已更改&#xff01; 1&#xff09;重新烧录开发板系统&#xff0c;导致IP地址变化。 2&#xff09;报警错误-中文&#xff08;警…