说明:只供学习交流,转载请注明出处
一,管道的基本概念
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
无名管道又被称为pipe,是Linux所支持的IPC方式的一种。它具有如下特点:
(1):只能用于血缘关系的进程之间通信。
(2):属于半双工的通信模式(进程在发送数据的同时不能接收到从管道传过来的数据)。具有固定的读端和写端。
(3):是一种特殊的文件,存在内存中。可通过read、write对其进程操作。
说明:全双工指的的在发送数据的同时,还可以接收数据。在某些版本的UNIX中,管道是全双工的。但在Linux中,管道是半双工模式的。
二,无名管道的用法
因为pipe存在内存中,所以无法像操作普通文件那样通过制定路径来打开文件。通常的做法是在父进程中创建管道,在创建子进程。由于子进程继承了父进程打开的文件描述符,所以父子进程就可以通过创建的管道进程通信。
为了在子父进程中创建管道,需要先定义一个包含两个元素的整形数组,用来存放管道的读端和写端对应的文件描述符。该数组在创建管道时作为参数传递。要注意的是,管道是一种半双工通信方式,即对进程来说,要么只能读管道,要么只能写管道。不充许对管道又读又写。其中数组的第一个元素固定代表管道的读端,第二个元素代表管道的写端。对于一个进程来说,只会用到其中的一个。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。
注意:必须在系统调用fork()之前调用pipe()否则子进程将不会继承文件描述符。
三,管道数据的读出和写入
一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。在读写时,为了保证数据流向需要关闭一个进程的读端和另一个进程的写端。而关闭管道只需要将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
四,pipe函数
头文件 | #include <unistd.h> | ||
函数原型 | int pipe(int filedes[2]); | ||
返回值 | 成功 | 失败 | 是否设置errno |
0 | -1 | 是 |
函数功能:创建一个无名管道。
参数说明:filedes[2]代表管道的两个文件描述符,管道创建后可以直接操作者两个文件描述符。其中filedes[0]代表读端(管道头),filedes[1]代表写端(管道尾)。
错误信息:
EFAULT:参数filedes非法。
EMFILE:进程使用了过多的文件描述符。
ENFILE:进程打开的文件数达到系统上限。
实例:
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int pipe_fd[2];
pid_t pid;
char buf[100] = {'\0'};
int num = 0;
if (pipe(pipe_fd)<0)
{
printf("Pipe create error!\n");
return (-1);
}
if ((pid=fork())==0)
{
printf("\n");
close(pipe_fd[1]);//子进程关闭了写端
sleep(2);//让父进程先运行,这样父进程先写子进程才有内容读
if ((num=read(pipe_fd[0], buf, 100))>0)
{
printf("%d numbers read from the pipe is %s\n", num, buf);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid > 0)
{
close(pipe_fd[0]);//父进程关闭了读端
if (write(pipe_fd[1], "hello", 5) != -1)
{
printf("Parent write1 hello!\n");
}
if (write(pipe_fd[1], " pipe", 5) != -1)
{
printf("Parent write2 pipe!\n");
}
close(pipe_fd[1]);
waitpid(pid, NULL, 0);//等待子进程结束
exit(0);
}
return (0);
}
运行结果:
[root@localhost test]# ./pipe
Parent write1 hello!
Parent write2 pipe!
10 numbers read from the pipe is hello pipe
[root@localhost test]#
说明:我们知道无名管道的创建是在fork()前,通过pipe()创建管道(包括管道头和管道尾,分别对应读端和写端),然后通过fork()创建子进程。创建子进程后,子进程会拷贝父进程的代码段、数据段、以及堆栈段。因此fork()后,pipe()创建的管道会被复制一份。一份归父进程,一份归子进程。为了让父子进程能够通过管道正常通信,而不至于在父子进程都在向管道写或者读时,管道中的数据错乱,就必须处理已有的管道。比如在例子中,为了保证父进程写,子进程读,就要关闭父进程的读和子进程的写。这样才能保证数据流向。而且为了保证子进程能够读到数据,就要延时等到父进程已经向管道写入数据才行。