Linux中的进程间通信之匿名管道解读

2025-03-21 03:50

本文主要是介绍Linux中的进程间通信之匿名管道解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Linux中的进程间通信之匿名管道解读》:本文主要介绍Linux中的进程间通信之匿名管道解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...

一、基本概念

我们知道多个进程之间是互相独立的,但是有时候我们需要将一个进程的数据传递到另一个进程,实现数据传输的效果,有的时候多个进程之间要共享同样的资源,有的时候一个进程要对其他进程发送消息,实现通知事件,还有的时候一个进程要完全控制另一个进程的执行,实现进程控制

因为进程间相互独立,所以进程通信是有较高成本

进程间通信的本质就是让不同的进程看到同一份资源,这份资源一定是由操作系统提供的第三方空间,不能是某个进程的,因为这样会破坏进程独立性,我们进程访问第三方空间本质上就是访问操作系统

一般操作系统会有一个独立的通信模块,隶属于文件系统,它被制定者制定了两个标准system V 和 posix ,其中system V 是本机内部进程间的通信,分为消息队列、共www.chinasem.cn享内存、信号量,posix 是网络进程通信,分为消息队列、共享内存、信号量、互斥量、条件变量、读写锁

在进程间通信的规则指定之前,还没有system V 和 posix 的时候,我们是通过管道进行进程间通信的,这是一种基于文件的通信方式

二、管道

1、温故知新

我们在之前的学习命令行的过程中学习过管道,那里的管道与这里的管道是一致的,本质上就是一个管子,在两头位置处有两种处理方式,在进入管道前处理一次,在管道中的内容就是已经被处理过一次的内容,然后离开管道后再处理一次,得出的结果就是一个数据被前面的命令处理一次的结果被后面的命令处理

当时学习的时候只浮于表面,实际上管道就是起到一个传递数据流的作用,两边为两个进程,进程A发出的信息可以通过管道到达进程B,管道本身没有处理数据的功能,只有传递数据的功能

2、实现方式

我们说管道是一个基于文件的通信方式,我们来看一下我们文件管理的内容

Linux中的进程间通信之匿名管道解读

进程中的PCB中有一个struct files_struct指针,指向结构体files_structfiles_struct结构体中存在一个文件描述符指针数组,指向对应的struct file对象,每个struct file都有inode描述文件属性,file_operators定义操作文件的函数接口,文件缓冲区缓冲文件,硬盘当中的文件如果要加载到内存中需要先加载到文件缓冲区,如果我们的管道文件在硬盘上,那么IO的速度将非常慢,不利于我们进行进程间的快速通信,那什么地方既速度快又能存放文件呢?答案就是内存

Linux中的进程间通信之匿名管道解读

我们把写入或者读取硬盘的IO操作去掉,将管道文件保存在缓冲区,其他进程再通过文件描述符读取缓冲区的内容,就可以实现进程间的管道通信,这里的管道文件就是匿名管道

管道文件的存放问题我们解决了,下一个问题就是其他进程怎么通过文件描述符读取缓冲区的内容

我们知道子进程被父进程创建后,如果不做修改,相当于是浅拷贝,父进程的PCB复制一份,files_struct也复制一份,那么它们就同时指向已经同一个struct file,如果父进程fd==3以读方式打开管道文件,fd==4以写方式打开管道文件,那么子进程也一样,然后父进程close(3)子进程close(4)实现父写子读,父进程close(4)子进程close(3)实现父读子写

因为一个文件是没法进行读写交替一起的,所以匿名管道其实是一种半双工的通信方式,即单向通信,当然我们可以通过建立多个匿名管道来实现双向通信

管道通信常用于父子进程通信,可用于兄弟进程、爷孙进程等有"血缘"的进程进行通信

3、匿名管道

#include <unistd.h>
int pipe(int pipefd[2]);
//pipefd:文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端,值为对应的文件描述符 
//返回值:成功返回0,失败返回错误代码

在pipe函数中,int fd[2]是一个输出型参数

我们来实现一个父读子写这样一个管道通信

#include <IOStream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstring>
#include <cstdlib>

#define N 2
#define NUM 1024
using namespace std;

void Writer(int wfd)
{
	//定义要发送的字符串
    string s = "this is your child";
    //获取当前进程的pid
    pid_t myid = getpid();
    int number = 0;

    char buffer[NUM];
    while(1)
    {
        //此处相当于buffer[0] = '\0';意思是将整个数组当做字符串用并且清空字符串
        buffer[0] = 0;
        //将字符串、pid、以及计数器number按照"%s-%d-%d"格式写到buffer当中
        snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),myid,number++);
        //这里传过来的wfd为对应的文件描述符,然后将buffer中的有效内容写到管道文件缓冲区中
        write(wfd,buffer,strlen(buffer));

        sleep(5);
    }
}

void Reader(int rfd)
{
    char buffer[NUM];
    while(1)
    {
    	//同上
        buffer[0] = 0;
        //将文件描述符rfd读取的内容存储到buffer中,并返回读取到的字符个数n
        ssize_t n = read(rfd,buffer,sizeof(bufpythonfer));
        //如果有内容则打印出来
        if(n > 0)
        {
            buffer[n] = 0;
            cout << "parent get a message[" << getpid() << "]# " << buffer << endl;
        }
        //没有内容即读取完成
        else if(n == 0) 
        {
            printf("parent read file done!\n");
            break;
        }
        //其他情况就是有bug了
        else break;
    }
}

int main()
{
	//pipefd用来存放输出型参数
    int pipefd[N] = {0};	
    //成功验证
    int n = pipe(pipefd);
    if(n < 0)
    {
        return 1;
    }
	//创建子进程
    pid_t id = fork();
	//错误情况
    if(id < 0)
    {
        return 2;
    }
    //子进程执行段,把读写函数打包一下,写到一个函数里,立体分明
    else if(id == 0)
    {
        //child
        //子进程要写不读,关掉pipefd[0],写pipefd[1],写完再关掉pipefd[1],然后退出
        close(pipefd[0]);
        Writer(pipefd[1]);
        close(pipefd[1]);
        exit(0);
    }
    //父进程执行段
    else{
        //parent
        //父进程要读不写,关掉pipefd[1],读pipefd[0],等待子进程结束再关掉pipefd[0]
        close(pipefd[1]);
        Reader(pipefd[0]);

        pid_t rid = waitpid(id,NULL,0);
        if(rid < 0)
        {
            return 3;
        }
        close(pipefd[0]);
    }
    return 0;
}

Linux中的进程间通信之匿名管道解读

这里父进程只在子进程写入的时候才读取,没有出现子进程写一半父进程就读取的情况,所以父子进程直接是会进行协同的,有同步和互斥性

(一)管道中的四种情况

对管道中可能出现的四种情况做说明:

  • 读写端正常,如果管道为空,读端就要被阻塞(上面印证)
  • 读写端正常,如果管道被写满,写端就要被阻塞(在管道特性这里印证)
  • 读端正常,写端关闭,读端可以读到0,表明读到了文件结尾,不堵塞
  • 写端正常,读端关闭,操作系统会杀死正在写入的进程,用信号SIGPIPE,也就是kill -13

注释掉main函数中子进程中的Writer函数,它会读到文件结尾并打印done信息

Linux中的进程间通信之匿名管道解读

写端一秒写入一次,读端一秒读一次,读端读5秒后退出读模式,关闭读端,然后静待5秒,等待子进程结束,然后打印它的退出码和收到的信号

int main()
{
	//......
    if(id < 0)
    {
        return 2;
    }
    else if(id == 0)
    {
        //child
        close(pipefd[0]);
        Writer(pipefd[1]);
        close(pipefd[1]);
        exit(0);
    }
    else{
        //parent
        close(pipefd[1]);
        Reader(pipefd[0]);
        close(pipefd[0]);
        cout << "father close read fd: " << pipefd[0] << endl;
        sleep(5);
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid &编程lt; 0)
        {
            return 3;
        }
        cout << "wait child success: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) << endl;
        sleep(5);
        cout js<< "parent quit" << endl;
    }
    
    return 0;
}

Linux中的进程间通信之匿名管道解读

(二)管道的特性

//子进程一直写
void Writer(int wfd)
{
    string s = "this is your child";
    pid_t myid = getpid();
    int number = 0;

    char buffer[NUM];
    while(1)
    {
        //buffer[0] = '\0';
        buffer[0] = 0;
        snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),myid,number++);
        write(wfd,buffer,strlen(buffer));
    }
}

//父进程5秒读一次数据
void Reader(int rfd)
{
    char buffer[NUM];
    while(1)
    {
        sleep(5);
        buffer[0] = 0;
        ssize_t n = read(rfd,buffer,sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            cout << "parent get a message[http://www.chinasem.cn" << getpid() << "]# " << buffer << endl;
        }
        else if(n == 0) 
        {
            printf("parent read file done!\n");
            break;
        }
        else break;
    }
}

Linux中的进程间通信之匿名管道解读

我们发现它的读取是杂乱无章的,说明管道是面向字节流的,这里与前面并不矛盾,有人说这里不是没写完就读取吗,你看这个句子一段一段的,其实这里是缓冲区写满了,写不下了,写入端堵塞导致的,在读取端读取之后写入端才继续写入,正好也印证了上面的说法

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Linux中的进程间通信之匿名管道解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153865

相关文章

Linux修改pip临时目录方法的详解

《Linux修改pip临时目录方法的详解》在Linux系统中,pip在安装Python包时会使用临时目录(TMPDIR),但默认的临时目录可能会受到存储空间不足或权限问题的影响,所以本文将详细介绍如何... 目录引言一、为什么要修改 pip 的临时目录?1. 解决存储空间不足的问题2. 解决权限问题3. 提

解读spring.factories文件配置详情

《解读spring.factories文件配置详情》:本文主要介绍解读spring.factories文件配置详情,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录使用场景作用内部原理机制SPI机制Spring Factories 实现原理用法及配置spring.f

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

Linux中的缓冲区和文件系统详解

《Linux中的缓冲区和文件系统详解》:本文主要介绍Linux中的缓冲区和文件系统方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、FILE结构1、fd2、缓冲区二、文件系统1、固态硬盘2、逻辑地址LBA(一)数据块 Data blocks(二)inode表

Linux系统中配置静态IP地址的详细步骤

《Linux系统中配置静态IP地址的详细步骤》本文详细介绍了在Linux系统中配置静态IP地址的五个步骤,包括打开终端、编辑网络配置文件、配置IP地址、保存并重启网络服务,这对于系统管理员和新手都极具... 目录步骤一:打开终端步骤二:编辑网络配置文件步骤三:配置静态IP地址步骤四:保存并关闭文件步骤五:重

Linux进程终止的N种方式详解

《Linux进程终止的N种方式详解》进程终止是操作系统中,进程的一个重要阶段,他标志着进程生命周期的结束,下面小编为大家整理了一些常见的Linux进程终止方式,大家可以根据需求选择... 目录前言一、进程终止的概念二、进程终止的场景三、进程终止的实现3.1 程序退出码3.2 运行完毕结果正常3.3 运行完毕

Linux命名管道方式

《Linux命名管道方式》:本文主要介绍Linux命名管道方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、命名管道1、与匿名管道的关系2、工作原理3、系统调用接口4、实现两个进程间通信二、可变参数列表总结一、命名管道1、与匿名管道的关系命名管道由mkf

Linux文件名修改方法大全

《Linux文件名修改方法大全》在Linux系统中,文件名修改是一个常见且重要的操作,文件名修改可以更好地管理文件和文件夹,使其更具可读性和有序性,本文将介绍三种在Linux系统下常用的文件名修改方法... 目录一、引言二、使用mv命令修改文件名三、使用rename命令修改文件名四、mv命令和rename命

Python解析器安装指南分享(Mac/Windows/Linux)

《Python解析器安装指南分享(Mac/Windows/Linux)》:本文主要介绍Python解析器安装指南(Mac/Windows/Linux),具有很好的参考价值,希望对大家有所帮助,如有... 目NMNkN录1js. 安装包下载1.1 python 下载官网2.核心安装方式3. MACOS 系统安

Linux find 命令完全指南及核心用法

《Linuxfind命令完全指南及核心用法》find是Linux系统最强大的文件搜索工具,支持嵌套遍历、条件筛选、执行动作,下面给大家介绍Linuxfind命令完全指南,感兴趣的朋友一起看看吧... 目录一、基础搜索模式1. 按文件名搜索(精确/模糊匹配)2. 排除指定目录/文件二、根据文件类型筛选三、时间