Linux进程间通信(管道)

简介: Linux进程间通信(管道)

前言

本篇文章将给大家讲解进程间通信中的管道使用方法和概念。

一、管道的概念

管道的概念来源于Unix操作系统,在Unix-like系统(如Linux)中被广泛使用。它也存在于其他操作系统中,如Windows。

管道可以将一个进程的输出直接连接到另一个进程的输入,从而实现数据的流动和传输。通过管道,一个进程产生的输出可以无需写入临时文件,而是直接传递给另一个进程进行处理,这样可以提高系统的效率和响应时间。

二、管道的原理和创建方法

1.管道的原理

管道是一种在内核中实现的进程间通信机制,通过共享内存缓冲区来传递数据。默认情况下,管道的缓冲区大小为4KB,它提供了一种读写进程之间的同步机制,读取进程会阻塞直到有数据可读,写入进程会阻塞直到有足够的空间可写。通过调整缓冲区大小,可以提高管道的读写效率。

2.管道的创建方法

创建管道的方法可以使用系统调用pipe()函数。这个函数会在内核中创建一个管道,并返回两个文件描述符,一个用于读取数据,另一个用于写入数据。

下面是使用pipe()函数创建管道的基本步骤:

1.包含头文件:首先需要包含头文件<unistd.h>,该头文件中声明了pipe()函数的原型。

2.调用pipe()函数:使用pipe()函数创建管道,并将返回的文件描述符存储在一个整型数组中。通常,数组的第一个元素用于读取数据,第二个元素用于写入数据。

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

参数fd是一个整型数组,长度为2。调用成功时,fd[0]表示管道的读取端,fd[1]表示管道的写入端。如果调用失败,返回值为-1。

3.使用管道进行进程间通信:通过文件描述符进行数据的读取和写入操作。一般来说,调用fork()函数创建子进程后,可以将父子进程分别关闭不需要的文件描述符,然后使用剩下的文件描述符进行进程间的数据交换。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
    pid_t pid;
    int fd[2];
    char buf[1024];
    pipe(fd);//创建管道
    if((pid = fork()) == 0)
    {
        /*子进程*/
        close(fd[0]);//关闭读端
        write(fd[1], "Hello", strlen("Hello") + 1);
        close(fd[1]);
    }
    else
    {
        /*父进程*/
        close(fd[1]);//关闭写端
        read(fd[0], buf, 1024);
        printf("parent read buf : %s\n", buf);
        close(fd[0]);        
    }
    return 0;
}

3.管道的局限性

1.单向传输:管道是单向的,数据只能在一个方向上流动。在创建管道时,需要确定一个进程负责写入数据,另一个进程负责读取数据。如果需要双向通信,需要创建两个管道。

2.半双工通信:管道是半双工的,即同一时间只能有一个进程进行读取或写入操作。当一个进程在读取数据时,另一个进程必须等待,反之亦然。这种限制可以通过使用多线程或者使用多个管道来解决。

3.父子进程限制:管道通常用于父子进程之间的通信。如果需要实现其他进程间通信,如兄弟进程或无关进程之间的通信,使用管道就不够灵活。在这种情况下,可以考虑使用其他进程间通信机制,如命名管道、共享内存或消息队列等。

4.有限的缓冲区:管道在内核中具有有限的缓冲区。当写入数据超过缓冲区大小时,写入进程将被阻塞,直到有足够的空间来容纳数据。同样,当读取进程尝试读取数据时,如果缓冲区为空,读取进程也会被阻塞。这可能导致进程阻塞或数据丢失的问题。

5.匿名管道限制:管道是匿名的,只能在具有共同祖先的进程之间使用。如果需要在没有共同祖先的进程之间进行通信,就不能使用匿名管道,而需要使用其他适合的进程间通信机制。

三、创建两个管道进行双向通信

由于管道特性的原因只能够支持半双工通信,那么想要支持全双工通信该怎么做呢?那么这里就可以创建两个管道出来进行全双工通信:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
    pid_t pid;
    int fd1[2];
    int fd2[2];
    char buf1[1024];
    char buf2[1024];
    pipe(fd1);//创建管道1
    pipe(fd2);//创建管道2
    if((pid = fork()) == 0)
    {
        /*子进程*/
        close(fd1[0]);//关闭读端
        write(fd1[1], "Hello Parent", strlen("Hello Parent") + 1);
        close(fd1[1]);
        close(fd2[1]);//关闭写端
        read(fd2[0], buf2, 1024);
        printf("child read buf : %s\n", buf2);
        close(fd2[0]);        
    }
    else
    {
        /*父进程*/
        close(fd1[1]);//关闭写端
        read(fd1[0], buf1, 1024);
        printf("parent read buf : %s\n", buf1);
        close(fd1[0]);
        close(fd2[0]);//关闭读端
        write(fd2[1], "Hello Child", strlen("Hello Child") + 1);
        close(fd2[1]);
    }
    return 0;
}

四、兄弟进程间的通信

管道除了可以进行父子之间的通信还可以进行兄弟之间的通信。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char** argv)
{
    pid_t pid;
    int fd[2];
    char buf[1024];
    int i = 0;
    pipe(fd);//创建管道
    for(i = 0; i < 2; i++)
    {
        if((pid = fork()) == 0)
        {
            break;
        }
    }
    if(pid > 0)
    {
        /*父进程*/
        printf("Parent pid : %d\n", getpid());
        close(fd[0]);
        close(fd[1]);
        wait(NULL);
        wait(NULL);
    }
    if(i == 0)
    {
        /*兄进程*/
        printf("old brother pid : %d\n", getpid());
        close(fd[0]);//关闭读端
        write(fd[1], "Hello brother", strlen("Hello brother") + 1);
        close(fd[1]);        
    }
    else if(i == 1)
    {
        /*弟进程*/
        printf("brother pid : %d\n", getpid());
        close(fd[1]);//关闭写端
        read(fd[0], buf, 1024);
        printf("brother read buf : %s\n", buf);
        close(fd[0]);             
    }
    return 0;
}

注意点:

父进程需要将读端和写端都关闭,将使用权限给兄弟进程,这样才能保证数据的正常传输。

五、管道的读写行为

当读端不存在时

管道的表现取决于操作系统的实现。一般情况下,当读端不存在时,写入进程会收到一个错误信号(SIGPIPE)。这是因为写入进程试图往一个没有读取端的管道写入数据,操作系统会认为这是一个非法操作,因此向写入进程发送SIGPIPE信号,通知其管道的读取端已经不存在。如果写入进程没有处理这个信号,它可能会被终止。因此,在使用管道进行通信时,写入进程应该处理SIGPIPE信号,以避免意外终止。

当写端不存在时

读取进程会从管道中读取数据,直到读取完所有的数据。读取进程不会收到错误信号,因为写入端不存在不会影响读取端的操作。读取进程会读取到写入进程已经写入的数据,并在读取完数据后返回一个特殊的值,通常是0,表示已经读取到了管道的末尾。

总结

本篇文章就讲解到这里,下篇文章讲解fifo有名管道。


相关文章
|
8天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
1月前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
116 4
linux进程管理万字详解!!!
|
24天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
62 8
|
21天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
69 4
|
1月前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
1月前
|
消息中间件 存储 Linux
|
2月前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
58 1
|
2月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
32 1