Loading

进程间通信方式(一)管道

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信IPC,InterProcess Communication)。

管道 pipe (半双工)

管道 (pipe)是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。

pipe管道-进程通信.png

管道特质

  1. 其本质是一个伪文件(实为内核缓冲区)
  2. 由两个文件描述符引用,一个表示读端,一个表示写端
  3. 规定数据从管道的写端流入管道,从读端流出

pipe的原理

管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

pipe的局限性

​ ① 管道采用半双工通信方式。即数据只能在一个方向上流动。

​ ② 只能在有公共祖先的进程间使用管道。

​ ③ 数据一旦被读走,便不在管道中存在,不可反复读取

④ 数据自己读不能自己写。

管道的读写行为

从管道中「读数据」

  1. 管道中有数据

    read返回实际读到的字节数。

  2. 管道中无数据

    (1) 管道写端全部关闭(管道写端引用计数为0),read返回0 (就像读到文件结尾)。

    (2) 管道写端全部关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)。

往管道「写数据」

  1. 管道读端全部关闭

    进程异常终止(也可使用捕捉 SIGPIPE 信号,使进程不终止,errno设置为EPIPE)。

  2. 管道读端没有全部关闭

    (1) 管道已满,write阻塞。

    (2) 管道未满,write将数据写入,并返回实际写入的字节数。

pipe程序示例

【实际步骤】

「父进程」向「子进程」发送消息:

  1. 创建管道;
  2. fork;
  3. 父进程关闭读端 fd[0] ,子进程关闭写端 fd[1] ;
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>


int main(int argc, char const *argv[]) {
  int fd[2];
  int ret;
  pid_t pid;
  char *str = "hello pipe\n";
  char buf[1024];

  // 1. 创建 pipe
  ret = pipe(fd);
  if (-1 == ret) {
    perror("pipe create error: ");
    exit(1);
  }
  // 2. fork
  if (-1 == (pid = fork())) {
    perror("fork error: ");
  }
  // 3. 父进程关闭读端 fd[0] ,子进程关闭写端 fd[1] 
  else if (pid > 0) { // 父进程写管道
    close(fd[0]); // 关闭读端
    sleep(2); // 父进程sleep,管道无数据且写端未全部关闭,子进程阻塞等待
    write(fd[1], str, strlen(str)); // 写入数据
    wait(NULL);
    close(fd[1]); 
  }
  else if (0 == pid) { // 子进程读管道
    close(fd[1]); // 关闭写端
    ret =read(fd[0], buf, sizeof(buf));
    if (-1 == ret) {  // 读数据
      perror("read error: ");
      exit(1);
    }
    write(STDOUT_FILENO, buf, ret); // 输出到屏幕
    close(fd[0]);
  }

  waitpid(pid, NULL, 0);
  return 0;
}

FIFO

FIFO指先进先出(first in,first out),常被称为有名管道(named pipe),以区分管道(pipe)。管道(pipe)只能用于 「有血缘关系」的进程间。使用 FIFO,不相关的进程也能交换数据*。

FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); // 默认 O_CREAT | O_EXCL

FIFO读写性质

只有读写都存在时,才能打开FIFO。及以下情况:

  1. 有进程以只写方式打开,且有进程以只读方式打开;
  2. 以读写方式打开。

由下面程序可验证:

  • 当没有写进程打开 FIFO 时读进程打开 FIFO 将阻塞
  • 当所有读进程结束后,写进程将终止

FIFO程序示例

写进程实际步骤】

  1. 创建 FIFO (已存在则跳过);
  2. 以只写方式打开 FIFO;
  3. 写数据;
  4. 关闭 FIFO 写。
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    int fd, i;
    char buf[4096];

    if (argc < 2) {
        printf("Enter like this: ./a.out fifoname\n");
        return -1;
    }
    // 1. 创建 FIFO (已存在则跳过)
    if (mkfifo(argv[1], S_IRUSR | S_IWUSR) && errno != EEXIST)
        sys_err("caon't create FIFO");
	// 2. 以只写方式打开 FIFO
    fd = open(argv[1], O_WRONLY);
    if (fd < 0) 
        sys_err("open");
	// 3. 写数据
    i = 0;
    while (1) {
        sprintf(buf, "hello FIFO %d\n", i++);
        write(fd, buf, strlen(buf));
        sleep(1);
    }
    // 4. 关闭 FIFO
    close(fd);
    return 0;
}

读进程实际步骤】

  1. 创建 FIFO (已存在则跳过);
  2. 以只读方式打开 FIFO;
  3. 读数据;
  4. 关闭 FIFO 读。
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[])
{
    int fd, len;
    char buf[4096];

    if (argc < 2) {
        printf("./a.out fifoname\n");
        return -1;
    }
	// 1. 创建 FIFO (已存在则跳过)
    if (mkfifo(argv[1], S_IRUSR | S_IWUSR) && errno != EEXIST)
        sys_err("caon't create FIFO");
	// 2. 以只读方式打开 FIFO
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) 
        sys_err("open");
    // 3. 读数据
    while (1) {
        len = read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
        sleep(3);           //多个读端时应增加睡眠秒数,放大效果.
    }
    // 4. 关闭 FIFO 读
    close(fd);
    return 0;
}

管道和FIFO的额外属性

  • O_NONBLOCK 标志对管道和FIFO的影响

O_NONBLOCK标志对管道和FIFO的影响.png

  • 原子性 完全由所请求字节是否小于等于 PIPE_BUF

参考

《UNIX环境高级编程:第3版》

《UNIX网络编程:第2卷 进程间通信》

posted @ 2021-01-25 14:10  JakeLin  阅读(270)  评论(0编辑  收藏  举报