目录
一、概念
二、匿名管道
(一)概念
(二)函数介绍
(三)示例代码
(四)原理
三、命名管道
(一)概念
(二)函数介绍
(三)示例代码
(四)原理
三、管道的读写规则
四、管道的本质
一、概念
管道是Linux下一种进程间通信的形式,我们把一个进程连接到另一个进程的数据流称为一个管道。
二、匿名管道
(一)概念
匿名管道是Linux操作系统中用于进程间通信的一种机制。它允许两个进程进行双向通信(半双工),必须是拥有血缘关系的进程,通常用于父子进程之间的通信。
匿名管道是“匿名”的,因为它只在创建它的进程和它创建的子进程之间存在,并且没有文件系统中的对应文件。
(二)函数介绍
NAMEpipe - create an interprocess channel
SYNOPSIS#include <unistd.h>int pipe(int fildes[2]);
RETURN VALUEUpon successful completion, 0 shall be returned; otherwise, -1 shall be returned and errno set to indicate the error.
该函数是传入一个数组,底层创建管道并返回该数组,其中 fildes[0] 为读端, fildes[1] 为写端,成功时返回0,失败返回-1。
当我们创建好管道后,因管道为半双工传输数据,我们若关闭父进程的读写段时,也要相应的关闭子进程的写读段。
(三)示例代码
#include <stdio.h>
#include <unistd.h>
#include <cassert>
#include <cstring>
int main()
{//创建管道int pipefd[2];int ret = pipe(pipefd);assert(ret == 0);//创建子进程pid_t id = fork();if(id == 0){//子进程//关闭写端close(pipefd[1]);char buff[1024] = {0};ssize_t num = read(pipefd[0], buff, sizeof(buff));assert(num != -1);printf("%s", buff);}//父进程关闭读端close(pipefd[0]);const char* str = "i am father";ssize_t num = write(pipefd[1], str, strlen(str));return 0;
}
上述代码是父进程对管道进行写入,而子进程对管道进行读出并输出至屏幕。
(四)原理
pipe() 函数实际上是新打开了一个文件,而传入的数组是用来记录该文件的读写端,也就是数组内存储的元素实际上文件描述符,其中 fildfd[0]是为读的方式打开的文件的文件描述符, fildfd[1]是为写的方式打开的文件的文件描述符。
上文中提到,匿名管道一般用于具有血缘关系的进程间通信。其原理为:利用 fork() 创建子进程后操作系统会将父进程的相关内核数据结构拷贝给子进程,我们在调用 fork() 之前执行 pipe() 函数,为父进程创建文件(管道)并返回该文件的读写端,fork() 之后子进程也继承了对该文件的读写端,因此实现了父子进程通过该文件进行通信。
三、命名管道
(一)概念
命名管道是一种特殊的文件系统对象,它允许不相关的进程进行双向通信。与匿名管道不同,命名管道在文件系统中有一个名字和路径,因此它不仅限于父子进程之间的通信,任何拥有适当权限的进程都可以访问和使用它。
(二)函数介绍
NAMEmkfifo - make a FIFO special file (a named pipe)SYNOPSIS#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
RETURN VALUEOn success mkfifo() returns 0. In the case of an error, -1 is returned
该函数是在同级目录创建一个管道文件,其中 pathname 为该文件的名称,而 mode 则是创建该文件时的权限。
(三)示例代码
// .h
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PIPE_NAME "mypipe.pipe"
using namespace std;//process1.cpp
#include "proc.h"
int main()
{// 创建管道umask(0);int ret = mkfifo(PIPE_NAME, 0666);assert(ret != -1);// 打开管道文件int id = open(PIPE_NAME, O_WRONLY);assert(id != -1);// 写入管道const char *str = "Hello world";ssize_t num = write(id, str, strlen(str));assert(num != -1);return 0;
}//process2.cpp
#include "proc.h"
int main()
{// 打开管道文件int id = open(PIPE_NAME, O_RDONLY);assert(id != -1);// 写入管道char buff[1024] = {0};ssize_t num = read(id, buff, sizeof(buff) - 1);assert(num != -1);buff[num] = 0;cout << buff << endl;return 0;
}
上述代码是由process1创建管道文件并向管道文件进行写入,二process2打开管道并进入读出。
(四)原理
与匿名管道不同,命名管道是直接在程序中创建管道文件,并不依赖进程的继承因此适用于任何具有权限的进程进行通信,其本质也是对文件的操作,即不同的进程分别对同一文件进行读写。
三、管道的读写规则
管道大小是固定的,也就是其只能存储一定量的数据,下面是管道的读写规则:
1、当写端持续写入讲管道写满后,写端被阻塞,直到数据被读出;
2、当读端持续从管道读数据并读完后,读端被阻塞,直到数据被写入;
3、当管道建立并读写端打开后,若写端关闭,则 read() 读取会返回0;
4、当管道建立并读写端打开后,若读端关闭,则写端进程会直接退出。
四、管道的本质
无论是匿名管道还是命名管道,其本质都是文件,即符合Linux下一切皆文件。但实际上与普通的文件不同。
对于匿名管道来说,其读写的数据实际存储在内存中的一片缓冲区里;对于命名管道来说,虽然其有用独立的 inode 节点,但是读写的数据并不存在在文件本身(磁盘),而是被加载至内存中的缓冲区。也就是管道通信实际上是依托于文件系统,利用内存的进程间的通信。
管道的特点:
1、管道具有生命周期;
2、管道是面向字节流的;
3、管道是半双工通信;
4、管道可以用来进行具有血缘关系的进程间通信;
5、管道具有同步互斥的机制,以保护共享资源。