【Linux进程间通信】Linux匿名管道详解:构建进程间通信的隐形桥梁

📝个人主页🌹:Eternity._

⏩收录专栏⏪:Linux " 登神长阶 "

🌹🌹期待您的关注 🌹🌹


❀Linux进程间通信


前言:当提及Linux系统中的进程间通信(IPC),管道(Pipes)无疑是最基础且广泛使用的一种机制。作为匿名通信的典范,管道为进程间数据交换提供了一个简单而有效的途径。在这个信息飞速传递的时代,掌握Linux管道的使用不仅是理解操作系统底层通信原理的关键一步,也是提升软件开发效率、构建复杂应用系统的必备技能

本篇文章将带您深入探索Linux进程间匿名通信的管道机制。我们将从管道的基本概念出发,逐步揭开其背后的工作原理,并通过实例演示如何在实际编程中创建、使用和维护管道。无论您是初学者,希望建立对Linux IPC的初步认识;还是经验丰富的开发者,渴望在现有基础上进一步精进;亦或是对系统编程充满好奇的学习者,渴望深入了解操作系统内部的奥秘,本文都将为您提供丰富的知识和实用的指导

我们将详细介绍管道的创建过程、数据读写操作、管道的生命周期管理以及常见的使用场景。 同时,我们还会探讨管道在并发编程中的表现,分析其在多进程环境下的行为特性,并提供相应的优化策略。通过理论与实践相结合的方式,相信您能够全面掌握Linux进程间匿名通信的管道技术,为您的软件开发之路增添一份坚实的力量

让我们一同踏上这段探索之旅,揭开Linux管道的神秘面纱,领略其在进程间通信中的独特魅力!


📒1. 进程间通信介绍

进程间通信(Interprocess communication,IPC)是指在不同的进程之间传播或交换信息。由于进程的用户空间是互相独立的,一般而言不能互相访问,但存在一些双方都可以访问的介质或系统空间来实现通信

  • 原理: 进程间通信主要依赖于双方都可以访问的介质或系统空间。这些介质包括共享内存区、系统空间以及双方都可以访问的外设(如磁盘上的文件、数据库中的表项等)。然而,广义上的通过这些方式进行的通信一般不算作"进程间通信"。进程间通信更常见的是通过一组编程接口来实现,这些接口允许程序员协调不同的进程,使它们能在一个操作系统里同时运行,并相互传递、交换信息

  • 必要性: 即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行。这些进程之间必须互相通信,以协调它们的行为和共享资源。进程间通信使得一个程序能够在同一时间里处理许多用户的要求


📚2. 什么是管道

  • 管道是Unix中最古老的进程间通信的形式
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个"管道"

管道分为:匿名管道和命名管道,本篇我们主要来了解一下匿名管道


📜3. 匿名管道

匿名管道是Linux中一种非常基础的进程间通信(IPC)方式,其本质上是一种内存级的文件,专门用于父子进程间或具有亲缘关系的进程间的通信

创建匿名管道

c 复制代码
#include <unistd.h>

//功能:创建一无名管道
//原型
int pipe(int fd[2]);

//参数
//fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
//返回值:成功返回0,失败返回错误代码


实例代码:

cpp 复制代码
#include <iostream>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX 1024

using namespace std;

int main()
{
    // 1. 建立管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n == 0);
    // 定义 n
    (void)n;
    // 查看文件描述符
    cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;

    // 2. 创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }

    // 子写,父读,
    // 3. 关闭父子不需要的fd,形成单向通信的管道
    if(id == 0)
    {
        // 子进程
        close(pipefd[0]);

        // 写入
        int cnt = 10;
        while(cnt)
        {
            char message[MAX];
            snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
            cnt--;
            write(pipefd[1], message, strlen(message));
            cout << "writing cnt: " << cnt << endl;
        }

        exit(0);
    }

    // 父进程
    close(pipefd[1]);

    // 读取
    char buffer[MAX];
    while(true)
    {
        ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
        if(n == 0)
        {
            cout << "child qiut, read tail" << endl;
            break;
        }
        else if(n > 0)
        {
            buffer[n] = 0; // '\0', 当作字符串
            cout << getpid() << ": " << "child say: " << buffer << " to me!" << endl;
        }
    }

    pid_t rid = waitpid(id, nullptr, 0);
    if(rid == id)
    {
        cout << "wait seccess" << endl;
    }

    return 0;
}

🌞fork共享管道原理


🌙结合文件描述符


⭐站在内核角度


📝4. 管道的读写情况与特点

🎈管道的读写情况

  1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据)
  2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)

我们让读端一直读,而写端在写入部分文件后让它sleep一段时间,我们这是来观察一下读端的情况

代码示例:(C++):

cpp 复制代码
if(id == 0)
{
    // 子进程
    close(pipefd[0]);

    // 写入
    int cnt = 10000;
    while(cnt)
	{
        char message[MAX];
    	snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
        cnt--;
        write(pipefd[1], message, strlen(message));
        // 在正常写入一次后,sleep,父进程读取不做修改
        sleep(4);       
	}
    exit(0);
}

当我们的管道被写满了的时候,写端就不能在进行写入了,我们必须等待读端将数据读取走才能继续往管道里面写入,我们让读端休眠上几面,让写端一直写

代码示例:(C++):

cpp 复制代码
if(id == 0)
{
    // 子进程
    close(pipefd[0]);

    // 写入
    int cnt = 0;
    while(true)
	{
        char message[MAX];
    	snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
        cnt++;
        write(pipefd[1], message, strlen(message));
        // 在正常写入一次后,sleep,父进程读取不做修改
        cout << "writing cnt: " << cnt << endl; 	
	}
    exit(0);
}

  1. 写端关闭,读端一直读取,读端会读到read返回值为0,表示读到文件结尾
  2. 读端关闭,写端一直写入,0S会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程

写端关闭代码示例:(C++):

cpp 复制代码
if(id == 0)
{
    // 子进程
    close(pipefd[0]);

    // 写入
    int cnt = 0;
    while(true)
    {
        char message[MAX];
     	snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
        cnt++;
        write(pipefd[1], message, strlen(message));
        //sleep(2);
        cout << "writing cnt: " << cnt << endl;
        // 在写入两次时,我们将子进程的写入关闭
        if(cnt == 2)
        {
        	close(pipefd[1]);
            break;
        }
    }

    exit(0);
}

// 父进程
close(pipefd[1]);

// 读取
char buffer[MAX];
while(true)
{
	sleep(4);
    ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
    // 当 n == 0 时,代表read已经读到文件结尾了
    if(n == 0)
    {
    	cout << "child qiut, read tail" << endl;
        break;
    }
    else if(n > 0)
    {
        buffer[n] = 0; // '\0', 当作字符串
        cout << getpid() << ": " << "child say: " << buffer << " to me!" << endl;
    }
}

我们这样设计代码,先让子进程写入之后,关闭掉pipefd1,然后观察父进程是否会打印,我们需要的代码


读端关闭代码示例:(C++):

cpp 复制代码
// 父进程
close(pipefd[1]);

// 读取
char buffer[MAX];
while(true)
{
	//sleep(4);
    ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
    if(n == 0)
    {
    	cout << "child qiut, read tail" << endl;
        break;
    }
    else if(n > 0)
    {
        buffer[n] = 0; // '\0', 当作字符串
        cout << getpid() << ": " << "child say: " << buffer << " to me!" << endl;
    }
    cout << "father return val(n)" << n << endl;
    sleep(1);
    // 打印一次后,我们退出循环    
    break;
}

// 关闭 pipefd[0],停止读取
cout << "close point read" << endl;
close(pipefd[0]);

sleep(3);

int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
    cout << "wait seccess, exit sig: " << (status&0x7f) << endl;
}

注意:当前状态码 & 0x7f可以查看到最后的退出码


🎩管道的特性

管道的5种特性

  1. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
  2. 匿名管道,默认给读写端要提供同步机制
  3. 面向字节流的入
  4. 管道的生命周期是随进程的
  5. 管道是单向通信的,半双工通信的一种特殊情况

在了解完管道的这些情况和特征后,我们可以利用管道来写一个简单的线程池

线程池代码链接


📖5. 总结

在探索Linux进程间匿名通信的管道机制这一旅程的尾声,我们不禁对Linux操作系统的精妙设计和强大功能有了更深一层的理解。管道,作为进程间通信的基础而又高效的工具,不仅简化了数据在不同进程间的流动过程,还极大地促进了多任务并发执行的灵活性
通过本文的学习,我们见证了管道从创建到使用的全过程,理解了其背后的工作原理,并掌握了如何在实际编程中利用管道来实现进程间的数据交换。从pipe()函数的调用,到文件描述符的分配,再到数据的读写操作,每一个步骤都蕴含着Linux系统设计的智慧与匠心

但Linux提供的进程间通信机制远不止于此。命名管道、消息队列、共享内存、信号量以及套接字等多种IPC方式,各自拥有独特的优势和适用场景。在未来的学习与实践中,我们可以继续深入探索这些机制,以更加灵活多样的方式实现进程间的协同工作

让我们以更加饱满的热情和坚定的信心,继续前行在Linux系统编程的学习之路上!

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关推荐
jump_jump2 小时前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化
AlfredZhao3 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334669 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪10 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小小工匠1 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化