自信、冷静、专注。 —— TM熊的自我勉励
linux
进程间通信方式:
- 无名管道;
- 命名管道;
- 消息队列;
- 信号;
- 信号量;
- 共享内存;
- 网络通信;
本文介绍
linux
下进程间通信方式之一的无名管道。
概述
什么是管道?
想象下水管,水从一端流向另一端,正常情况下是不是只能从一端流入,一端流出。
管道又分为:
- 无名管道
- 有名管道
即使对无名管道不是很了解,但是在你使用linux
系统命令时应该经常接触到的,就是命令之间用于传递上个命令结果到下个命令作为参数的|
符号:
ps -aux | grep init
而无名管道与有名管道最直观的区别在于有名管道能在文件系统中找到对应类型的文件,举个例子:
$ mkfifo /tmp/tpipe
$ ls -l /tmp/tpipe
prw-r--r-- 1 wotsen wotsen 0 Oct 25 10:20 /tmp/tpipe
其中查看文件类型时,前面的p
标志就代表它是一个管道文件。
在一个终端内进行写:
$ echo "hello pipe" > /tmp/tpipe
在另一个终端内进行读取:
$ cat /tmp/tpipe
hello pipe
无名管道
管道文件也是一种文件,那么我们写管道、读管道也就是读写文件。
无名管道,顾名思义则是没有名称的管道文件,只存在内存中,有名管道则能在文件系统内查看到其名称。
无名管道特性:
- 无名管道具有半双工、单向的特性,只能一端写,一端读;
- 无名管道仅仅用于有亲缘关系的父子进程之间,你可以理解为因为管道没有名称,所以只能通过子进程继承的方式从父进程获取管道的句柄(文件描述符);
linux
中无名管道使用的场景非常多,例如绝大部分shell
程序都支持管道,命令行中使用|
方式将前一个命令的输出通过管道给到后一个命令。
管道的实现是在内存中分配的空间用于存储数据,但是使用时具有部分和普通文件相同的特性:文件描述符、read/write/fcntl
可用,所以在实际的使用过程比较简单。
无名管道API
创建无名管道:
#include <unistd.h>
int pipe(int pipefd[2]);
int pipefd[2]
:参数为2个长度的数组,用于存放管道创建成功时读和写的描述符,[0]用于读,[1]用于写;- 返回值0为成功,-1为异常。
Tips:如果不记得函数详细说明,可以使用man
命令查看,ubuntu
安装man
手册:
apt-get install manpages-de manpages-de-dev manpages-dev glibc-doc manpages-posix-dev manpages-posix
管道文件描述符中[0]用于读,[1]用于写,这是由于无名管道的特性引起的(先入先出,半双工,那么得规定哪端入,哪端出),简单模型如下:
不过实际使用中一般用于父子继承间通信,pipefd[2]
父子进程各有一对:
注意:在用于父子进程中通信时,确保父子进程仅有一方读,另一方写,不要父或子对同一个管道既可写又可读。如果父进程用于写入数据,子进程则用于读出数据,并且父进程应该将读端pipefd[0]关闭,子进程应该将写端pipefd[1]关闭。如果需要父子进程双向通信,应该另外创建一个管道(一对)。原因是无名管道是半双工的,避免同时读写造成数据混乱。
父子进程的双向通信模型:
关闭描述符:
和普通文件一样使用close()
进行关闭:
int close(int fd);
close(pipefd[0]);
close(pipefd[1]);
管道读写:
前面说过,管道的读写同文件读写一样,使用read/write
即可:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
read(pipefd[0], buf, count);
write(pipefd[1], buf, count);
设置阻塞,非阻塞:
创建管道时默认是阻塞的,不过使用fcntl
来设置,设置阻塞时将对应的标记为置为~O_NONBLOCK
,非阻塞时为O_NONBLOCK
:
int fcntl(int fd, int cmd, ... /* arg */ );
bool set_fd_block(