【Linux学习笔记】进程间通信之管道

在了解管道之前,我们先来了解下进程之间为什么需要通信。

1、进程间通信的目的

  • 数据传输: 一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它发什么了某种事件
  • 进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个所有陷入和异常,并能够及时知道它的状态。

那么为什么进程不直接通信呢?
进程具有独立性,它们时互相独立的,每个进程都有自己的PCB,在fork()时,父子进程都采用的是写时拷贝,因此,在每个进程看来,自己享有所有的内存资源,所以两个进程不能直接通信。我们只有让两个或多个进程访问到同一个资源,才能达到通信的目的。

2、管道

什么是管道
  • 管道是unix中最古老的进程间通信的形式。
  • 我们把一个进程连接到另一个进程的一个数据流称为一个“管道”。
  • 管道的本质是内核的一块缓存。

这里写图片描述

管道的内核缓存大小是65536字节,往管道中写入数据时,一次写入大小不要超过PIPE_BUF(4096)

管道分为两种:匿名管道和命名管道。

1.匿名管道
#include <unistd.h>
功能:创建一个无名管道
原型:
    int pipe(int fd[2]);
参数:
fd:文件描述符    fd[0]为表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码

这里写图片描述

下面我们来看一个例子:

//父进程向管道中写数据,子进程读数据

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main( void )
{

    int fds[2];
    pipe(fds); // 创建管道

    if ( fork() == 0 ) { // 子进程读取管道
        close(fds[1]); // 管道的读端是fds[0], 所以关闭fds[1]

        char buf[100] = {};
        read(fds[0], buf, 100); // 读取管道中的内容
        printf("child,buf=[%s]\n", buf);
        close(fds[0]); // 关闭管道的读端
        exit(0);
    } else { // 父进程向管道中写入数据
        close(fds[0]); // 父进程要往管道中写数据,所以关闭fds[0]
        char *msg = "this is maomaochong";
        sleep(2);
        write(fds[1], msg, strlen(msg)); // 两秒之后向管道中写入数据
        close(fds[1]);
    }
}

fork之后
这里写图片描述
fork之后关掉不用的描述符
这里写图片描述

从文件描述符的角度来看:

1.父进程创建管道

2.父进程fork出子进程
这里写图片描述

3.父进程关闭fd[0],子进程关闭fd[1]
这里写图片描述

匿名管道的特点
  • 只能用于具有共同祖先的进程(具有亲缘关系的,兄弟进程也可以)之间通信。
  • 一般而言,进程退出,管道释放。所以管道生命周期随进程。
  • 一般而言,内核会对管道进行同步与互斥。
  • 管道提供面向字节流服务,数据只能向一个方向传输,双方传输时,需要建起两个管道。
管道的读写规则
  • 写方一只写,读方不读:write调用阻塞,直到有进程读走数据。
  • 写方不写,读方一直读:read调用阻塞,直到有数据来为止。
  • 写方将写端关闭,读方一直读:读方将读完然后返回0;
  • 写方一直写,读方将读端关闭:会产生SIGPIPE信号,终止write进程。
2.命名管道
  • 匿名管道的一个限制就是必须在具有共同祖先的进程间通信。
  • 如果想在毫无相关的进程间交换数据,就要使用命名管道。
  • 命名管道是一种特殊类型的文件

创建一个命名管道有两种方式:

在命令行创建:  
$  mkfifo filename

命名管道可以从程序里创建,相关函数是;
 int mkfifo(const char8 filename,mode_t mode);
  成功返回0,失败返回-1

下面我们来看一个例子:
writepipe.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main( void )
{
    int fd = open("my.p", O_WRONLY);

    char buf[10] = {};
    for (int i=0;  ; i++ ) {
        sprintf(buf, "%c", 'A'+i%26);
        write(fd, buf,  1);
        printf("write %d ok\n", i);
        sleep(1);
    }
}

readpipe.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main( void )
{
    int fd = open("my.p", O_RDONLY);

    char buf[1024];
    while ( 1 ) {
        memset(buf, 0x00, sizeof(buf));
        read(fd, buf, 1024);
        printf("buf=[%s]\n", buf);
    }
}

这里写图片描述

匿名管道与命名管道的区别
  • 匿名管道有函数pipe创建并打开
  • 命名函数有mkfifo创建,打开用open
  • 命名管道与匿名管道之间唯一的区别在他们创建于打开的方式不同,一旦这些工作做好之后,他们具有相同的的语义。
  • 最大的区别是:命名管道时文件在硬盘上有对应的文件,而匿名管道是在内核中实现的。
打开规则
  • 为读打开时:
    无写方:阻塞直到有相应进程来打开该FIFO
    有写方:返回成功;
  • 为写打开:
    无读方:阻塞直到有相应进程为读而打开该FIFO
    有读方:返回失败;

猜你喜欢

转载自blog.csdn.net/lyjwonderful/article/details/80471117
今日推荐