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.cppcns.com/os/linux/704971.html
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1153865

相关文章

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存

Linux使用scp进行远程目录文件复制的详细步骤和示例

《Linux使用scp进行远程目录文件复制的详细步骤和示例》在Linux系统中,scp(安全复制协议)是一个使用SSH(安全外壳协议)进行文件和目录安全传输的命令,它允许在远程主机之间复制文件和目录,... 目录1. 什么是scp?2. 语法3. 示例示例 1: 复制本地目录到远程主机示例 2: 复制远程主

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

Linux基础命令@grep、wc、管道符的使用详解

《Linux基础命令@grep、wc、管道符的使用详解》:本文主要介绍Linux基础命令@grep、wc、管道符的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录grep概念语法作用演示一演示二演示三,带选项 -nwc概念语法作用wc,不带选项-c,统计字节数-

MySQL的ALTER TABLE命令的使用解读

《MySQL的ALTERTABLE命令的使用解读》:本文主要介绍MySQL的ALTERTABLE命令的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、查看所建表的编China编程码格式2、修改表的编码格式3、修改列队数据类型4、添加列5、修改列的位置5.1、把列