1. 匿名管道
匿名管道是UNIX系统IPC(进程间通信)最古老的方式,所有UNIX系统都提供这种通信机制
匿名管道有以下局限性:
- 历史上,它们是半双工的(数据只能在一个方向上流动)。现在某些操作系统提供全双工匿名管道,但是为了可移植性,应该按照半双工来进行编程
- 管道只能在具有公共祖先的两个进程间使用。通常一个管道由一个进程创建,在进程fork之后,父子进程之间通过该管道进行通信。
命名管道(FIFO)没有第二种限制,UNIX域套接字没有这两种限制
1.1 pipe函数
通过pipe函数创建管道
int pipe(int pipefd[2]);
函数成功返回后,pipefd中保存两个文件描述符:
- pipefd[0]:读打开
- pipefd[1]:写打开
即pipefd[1]的输出是pipefd[0]的输入。
对于全双工实现的匿名管道,这两个文件描述符都是读/写打开的。
对于匿名管道,通过fstat函数应用于其读端(pipefd[0])时,获得的stat文件属性中的st_size字段是无意义的。
下图中左图显示管道的两端在一个进程中相互连接;右图则强调数据需要通过内核在管道中流动
1.2 匿名管道常用操作
通常,进程先调用pipe,接着调用fork,从而创建父进程与子进程间的IPC通道。
对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1])。
对于从子进程到父进程的管道,父进程关闭管道的写端(fd[1]),子进程关闭读端(fd[0])。
1.2.1 当管道的一端被关闭后,遵守下列规则:
-
当read一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。
如果管道的写端还在,就并不会产生文件的结束(如果管道没数据,则read阻塞)
-
如果write一个读端已被关闭的管道,产生信号SIGPIPE。该信号默认操作是终止进程,如果忽略该信号或捕获到该信号且捕获函数返回,则write返回-1,errno置EPIPE。
1.2.2 内核的管道缓冲区大小:
在写匿名管道(或FIFO)时,常量PIPE_BUF规定了内核的管道缓冲区大小。如果对管道调用write且写入字节数小于PIPE_BUF,那么此操作不会与其他进程对同一管道(或FIFO)的write操作交叉。但是如果同时有多个进程写该管道(或FIFO)且我们write的数据量大于PIPE_BUF,那么我们所写的数据可能会与其他进程所写的数据相互交叉。
int p[2];
pipe(p);
cout << fpathconf(p[0],_PC_PIPE_BUF) << endl;
//打印4096,说明PIPE_BUF管道缓冲区大小是4096字节。
1.2.3 示例
示例:父进程创建管道并fork子进程,指定父进程为管道写入端、子进程为读取端,父子进程间单向通信
main.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(){
int pd[2];
int r = pipe(pd);//创建管道
printf("pipe: read: %d write: %d\n",pd[0],pd[1]);
int pid = fork();//fork子进程
if(pid){
//父进程执行此部分
close(pd[0]); //设置父进程为管道的写入端,因此关闭读取端文件描述符pd[0]
char buf[] = "this is some datas";
sleep(3); //父进程sleep三秒
printf("parent Process begin write datas\n");
write(pd[1],buf,strlen(buf)); //向管道写入数据
printf("parent Process write datas done\n");
}
else{
//子进程执行此部分
close(pd[1]); //设置子进程为管道的读取端,因此关闭写入端文件描述符pd[1]
char buf[30];
int num = read(pd[0],buf,sizeof(buf)-1); //从管道读取数据
buf[num] = '\0'; //字符串结尾
printf("son Process get %d datas : %s\n",num,buf);
}
}
运行结果
xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ gcc pipe_test.c
xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ ./a.out
pipe: read: 3 write: 4
parent Process begin write datas
parent Process write datas done
son Process get 18 datas : this is some datas
可见父进程成功将数据写入管道,子进程成功从管道读出数据。由于父进程在写入前sleep了三秒,可以证实管道读取端要读数据时,如果管道为空且存在写进程时会阻塞等待写入端写入数据后再读取。
1.2.4 补充:分页显示程序
常用的分页显示程序:more和less
如果想将输出结果按照一页一页的形式呈现出来,就可以使用管道实现:
fork一个子进程,然后让子进程的标准输入成为管道读端,然后exec分页程序。父进程向管道写端写输出内容即可。
示例:将文件内容通过分页程序显示出来
int main(int argc, char *argv[])
{
int fd[2