【Linux 应用编程】进程管理 - 进程间通讯IPC之管道 pipe 和 FIFO

IPC(InterProcess Communication,进程间通讯)是进程中的重要概念。Linux 进程之间经常使用的通讯方式有:mysql

  • 文件:简单,低效,须要代码控制同步
  • 管道:使用简单,默认阻塞
    • 匿名管道 pipe:只存在于内核缓冲区,只能用于有血缘关系的进程
    • 有名管道 FIFO:在文件系统中存在,可用于无血缘关系的进程
  • 信号量:使用复杂,但开销小,操做系统自己支持信号量
  • 内存映射区 mmap:进程有无血缘关系均可以
  • 本地套接字 socket:稳定可靠

管道概念

经过管道,能够把一个进程的输出做为另外一个进程的输入。管道分为两种:web

  • 匿名管道:主要用于两个进程间有父子关系的进程间通讯
  • 命名管道:主要用于没有父子关系的进程间通讯

一般用的管道,都是匿名管道。管道其实是一块内核缓冲区,不占用磁盘空间,但操做方式跟文件是同样的。sql

在 Linux 的命令行终端中,管道是再经常使用不过的命令技巧了。经过管道能够把前一个命令的输出做为后一个命令的输入。在 Linux 命令中一般经过符号 | 来使用管道。数组

例如,查看全部的进程,而后按关键字过滤:缓存

ps aux | grep mysql

匿名管道

匿名管道是不能在文件系统中以任何方式看到的半双工管道,不占磁盘空间。管道的特色有:bash

  • 一端只读或只写。读端、写端分别是一个文件描述符
  • 操做管道的进程退出后,管道自动释放
  • 管道默认是阻塞的
  • 半双工,数据在管道内只能单向传输

匿名管道没法判断消息是否被读完,一般仅用于父进程向子进程传递数据,或子进程向父进程传递数据。此时能够在父子进程中关闭不用的读或写端。数据结构

管道是基于环形队列这个数据结构实现的,数据先进先出。默认的缓冲区大小是 4 KB,大小会自动调整。socket

  • 单工:数据单向流动,例如遥控器
  • 双工:数据能够同时双向流动,例如手机
  • 半双工:数据能够双向流动,但不可同时双向流动,例如对讲机

pipe 函数

pipe() 函数用于建立匿名管道。.svg

函数原型:函数

#include <unistd.h>

int pipe(int pipefd[2]);

参数:返回文件描述符数组,对应管道的两端,往写端写的数据会被内核缓存起来,直到读端将数据读完。其中:

  • pipefd[0]:读端
  • pipefd[1]:写端

返回值:成功返回 0,不然返回-1。

示例:

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

int main()
{
    int fd[2];
    int ret, pid;
    char buf;
    ret = pipe(fd);
    if (ret == -1)
    {
        perror("pipe error");
        exit(1);
    }

    pid = fork();
    if (pid == 0)
    {
        close(fd[1]);
        while(read(fd[0], &buf, 1) > 0)
        {
	        // 两种方式均可以实现输出
            printf("%c\n", buf);
            write(STDOUT_FILENO, &buf, 1);
        }
        close(fd[0]);
    } 
    else if (pid > 0)
    {
        close(fd[0]);
        write(fd[1], "hello world", 12);
        close(fd[1]);
        wait(NULL);
    } 
    else
    {
        perror("fork error");
        exit(1);
    }

    close(fd[0]);
    close(fd[1]);
    return 0;
}

命名管道

命名管道也叫 FIFO 文件,在文件系统中以文件名的形式存在,大小为0。匿名管道没法在无关进程之间通讯,但 FIFO 能够借助文件系统中的文件,使得同一主机内的全部的进程均可以通讯。

进程经过 FIFO 通讯时,首先要打开管道文件,而后使用 read 、write 函数通讯。

mkfifo 函数

函数原型:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

参数:

  • pathname:指定文件名
  • mode:指定文件的读写权限

返回值:
函数成功返回 0,不然返回-1 并设置 errno,常见 errno 有:

  • EACCES:pathname 所在的目录不容许执行权限
  • EEXIST:pathname 已经存在
  • ENOENT:目录部分不存在
  • ENOTDIR:目录部分不一个目录
  • EROFS:路径指向一个只读的文件系统

示例:
建立两个进程,一个将文件内容读到 FIFO,另外一个从 FIFO 读内容写到另外一个文件。
写 FIFO 的文件示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define BUFSIZE 1024

int main(int argc, char *argv[]){
    int ret;
    int datafd, fifofd;
    int bytes;
    char buffer[BUFSIZE];
    const char *fifoname = "/tmp/fifo";

    if (argc != 2) {
        printf("please input filename\n");
        exit(EXIT_FAILURE);
    }

    if (access(fifoname, F_OK) < 0) {
        ret = mkfifo(fifoname, 0777);
        if (ret < 0) {
            perror("mkfifo error");
            exit(EXIT_FAILURE);
        }
    }
    fifofd = open(fifoname, O_WRONLY);
    datafd = open(argv[1], O_RDONLY);
    if (fifofd < 0) {
        perror("open fifo error");
        exit(EXIT_FAILURE);
    }
    if (datafd < 0) {
        perror("open file error");
        exit(EXIT_FAILURE);
    }

    bytes = read(datafd, buffer, BUFSIZE);
    while (bytes > 0) {
        ret = write(fifofd, buffer, bytes);
        if (ret < 0) {
            perror("write fifo error");
            exit(EXIT_FAILURE);
        }
        bytes = read(datafd, buffer, BUFSIZE);
    }
    close(fifofd);
    close(datafd);
    return 0;
}

读 FIFO 的文件示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define BUFSIZE 1024

int main(int argc, char *argv[]) {
    char *fifoname = "/tmp/fifo";
    int fifofd, datafd;
    int bytes, ret;
    char buffer[BUFSIZE];

    if (argc != 2) {
        printf("please input a filename");
        exit(EXIT_FAILURE);
    }

    fifofd = open(fifoname, O_RDONLY);
    datafd = open(argv[1], O_WRONLY);
    if (fifofd < 0) {
        perror("open fifo error");
        exit(EXIT_FAILURE);
    }
    if (datafd < 0) {
        perror("open file error");
        exit(EXIT_FAILURE);
    }

    bytes = read(fifofd, buffer, BUFSIZE);
    while(bytes > 0) {
        ret = write(datafd, buffer, bytes);
        if (ret < 0) {
            perror("write file error");
            exit(EXIT_FAILURE);
        }
        bytes = read(fifofd, buffer, BUFSIZE);
    }
    close(datafd);
    close(fifofd);
    return 0;
}