Linux讲解 进程间通信 管道

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Hanani_Jia/article/details/82806753

  今天我们讲解进程间的通信,首先回顾一下进程的概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

  进程的用户空间是相互独立的,一般来说是不能相互访问的,但是很多情况需要我们不同的进程共同完成一个任务,或者进程之间需要沟通交流的时候,就必须要用到我们的进程间通信。

  经常在以下场景的时候用到我们的进程间的通信:

  1. 数据传输:一个进程需要向另一个进程发送数据信息的时候,并且数据信息不能太大的时候。

  2. 资源共享:多个进程之间共享某一部分的资源,假如其中一个进程对一个数据进行了修改,另一个进程要知道。

  3. 通知事件:一个进程需要向另一个进程发送信息,通知他们发生了某种事件的时候,比如我们的子进程结束的时候要发消息给我们的父进程。

  4. 进程控制:当某一些进程控制另一部进程的时候,这时候这个进程就需要知道他想操控的进程的异常等等信息。并且能够知道它的一些状态改变。

  进进程间通信主要包括管道, 系统IPC(包括消息队列,信号,共享存储), 套接字(SOCKET).今天我们主要讲解第一种--管道。管道是unix上中最古老的进程间通信的形式。管道的本质就是一块缓冲区,管道有一个很大的特性就是半双工、单向通信。也就是说数据只能向其中一个方向流动。如果需要双方通信的时候,就需要创建两个管道。

  系统给我们提供了创建管道的接口,我们可以通过pipe函数来创建一个管道。

int pipe(int pipefd[2]); 这里的pipefd[2]数组是用来接收我们的管道创建成功之后返回的两个文件描述符pipefd[0]用于从管道读取数据,而pipefd[1]用于往管道中写入数据。大家也可以看到我们的函数返回值是int类型,如果我们的管道成功创建会返回0,如果创建失败则返回-1。并且使用我们的pipe函数的时候需要包含我们的#include<unistd.h>头文件。

#include<stdio.h>

#include<unistd.h>

#include<string.h>

#include<errno.h>

//实现匿名管道

int main()

{

    int fd[2];

    if(pipe(fd)<0)

       {

           perror("pipe error\n");

           return -1;

       }

    int pid=-1;

    pid=fork();//创建一个子进程

    if(pid<0)

    {

        perror("pid error\n");

        return -1;

    }

    if(pid==0)

     {

         //进来说明是子进程,我们让子进程来读取数据

        close(fd[1]);//管道是半双工、单向所以需要关掉一个,这里关掉的是fd【1】写入数据的文件描述符

        char buff[1024];

        sleep(5);

        read(fd[0],buff,1024);

        printf("child: %s\n",buff);

        close(fd[0]);

     }

    else

    {

        //这个进来的是父进程

        close(fd[0]);

        write(fd[1],"hello ",6);

        write(fd[1],"world ",6);

        write(fd[1],"pipe ",5);

        close(fd[1]);

    }

    return 0;

}

我们通过程序来创建一个管道,然后用它来进行传输信息。

因为我们在程序中对子进程就行了休眠5s所以这里执行程序之后需要等5s才能输出数据,这里我们是为了保证父进程把数据写进去了,其实这里3s,1s都可以。这算是实现了父进程和子进程之间的通信,这是我们管道的实现。

  当我们运行完fork函数之后会变成上图的样子,子进程复制了父进程,他们都有一个fd数组都能对管道进行读写,但是我们需要对它进行关闭。

关掉各自不用的之后就是一个读一个写。

我们站在文件描述符来看我们创建的管道的时候就是

父进程创建了一个管道之后新建的两个文件描述符分别指向了我们管道的读端和写端,之后我们通过fork来创建一个子进程

这时候我们的子进程就也有对应的文件描述符指向我们和父进程同一个的管道。

之后关掉之后我们就可以用对应的文件描述符来对管道进行读写操作。

  我们的管道读写存在四种情况:

  1. 我们的写端不断的往管道里边写入数据,但是我们的读端却不去读取数据,我们的管道也是有大小的,这样下去就会导致我们的管道被数据写满,从而导致再次去调用write函数的时候导致阻塞,直到我们的管道中有空间写入数据的时候再去返回。

  2. 我们的管道写端没有关闭,但是他并不忘里边写数据,我们的读端却一直要去读取数据,这时候我们调用read函数的时候会导致堵塞,直到我们管道中有数据读取可以返回的时候再返回。

  3. 写端不写数据了,但是我们把写端的文件描述符fd[0]给关掉了,这时候我们读端读取完管道中剩余的数据之后,如果没有数据了,read就会返回0,和我们在读取文件的时候读取到文件结尾的情况是一样的。

  4. 读端不读取数据了,并且关掉了我们读取的描述符fd[1],但是写数据的一段还在不断的写,进程就会收到SIGPIPE信号导致我们的进程异常终止掉。

  在管道的写端或者读端有一个引用计数,就是来计算我们的写端或者读端是不是还有文件描述符在控制着它。

  管道有几大特点:

  1. 只能用于有亲缘关系之间的进程使用,通常就是我们上边介绍的由一个父进程创建一个管道然后通过fork函数来实现父子间都可以使用的管道。

  2. 一般而言,管道的生命周期和进程是一样的,随着进程的退出管道也会释放。

  3. 一般来说进程对管道的操作是互斥和同步的。保证数据访问的一致性。

  4. 管道是半双工的,当需要双向通信的时候需要创建两个管道。

 

   

猜你喜欢

转载自blog.csdn.net/Hanani_Jia/article/details/82806753