《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]);//子进程是读数据,所以要关闭fd1
        read_from_pipe(fd[0]);
        exit(0);
    default://default是父进程执行的部分
        close(fd[0]);//父进程是写,所以要关闭fd0
        wtire_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就行了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

快速掌握PHP:用这个网站,让学习变得简单有趣!

介绍&#xff1a;PHP是一种广泛使用的开源服务器端脚本语言&#xff0c;特别适合Web开发。 PHP&#xff0c;全称为Hypertext Preprocessor&#xff0c;即超文本预处理器&#xff0c;是一种嵌入在HTML中的服务器端脚本语言。它主要用于管理动态内容和数据库交互&#xff0c;使得…

双非本科准备秋招(9.3)—— JVM2

学这个JVM还是挺抽象的&#xff0c;不理解的东西我尽量记忆了&#xff0c;毕竟刚接触两天&#xff0c;也没遇到过实际应用场景&#xff0c;所以学起来还是挺费劲的&#xff0c;明天再补完垃圾回收这块的知识点。U•ェ•*U 先补一下JVM运行时的栈帧结构。 线程调用一个方法的执…

【并发编程】volatile原理

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程⛺️稳重求进&#xff0c;晒太阳 volatile原理实现是内存屏障&#xff0c;Memory Barrier 对volatile变量的写指令后会加入写屏障。对volatile变量的读指令前会加入读屏障 如何保…

Spring - 基本用法参考

Spring 官方文档 Spring容器启动流程&#xff08;源码解读&#xff09; BeanFactoryPostProcessor vs BeanPostProcessor vs BeanDefinitionRegistryPostProcessor&#xff1a; From java doc&#xff1a; BeanFactoryPostProcessor may interact with and modify bean defin…

网工内推 | 申通快递急招网安、测试工程师,包食宿,30k*13薪

01 申通快递 招聘岗位&#xff1a;信息安全工程师 职责描述&#xff1a; 1、 负责集团数据安全风险的识别、协同、跟踪、改进优化及事后评估&#xff1b; 2、 负责集团数据安全专项风险的治理及系统上线前的数据安全评审&#xff1b; 3、 负责集团信息安全、合规等方面制度的编…

限时回归!!!3D版《空洞骑士》!!!

空洞骑士是一款基于横板平台跳跃的传统风格2D动作冒险游戏。庞大的游戏世界交错相通&#xff0c;玩家控制小虫子去探索幽深黑暗的洞穴&#xff0c;成为了一代人茶余饭后的惦念&#xff0c;深受广大玩家们的喜爱。 这类平台跳跃游戏一般是游戏开发初学者以及独立游戏开发者们比…

React一学就会(7): 细说redux及其应用

不知不觉一个星期结束了&#xff0c;很快就要过年了&#xff0c;中间休息了两天&#xff0c;小孩生病&#xff0c;我也有点感冒了&#xff0c;还好&#xff0c;我的这个 React 基础教学课程也基本结束了。大家有不明白的可以留言问我&#xff0c;我一定竭尽所能的帮助你。后面几…

D3703F——应用于音响系统的静音检测电路 当音响系统在放音或快进 / 退时进行静音检测,输出控制信号。

D3703F 是 一 块 汽 车 音 响 静 音 检 测 电 路 。 用 于 音 响 系 统 检 测 在 放 音 或 快 进 / 退 时 进 行 静 音 检 测 。 D3703F 的 的 电 压 范 围 &#xff1a; 3.2V &#xff5e; 16V &#xff0c; 信 号 检 测 和 静 音 时 间 可 通 过 外 围 电 阻 、 电 容 来 …

中小型企业知识库建设的秘诀来啦,赶紧收藏起来

知识库是企业的智慧宝库&#xff0c;其中的信息和知识的整合&#xff0c;可以极大地提高工作效率和团队协作能力。尤其对中小企业来说&#xff0c;知识库的建设更是关系企业未来发展的重要因素。那么&#xff0c;怎样有效地构建高效的知识库系统呢&#xff1f;下面这些秘诀值得…

Arthas的使用

1. 简介 官网 线上debug神器&#xff0c;就不过多介绍 2. 环境搭建 win11环境 ,jdk11 2.1 安装 下载地址 2.2 启动 cmd java -jar arthas-boot.jar启动之后会自动检测启动的java服务 1~4 &#xff0c;springboot是启动类名&#xff0c;所以我选择了3 3. 常用操作 3.…

Hadoop3.x基础(1)

来源&#xff1a;B站尚硅谷 这里写目录标题 大数据概论大数据概念大数据特点(4V)大数据应用场景 Hadoop概述Hadoop是什么Hadoop发展历史&#xff08;了解&#xff09;Hadoop三大发行版本&#xff08;了解&#xff09;Hadoop优势&#xff08;4高&#xff09;Hadoop组成&#xf…

非阿里云注册域名如何在云解析DNS设置解析?

概述 非阿里云注册域名使用云解析DNS&#xff0c;按照如下步骤&#xff1a; 添加域名。 添加解析记录。 修改DNS服务器。 DNS服务器变更全球同步&#xff0c;等待48小时。 添加解析记录 登录云解析DNS产品控制台。 在 域名解析 页面中&#xff0c;单击 添加域名 。 在 …

SkyWalking+es部署与使用

第一步下载skywalking :http://skywalking.apache.org/downloads/ 第二步下载es:https://www.elastic.co/cn/downloads/elasticsearch 注&#xff1a;skywalking 和es要版本对应&#xff0c;可从下面连接查看版本对应关系&#xff0c;8.5.0为skywalking 版本号 Index of /di…

实惨!多本EI接连被各大数据库剔除!2024年EI期刊目录首次更新-附下载

EI目录更新 本月爱思唯尔&#xff08;Elsevier&#xff09;官网更新了EI Compendex收录期刊目录&#xff0c;这是2024年第一次更新。 Elsevier发布2024年第一版EI期刊目录 更新时间&#xff1a;2024年1月1日 不同于SCI/SSCI目录每月更新一次的频率&#xff0c;EI目录更新没有…

VBA技术资料MF112:列出目录中的所有文件和文件夹

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

构建高效外卖系统:利用Spring Boot框架实现

在当今快节奏的生活中&#xff0c;外卖系统已经成为人们生活中不可或缺的一部分。为了构建一个高效、可靠的外卖系统&#xff0c;我们可以利用Spring Boot框架来实现。本文将介绍如何利用Spring Boot框架构建一个简单但功能完善的外卖系统&#xff0c;并提供相关的技术代码示例…

解析线上HBase集群CPU飙高的原因与解决方案

在日常的运维工作中&#xff0c;CPU负载高是一种常见的故障状况&#xff0c;它可能对系统的正常运行和性能产生不利影响。为了准确地定位具体的异常原因&#xff0c;掌握一些专业的工具和方法是至关重要的。本文将通过一个实际的案例&#xff0c;详细介绍如何排查在线上HBASE集…

【C++】C++入门基础讲解(二)

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 导读 接着上一篇的内容继续学习&#xff0c;今天我们需要重点学习引用。 1. 引用 在C中&#xff0c;引用是一种特殊的变量&#xff…

动态gif图如何在线做?这一招分分钟生成

Gif动图是怎么制作呢&#xff1f;Gif动画已经是日常聊天娱乐必备的了&#xff0c;那么这种有趣的gif表情要怎么操作呢&#xff1f;很简单&#xff0c;使用gif动图生成&#xff08;https://www.gif.cn/&#xff09;工具无需下载软件&#xff0c;小白也能轻松操作。可上传MP4格式…

Vue2 VS Vue3 生命周期

一、生命周期的概念 Vue组件实例在创建时要经历一系列的初始化步骤&#xff0c;在此过程中Vue会在合适的时机&#xff0c;调用特定的函数&#xff0c;从而让开发者有机会在特定阶段运行自己的代码&#xff0c;这些特定的函数统称为&#xff1a;生命周期钩子&#xff08;也会叫…