Linux进程间通信(管道)

举报
yd_274589494 发表于 2023/07/30 12:00:37 2023/07/30
【摘要】 @TOC 前言本篇文章将给大家讲解进程间通信中的管道使用方法和概念。 一、管道的概念管道的概念来源于Unix操作系统,在Unix-like系统(如Linux)中被广泛使用。它也存在于其他操作系统中,如Windows。管道可以将一个进程的输出直接连接到另一个进程的输入,从而实现数据的流动和传输。通过管道,一个进程产生的输出可以无需写入临时文件,而是直接传递给另一个进程进行处理,这样可以提高系统...

@TOC


前言

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

一、管道的概念

管道的概念来源于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有名管道。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。