首页 > 系统相关 >进程间的通信方式——pipe(1、管道)

进程间的通信方式——pipe(1、管道)

时间:2023-04-25 16:02:44浏览次数:36  
标签:int 通信 pipe 管道 fd printf 进程


本章内容

  • 采用pipe管道如何进行进程之间的通信
  • pipe管道进程通信的规则和限制
  • Linux中pipe管道的实现机制和管理pipe管道的结构体

什么是进程通信

进程通信就是两个进程之间进行数据交换,在Linux中有好几种可以进行进程通信的方式,在这篇文章中我们主要介绍最基本的进程通信方式——pipe管道。


进程通信的途径

进程之间交换信息的唯一途径就是传送打开的文件。


管道(pipe)

管道是一种最古老也是最基本的系统IPC形式,所有的Linux系统都提供此种通信机制。但是管道有以下两个局限性:

  • 它是半双工的,即数据一个管道上的数据只能在一个方向上流动,如果要实现双向通信,就必须在两个进程之间建立两个管道;
  • 管道只能在具有公共祖先的两个进程之间使用; -

pipe的实现机制

管道是由内核管理的一个缓冲区,它的一端连接一个进程的输出,另一端连接一个进程的输入。管道的缓冲区不需要很大,它被设计为环形的数据结构,当两个进程都终止后,管道的生命周期也会被结束。

管道的创建

管道是通过调用pipe函数创建的。

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

它由输出型参数fd返回两个文件描述符,fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入,当管道创建成功后pipe函数返回0,如果创建失败则返回-1,fd[0]和fd[1]之间的关系如下图:

进程间的通信方式——pipe(1、管道)_管道

如何通过pipe进行通信

上面我们在单个进程中建立了管道,但是实际上,单个进程中的管道是没有什么用的,通常,进程会先调用pipe函数产生管道,接着调用fork()函数,fork函数会将父进程的相关数据结构继承到子进程中,这样就使子进程中的文件描述符表中的fd[0]和fd[1]指向父进程所指向的管道文件,这样就能实现两个进程之间的通信了。上面的过程如下图:

进程间的通信方式——pipe(1、管道)_#include_02

利用pipe通信的相关规则

对于一个从子进程到父进程的管道(子进程写,父进程读),父进程关闭fd[1],子进程关闭fd[0],当管道的一段被关闭后(在上面的基础上关闭管道的一端)下列两条规则起作用:

  1. 当读一个写段已经被关闭的管道时,在所有的数据都被读取后,read返回0(read返回0表示已经读到文件结束符);
    下面我们进行验证:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>

int main()
{
    //create pipe
    int fd[2]={0,0};
    if(pipe(fd)!=0){
        //create false
        perror("pipe");
        exit(1);
    }
    // pipe create success
    pid_t id=fork();
    if(id==0){
        //child -->write fd[1]
        printf("Child\n");
        sleep(2);
        const char* msg="Hello,leap\n";
        close(fd[0]);
        int count=3;
        while(count--){
            ssize_t size=write(fd[1],msg,strlen(msg));
            printf("size:%d\n",size);
            //if(count--){
            //  sleep(1);
            //}
            sleep(1);
            printf("child is writing...\n");
        }
        close(fd[1]);
        exit(0);
    }
    else{
        //father -->read fd[0]
        printf("Father\n");
        sleep(2);
        close(fd[1]);
        char buf[1024];
        int count=3;
        while(1){
            ssize_t Len=read(fd[0],buf,1024);
            //printf("Len::%d\n",Len);
            printf("Father is reading...\n");
            if(Len>0){
                //read success
                buf[Len]='\0';
                printf("child say:%s",buf);
            }
            else if(Len==0){
                //read end of file
                printf("Read the end of pipe\n");
                break;
            }
            else{
                perror("read");
                exit(1);
            }
        }
        close(fd[0]);
        int status=0;
        pid_t _pid=waitpid(id,&status,0);
        if(_pid==id){
            printf("Wait success for child\n");
            printf("Exit code:%d,Exit signal:%d\n",(status>>8)&0xff,status&0xff);
        }
        else{
            perror("wait");
        }
        exit(0);
    }
    return 0;
}

程序让子进程写三次字符串然后关闭子进程fd[1],即关闭管道的写端,不关闭父进程的fd[0],即管道的读端。

进程间的通信方式——pipe(1、管道)_子进程_03

  1. 如果写一个读端已经被关闭的管道,则会产生相关信号对写段的进程进行终止,如果忽略该信号或捕捉该信号并从处理程序返回,则write会返回-1,errno会设置为EPIPE;
    下面我们进行验证:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>

int main()
{
    //create pipe
    int fd[2]={0,0};
    if(pipe(fd)!=0){
        //create false
        perror("pipe");
        exit(1);
    }
    // pipe create success
    pid_t id=fork();
    if(id==0){
        //child -->write fd[1]
        printf("Child\n");
        sleep(2);
        const char* msg="Hello,leap\n";
        close(fd[0]);
        int count=5;
        while(1){
            ssize_t size=write(fd[1],msg,strlen(msg));
            printf("size:%d\n",size);
            if(count--){
                sleep(1);
            }
            printf("child is writing...\n");
        }
        close(fd[1]);
    }
    else{
        //father -->read fd[0]
        printf("Father\n");
        sleep(2);
        close(fd[1]);
        char buf[1024];
        int count=3;
        while(count--){
            ssize_t Len=read(fd[0],buf,1024);
            //printf("Len::%d\n",Len);
            printf("Father is reading...\n");
            if(Len>0){
                //read success
                buf[Len]='\0';
                printf("child say:%s",buf);
            }
            else if(Len==0){
                //read end of file
                printf("Read the end of pipe\n");
            }
            else{
                perror("read");
                exit(1);
            }
        }
        close(fd[0]);
        int status=0;
        pid_t _pid=waitpid(id,&status,0);
        if(_pid==id){
            printf("Wait success for child\n");
            printf("Exit code:%d,Exit signal:%d\n",(status>>8)&0xff,status&0xff);
        }
        else{
            perror("wait");
        }
        exit(0);
    }
    return 0;
}

代码的意图是这样:我们让write端(子进程)一直写字符串msg,而read端(父进程)先读三次然后在关闭掉父进程的fd[0],这样就形成了子进程一直写,而父进程没有在读的情况。结果如下:

进程间的通信方式——pipe(1、管道)_管道_04


我们发现父进程关闭掉fd[0]后子进程被异常终止了,我们从子进程的退出码和退出信号码发现它是被13号信号(SIGPIPE)所终止的,所以写一个读端关闭的管道这对PIPE来说并不成立,操作系统会在读端关闭后向写端的进程发送SIGPIPE使进程被终止。

  1. 如果管道的读端和写端都没有关闭,但是管道的写端没有再向管道写数据了。这时如果管道中没有数据了,那么在此read进程会产生阻塞,直到管道中有数据了才读取数据并返回。
  2. 如果有指向管道读端的文件描述符没有关闭,而持有管道读端的没有从管道中读数据,这时有进程向管道中写数据,如果管道被写满再向管道写数据是,再次write会导致进程阻塞,直到管道中有空间了才会继续向管道中写数据并返回。

pipe管道容量

我们可以通过* man 7 pipe*;来查询管道的容量pipe_capacity

## Linux的管道实现机制
从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:
管道是一个固定大小的缓冲区,在Linux中,该缓冲区的大小为一页,即4kb,使它的大小不会像普通文件那样不加检验的增长。在Linux中,内核使用struct pipe_inode_info结构体来描述一个管道,这个结构体定义在pipe_fs_i.h中。

struct pipe_inode_info结构体

struct pipe_inode_info {
//管道等待队列,当pipe为空/满时指向等待的读者和写者
    wait_queue_head_t wait;            
//pipe中非空缓冲区的数量和当前pipe的入口
    unsigned int nrbufs, curbuf;
//临时释放的页也叫高速缓存区页框指针
    struct page *tmp_page;
//读进程的标志或ID号
    unsigned int readers;
//写进程的标志或ID号
    unsigned int writers;
//在等待队列中睡眠的写进程的个数
    unsigned int waiting_writers;
//reader的总数
    unsigned int r_counter;
//writer的总数
    unsigned int w_counter;
//用于通过信号进行异步I/O通知
    struct fasync_struct *fasync_readers;

    struct fasync_struct *fasync_writers;
//pipe对应的inode
    struct inode *inode;
//pipe的环形缓冲区
    struct pipe_buffer bufs[PIPE_BUFFERS];
};

缓冲区的个数

#define PIPE_BUFFERS (16)
  • 1

管理缓冲区的结构

struct pipe_buffer {
//包含当前pipe_buffer数据的页
    struct page *page;   
//页中所包含的数据的偏移量,长度
    unsigned int offset, len;
//与buffer相关的操作
    const struct pipe_buf_operations *ops;
//pipe_buffer标志
    unsigned int flags;
    unsigned long private;
};

标签:int,通信,pipe,管道,fd,printf,进程
From: https://blog.51cto.com/u_16081664/6224228

相关文章

  • 进程间通信方式———3、信号量(Semaphore)
    1.信号量信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一......
  • zookeeper系列之通信模型
    本文的主题就是讲解Zookeeper通信模型,本节将通过一个概要图来说明Zookeeper的通信模型。Zookeeper的通信架构在Zookeeper整个系统中,有3中角色的服务,client、Follower、leader。其中client负责发起应用的请求,Follower接受client发起的请求,参与事务的确认过程,在leadercrash后的leade......
  • [MLIR] CodeGen Pipeline总结
    参考资料:[MLIR]CodeGenPipeline总结-知乎(zhihu.com)本文主要以tensorflow为例,介绍了其接入MLIR后的CodeGen过程,以及简要分析了一些现在常用的CodeGenpipeline。本文是本人在结合博客(CodegenDialectOverview-MLIR-LLVMDiscussionForums)以及相关资料而写......
  • redis之哈希类型-列表类型-集合类型-有序集合-慢查询-pipeline-发布订阅-Bitmap位图-H
    目录redis之哈希类型-列表类型-集合类型-有序集合-慢查询-pipeline-发布订阅-Bitmap位图-HyperLogLog-GEO地理位置昨日内容回顾今日内容详细1哈希类型2列表类型3集合类型4有序集合5慢查询6pipeline与事务7发布订阅8Bitmap位图9HyperLogLog10GEO地理位置redis之哈希类型......
  • 哈希类型 列表类型 集合类型 有序集合 慢查询 pipeline与事务 发布订阅 Bitmap位图 Hy
    昨日回顾#1redis介绍 -特性#速度快:10wops(每秒10w读写),数据存在内存中,c语言实现,单线程模型#持久化:rdb和aof#多种数据结构:5大数据结构BitMaps位图:布隆过滤器本质是字符串HyperLogLog:超小内存唯一值计数,12kbHyperLogLog本质是......
  • 使用pipeline执行命令遇到redis.Nil的坑
    参考项目kratos_rockscacheredis数据准备关键代码特别注意,使用pipeline的Exec方法,一定要判断一下redis.Nil这个错误:~~~......
  • OpenHarmony的线程间通信EventHandler
    一、初识EventHandler​ 在OpenHarmony的开发过程中,如果遇到处理下载、运算等较为耗时的操作时,会阻塞当前线程,但是实际操作中又不希望当前线程受到阻塞。比如:我们的app在界面上有一个下载文件的处理按钮,如果在按钮按下时,直接处理下载任务时,当前的界面就会阻塞,不允许操作界面上的任......
  • websocket与C# socket相互通信
    web端代码就是js代码,C#有两种方式:使用第三方库,如Fleck,使用C#原生socket编程实现 web端:<!doctypehtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>下发网站上文件到学生机</title><scripttype=......
  • Android多线程通信-handler机制
    AndroidStudio主线程不允许耗时操作,最后通过多线程实现了AndroidStudio远程连接数据库但是主线程可以分线程传值,分线程却不可以直接向主线程传值AndroidStudio通过handler和massage解决了这个问题 话不多说,看代码——privateHandlerhandler=newHandler(){@Overridep......
  • 西门子上位机通信项目-开篇
    开篇这里系统整理一个西门子上位机项目,这个计划已经酝酿很久,也在B站上看了很多上位机的视频。我想自己总结一套上位机的开发方案,方便自己后期查阅,也希望能帮助到同样在做上位机编程的工控人。我的计划是在博客园先整理一下开发方案,成熟后在B站也以视频的方式供同行学习。 在......