定义
管道是Unix中最古老的进程间通信的形式
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
内存在内核上,内存是一个缓存区
匿名管道
创建无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
用fork来解释管道共享
父进程原有的管道经过 fork 产生的子进程共用管道,读端写端都复制,fork 之后关闭不用的描述符,子进程进行读操作,父进程进行写操作
- 文件描述符角度
- 内核角度
从以上可以看出子进程将数据读到一个数据块中,父进程在数据块中进行写的操作,从中,管道的作用如同文件,符合Linux中一切皆文件
管道读写规则
- 管道对应的文件描述符状态被更改,为非阻塞
当没有数据可读时
O_NONBLOCK disable:写端关闭,read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:写端不关闭,read调用返回-1,errno值为EAGAIN。
当管道满的时候
O_NONBLOCK disable: 读端关闭,write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:读端不关闭,调用返回-1,errno值为EAGAIN
-
管道对应的文件描述符被关闭,即阻塞
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出 -
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
命名管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件
创建命名管道
- 从命令行上创建
$ mkfifo filename - 从程序里创建
int mkfifo(const char *filename, mode_t mode);
例:mkfifo(“p2”, 0644);
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开,命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与 pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义
- FIFO(命名管道)有标识符,pipe(匿名管道)没有标识符
命名管道的打开规则
-
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:写端关闭,阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:写端不关闭,立刻返回成功 -
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:读端关闭,阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:读端不关闭,立刻返回失败,错误码为ENXIO
命名管道应用
用命名管道实现文件拷贝
读取文件,写入命名管道
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
do
{
perror(m);
exit(EXIT_FAILURE);
} while(0)
int main(int argc, char *argv[])
{
mkfifo("tp", 0644);
int infd;
infd = open("abc", O_RDONLY);
if (infd == -1) ERR_EXIT("open");
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1) ERR_EXIT("open");
char buf[1024];
int n;
while ((n=read(infd, buf, 1024))>0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
读取管道,写入目标文件
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
do
{
perror(m);
exit(EXIT_FAILURE);
} while(0)
int main(int argc, char *argv[])
{
int outfd;
outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1) ERR_EXIT("open");
int infd;
infd = open("tp", O_RDONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
int n;
while ((n=read(infd, buf, 1024))>0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}