Linux进程间通讯 -- 管道

2024-01-07 19:19:59

Linux进程间通讯 – 管道

1. 原理

Linux 进程间通讯,也称为IPC(InterProcess Communication)

在 Linux 中每个进程都具备独立的进程地址空间,对每个进程的独立地址空间进行划分,在0G - 3G部分被划分为用户空间,而3G - 4G部分被划分为内核地址空间。注意此0G - 4G 为虚拟地址空间,实际上会通过MMU映射到物理地址空间。

在进行地址映射的时候,每个进程的用户空间在实际物理空间上将被映射到多个地址空间,而多个进程的内核空间将会被被映射到同一块区域,因此多个进程之间具备相同的内核地址空间,通过此共同的内核地址空间实现线程间数据交互即为进程间通讯,也即IPC。
在这里插入图片描述

2. 进程间通讯

Linux进程间通讯的方式主要分为以下四大种类型:

  1. 管道 pipe
  2. 信号 signal
  3. 共享映射区 mmap
  4. 本地套接字 socket

2.1 管道

使用管道实现进程间通讯的优点是:使用简单!

管道分为有名管道 FIFO 和匿名管道 pipe,匿名管道仅能用于具备血缘关系的进程间通讯;而有名管道因其具备了名字可以被找到,因此可用于无血缘关系的进程间通讯。

管道本质是一个伪文件,是由内核管理的一个缓冲区,同时此缓冲区被设计成环形,具备两个端口,一端连接数据的写入,一端连接数据的输出,因此管道仅可用于单向通讯的场合

当管道中无有效数据时,从管道中读取数据时进入阻塞等待状态,直至有数据从管道的写端写入。

当管道中数据被写满时,再次往管道内写入数据会进入阻塞等待状态,直至有数据从管道的读端被读走。

2.1.1 匿名管道 pipe

创建管道:

#include <unistd.h>

int pipe(int pipefd[2]);
  • 传入参数:包含两个文件指针的数组,pipefd[0] 指向管道的读端;pipefd[1] 指向管道的写端
  • 返回值:0:成功 -1:失败

示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, char **argv)
{
    int fd[2] = { 0 };
    int ret = 0;
    pid_t pid = 0;
    char buf[512] = {0};

    ret = pipe(fd);
    if (ret < 0) {
        perror("create pipe failed");
        return -1;
    }

    pid = fork();
    if (pid == 0) {
        printf("----------Is child process ------------\n");
        close(fd[0]);       /* close the read of pipe. */
        write(fd[1], "hello world!\n", sizeof("hello world!\n"));
    } else {
        if (pid < 0) {
            perror("fork failed");
            return -1;
        }
        printf("----------Is father process -----------\n");
        close(fd[1]);       /* close the write of pipe. */

        while (1) {
            memset(buf, 0, sizeof(buf));
            ret = read(fd[0], buf, sizeof(buf));
            printf("ret:%d read:%s\n", ret, buf);
            sleep(1);
        }
    }

    return 0;
}

运行结果如下:
在这里插入图片描述
运行分析:

  1. 使用pipe创建一个匿名管道
  2. 使用fork创建子进程,父子进程均拥有此pipe
  3. 子进程关闭管道读端,延时1s后往管道写端写入数据
  4. 父进程关闭管道写端,之后马上对管道读端读取数据,此时由于管道内没有有效数据,因此父进程发生阻塞,直至子线程往管道内写入数据
  5. 子进程写完数据后进程结束,对应的写端口被系统关闭,因此父进程读取管道直接返回0,不再阻塞。
    在这里插入图片描述
2.2.2 有名管道 FIFO

创建有名管道:

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

int mkfifo(const char *pathname, mode_t mode);
  • 传入参数:
    • pathname: 有名管道名字
    • mode: 有名管道对应文件的权限(8进制),最终文件权限为 (mode & ~umask),通常此值使用0664
  • 返回值:
    • 0: success -1:fail

注意事项:

  1. 如果存在有名管道对应名字的文件,则会返回错误 errno = EEXIST,因此没有做特殊处理时,重复运行此程序通常会报错。

  2. 创建好了有名管道之后,open此有名管道时,必须要求此管道的读端和写端均被打开,否则 open 函数会进入阻塞状态

    • 如果采用只读模式 open("test_fifo", O_RDONLY)打开,则open函数会阻塞直至其他函数采用O_WRONLYO_RDWR打开写端
    • 如果采用只写模式 open("test_fifo", O_WRONLY)打开,则open函数会阻塞直至其他函数采用O_RDONLYO_RDWR打开读端
    • 如果采用非阻塞方式打开只读端口 open("test_fifo", O_RDONLY | O_NONBLOCK) 不会报错,但采用非阻塞方式打开只写接口 open("test_fifo", O_WRONLY | O_NONBLOCK) 会返回 No such device or address 错误
    • 因此采用读写方式打开不会发送阻塞也不会报错 open("test_fifo", O_RDWR),但应用不一定安全!

示例:

fifo_r.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
    int fd = 0, ret = 0;
    char buf[512] = {0};

    if (mkfifo("test_fifo", 0664) < 0 && errno != EEXIST) {
        perror("mkfifo error");
        return -1;
    }

    //fd = open("test_fifo", O_RDONLY | O_NONBLOCK);
    fd = open("test_fifo", O_RDONLY );
    if (fd < 0) {
        perror("open fifo error");
        return -1;
    }

    while (1) {
        ret = read(fd, buf, sizeof(buf));
        printf("read: ret:%d, buf = %s\n", ret, buf);
        sleep(1);
    }
    
    return 0;
}

fifo_w.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char **argv)
{
    int fd = 0, ret = 0;
    int cnt = 0;
    char buf[512] = {0};

    if (mkfifo("test_fifo", 0664) < 0 && errno != EEXIST) {
        perror("mkfifo error");
        return -1;
    }

    //fd = open("test_fifo", O_RDWR);
    fd = open("test_fifo", O_WRONLY);
    if (fd < 0) {
        perror("open fifo error");
        return -1;
    }

    while (1) {
        cnt ++;
        snprintf(buf, sizeof(buf), "hello world! cnt:%d", cnt);

        ret = write(fd, buf, strlen(buf)+1);
        printf("write: ret:%d, buf = %s\n", ret, buf);
        sleep(2);
    }
    
    return 0;
}

运行结果:
在这里插入图片描述
对应创建的test_fifo文件如下
在这里插入图片描述

2.2 信号

//TODO:

2.3 共享内存

//TODO:

2.4 本地套接字

//TODO:

文章来源:https://blog.csdn.net/qq_43332314/article/details/134091116
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。