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有名管道。


相关文章
|
2天前
|
Linux Shell C语言
|
2天前
|
监控 Linux Shell
|
4天前
|
存储 Linux 程序员
【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解
【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解
|
5天前
|
Unix Linux
【Linux】一文了解【进程优先级相关知识点】&【PRI / NI值】背后的修正原理(13)
【Linux】一文了解【进程优先级相关知识点】&【PRI / NI值】背后的修正原理(13)
|
5天前
|
Linux 调度
【Linux】盘点广义层面上【三种最基本的进程状态】
【Linux】盘点广义层面上【三种最基本的进程状态】
|
5天前
|
Linux Shell
【Linux】深度解析Linux中的几种进程状态
【Linux】深度解析Linux中的几种进程状态
|
5天前
|
Linux Shell 调度
【Linux】用三种广义进程状态 来理解Linux的进程状态(12)
【Linux】用三种广义进程状态 来理解Linux的进程状态(12)
|
5天前
|
Linux Shell 调度
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
|
5天前
|
Linux Shell
【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】
【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】
|
6天前
|
算法 大数据 Linux
深入理解Linux内核的进程调度机制
【4月更文挑战第30天】操作系统的核心职能之一是有效地管理和调度进程,确保系统资源的合理分配和高效利用。在众多操作系统中,Linux因其开源和高度可定制的特点,在进程调度机制上展现出独特优势。本文将深入探讨Linux内核中的进程调度器——完全公平调度器(CFS),分析其设计理念、实现原理及面临的挑战,并探索未来可能的改进方向。