【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - 管道
一、 无名管道
无名管道是临时的,在完成通信后将自动消失,因为文件描述符只能在某个进程中可见,因此被广泛应用于具有亲缘关系的进程间实现通信。采用的方法是先创建管道,再创建进程,使子进程继承父进程的创建的管道文件描述符,而要用无名管道实现非亲缘关系进程间的通信,则需要专门的文件描述符传递机制,这是可以实现的但需要借助其他机制,比如本地socket,此内容将在socket编程章节讲解。
1. 无名管道通信原理
2. 创建无名管道
3. 读写无名管道
(1) 以阻塞的方式读无名管道,如果当前没有进程(包括当前进程)可以访问写端,读操作都将立即返回。并按如下方式操作。
- 如果管道现无数据,立即返回0。
- 如果管道现有数据大于要读取的数据,立即读取期望大小的数据。
- 如果管道现有数据小于要读取的数据,立即读取现有所有数据。
- 如果管道现无数据,读操作阻塞。
- 如果管道现有数据大于要读取的数据,立即读取期望大小的数据。
- 如果管道现有数据小于要读取的数据,立即读取现有所有数据。
二、文件描述符重定向
(1)cat<test01
(2)cat>test02<test01
(3)cat>test02 2>error <test01
(4)cat>test02 1&2 <test01
(5)cat 1&2 1>test02<test01
三、重定向编程dup和dup2函数
输入重定向、输出重定向、错误输出重定向。dup和dup2函数可以实现文件描述符的复制操作。
需要注意的是,dup和dup2函数复制文件描述符的功能对所有文件都适用,但这一操作与在同一进程中再次打开某个文件是有本质的区别的,在同一进程中再次打开某文件将分配新的struct file文件表项,两者完全独立,而dup和dup2函数是共享struct file文件表项的。
用dup2函数实现who|sort,示例代码:
<span style="font-size:18px;">#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int fds[2];
if(pipe(fds)==-1)
{
perror("pipe");
exit(EXIT_FAILURE);
}
if (fork()== 0)
{
char buf[128];
dup2(fds[0], 0);
close(fds[1]); //must include ,or block
execlp("sort", "sort", (char *)0);
//execlp("cat", "cat", (char *)0);
}
else
{
if(fork() == 0)
{
dup2(fds[1], 1);
close(fds[0]);
execlp("who", "who", (char *)0);
}
else
{
close(fds[0]);
close(fds[1]);
wait(NULL);
wait(NULL);
}
}
return 0;
}</span>
四、流重定向popen函数
popen示例代码,实现功能echo test|cat:
<span style="font-size:18px;">#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<limits.h>
#include<string.h>
int main(int argc,char *argv[])
{
FILE *finput,*foutput;
char buffer[PIPE_BUF];
int n;
finput=popen("echo -e test!","r");
foutput=popen("cat","w");
read(fileno(finput),buffer,strlen("test!"));
write(fileno(foutput),buffer,strlen("test"));
pclose(finput);
pclose(foutput);
printf("\n");
exit(EXIT_SUCCESS);
}</span>
五、有名管道FIFO
FIFO可以在同主机任意进程间实现通信。
1. 创建方法
2. 读写有名管道
和无名管道一样,有名管道也是一种特殊类型的文件,实质仍然是一段内核管理的内存空间,但在通过write和read系统调用来执行读写操作前,需要用open函数线打开管道文件。读写方式与无名管道类似,具体内容参考教材229页内容。
3. 非亲缘关系进程使用有名管道通信应用实例
向有名管道发送数据的进程代码:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/my_fifo"
int main(int argc,char *argv[])
{
int pipe_fd;
int res;
char buffer[]="hello world!";
if (access(FIFO_NAME, F_OK) == -1)
{
res = mkfifo(FIFO_NAME, 0766);
if (res != 0)
{
fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, O_WRONLY);
printf("the file's descriptor is %d\n", pipe_fd);
if (pipe_fd != -1)
{
res = write(pipe_fd, buffer, sizeof(buffer));
if (res == -1)
{
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
printf("write data is %s,%d bytes is wirte\n",buffer,res);
(void)close(pipe_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
从有名管道读取数据的进程代码:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/my_fifo"
int main(int argc,char *argv[])
{
int pipe_fd;
int res;
char buffer[4096];
int bytes_read = 0;
memset(buffer, '\0', sizeof(buffer));
printf("Process %d opening FIFO O_RDONLY\n", getpid());
pipe_fd = open(FIFO_NAME, O_RDONLY);
printf("the file's descriptor is %d\n",pipe_fd);
if (pipe_fd != -1)
{
bytes_read = read(pipe_fd, buffer, sizeof(buffer));
printf("the read data is %s\n",buffer);
close(pipe_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}
运行操作过程,首先编译发送数据的进程代码和读取数据的进程代码。
执行写端,运行结果:
$ ./fifo_write
Process 3191 opening FIFO O_WRONLY//因为没有进程打开读端,阻塞open函数
接着在另一终端执行读端,运行结果:
$ ./fifo_read
Process 3267 opening FIFO O_RDONLY
the file's descriptor is 3
the read data is hello world!
Process 3267 finished, 13 bytes read
执行读端后,写端输出以下信息:
the file's descriptor is 3
write data is hello world!,13 bytes is wirte
Process 3191 finished
六、两类型管道具有以下特点
(1)管道是特殊类型的文件,在满足先入先出的原则条件下可能进行读写,但不能定位读写位置。
(2)管道是单向的,要实现双向,需要两个管道。无名管道只能实现亲缘关系进程间通信(即无名管道的两个文件描述符可以被两者都访问到),而有名管道以磁盘文件的方式存在,可以实现本机任意两进程间通信。
(3)无名管道阻塞问题。无名管道无须显式打开,创建时直接返回文件描述符,而在读写时需要确实对方的存在,否则将退出。即如果当前进程向无名管道的写数据时,必须确定其别一端为某个进程(这个进程可以是当前进程)拥有,即有一个(或多个)进程的文件描述符表中至少有一个成员指向管道的另一端(显然,能够读写管道当前端,则本端在当前进程中是可以访问的)。如果写入无名管道的数据超过其最大值,写操作将阻塞,如果管道中没有数据,读操作将阻塞,如果管道发现另一端断开(另一端文件描述符关闭),将自动退出。
(4)有名管道阻塞问题。有名管道在打开时需要确实对方的存在,否则将阻塞。即以读方式打开某管道,该操作得以继续执行的条件是:在此之前,已经有一个进程以写的方式打开此管道,否则阻塞,直到条件满足,因此有名管道将阻塞在打开位置。也可以以读写(O_RDWR)方式打开有名管道,进程能够继续执行(不阻塞),只是这样操作没有什么意思,即当前进程读,当前进程写。