Linux进程间通信 - (一) 管道
一、概述
管道是Linux进程间通信的一种方式,它把一个程序的输出直接连接到另一程序的输入。Linux的管道主要包括两种:无名管道和有名管道。
无名管道
只能用于具有亲缘关系的进程之间的通信
(如父子进程或者兄弟进程之间)。- 是一个单工的通信模式,具有固定的读端和写端。
管道也可以看成一种特殊的文件,对于它的读写也可以使用普通的read()、write()等函数。但是它不属于任何文件系统,并且只存在于内存中。
有名管道
-
它可以使互不相关的两个进程实现彼此通信。
-
该管道可以通过路径名来指出,并且
在文件系统中是可见的
。在建立了管道后,两个进程就可以把它当做普通文件进行读写操作,非常方便。 -
FIFO严格地遵循先进先出规则,对管道及FIFO的读写总是从开始处返回数据,对它们的写则把数据添加到末尾。
有名管道不支持如lseek()等文件定位操作。
两种管道的原理如下图所示:
二、编程接口
无名管道
- 无名管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符:
fd[0]
和fd[1]
。其中
fd[0]固定用于读管道,而fd[1]固定用于写管道.
- 管道关闭时只需要用
close()
函数将这两个文件描述符关闭即可。
具体形式如下图所示:
管道创建函数
创建管道可以通过pipe()函数来实现,语法如下:
头文件
#include <unistd.h>
函数原型
int pipe(int fd[]);
函数传入值
fd:包含两个元素的整形数组,存放管道对应的文件描述符
函数返回值
成功:0
出错:-1
管道的读写
用pipe()
函数创建的管道两端处于一个进程中。由于管道主要是用于不同进程间通信,通常是先创建一个管道,再调用fork()函数创建一子进程,子进程会继承父进程所创建的管道。此时,父子进程管道的文件描述符对应的关系如下:
需要注意的是,无名管道是单工的工作方式,即进程要么只能读管道,要么只能写管道。父子进程虽然都拥有管道的读端和写端,但是只能使用其中一个。(例如,我们可以约定父进程读,子进程写)。这样就应该把不使用的读端或写端的文件描述符关闭。如上图所示,我们就建立以一个“父进程读,子进程写的管道”。同样的而我们也可以父进程写而子进程读。
如果,我们在父进程创建了多个子进程,那么各个子进程都会继承父进程的fd[0]和fd[1]。使用上面的方法,我们就可以建立亲缘进程间的通信渠道了。
管道读写的注意事项:
- 只有在管道的的读端存在时,写端才会有意义,否则,向管道写入数据的进程将收到内核传来的SIGPIPE(通常为Broken Pipe错误)。
- 向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区只要有空间,写进程就会试图向管道写入数据。
如果管道缓冲区已满,那么写操作将会一直阻塞
。 - 父子进程在运行时,他们的先后顺序并不能保证。所以我们不能保证父子进程已经关闭了相应的文件描述符,那么我们可以在相应的进程中调用sleep()函数,等待父/子进程完成相应的关文件描述操作。当然,这个是我们目前来看作的一种妥协,实际上我们可以用进程间的同步与互斥机制。
有名管道
有名管道(FIFO)的创建可以使用mkfifo()
函数,该函数类似文件中的open()操作,可以指定管道的路径和访问权限(我们也可以在命令行中使用"mknod <管道名>"来创建有名管道);
在创建管道成功之后,就可以用open()
、read()
、write()
等函数来进行读写操作,像操作普通文件一样。同样的我们可以通过open()函数向使用普通文件一样对管道设置相应的权限,如只读(O_RDONLY)。
-
对于读进程
缺省的情况下,如果当前FIFO内没有数据,读进程将一直阻塞到有数据写入或是FIFO写端都被关闭。 -
对于写进程
只要FIFO有空间,数据就可以被写入。若空间不足,写进程会阻塞,直到数据都写入为止。
mkfifo()函数语法
所需头文件
#include <sys/types.h>
#include <sys/stat.h>
函数原型
int mkfifo(const char *filename,mode_t mode);
参数
filename:要创建的管道名
mode:管道的访问权限
函数返回值
成功:0
出错:-1
FIFO相关出错信息
EACESS
参数filename所指定的目录路径无可执行的权限
EEXIST
参数filename所指定的文件已存在
ENAMETOOLONG
参数filename的路径名称太长
ENOENT
参数filename包含的文件不存在
ENOTDIR
参数filename中的目录存在但却非真正的目录
ENOSPC
文件系统剩余空间不足
EROFS
参数filename指定的文件存在于只读文件系统内