每个进程各自有不同的用户地址空间,任 何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲 区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)
无名管道PIPE只适用于父子进程间的通信,还有一种叫做有名管道FIFO将会在之后作介绍.
PIPE是Linux进程间通信方法之一,它是一个单向通道,先进先出,管道的尾端为写端,描述符pipe_fd[1] . 头部为读端,描述符为pipe_fd[0](注意,强制规定)
主要函数:
int pipe(int pipe_fd[2])
调用pipe函数在内核中开辟一块缓冲区(称为管道)用于单向通信,它有一个读端一个写端,然后通过filedes参数传给用户程序两个文件描述符,filedes[0]指 向PIPE的读端,filedes[1]指向PIPE的写端。所以在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]); 向这个文件读写数据 其实是在读写内核缓冲区.
创建无名管道的基本步骤:
- 父进程调用pipe(int pipe_fd[2])函数,创建管道并得到两个文件描述符,分别指向管道的头部和尾部,也就是读端和写端
- 父进程调用fork()函数创建子进程,因为fork()函数,子进程拷贝父进程的数据段和代码段,所以子进程也能得到第一步创建的两个文件描述符,且指向同一个管道
- 父进程关闭管道的读端,子进程关闭管道的写端(想想为什么这么做???)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
int main()
{
char buf[20];
pid_t pid;
int pipe_fd[2];
pipe(pipe_fd); //创建管道
if ((pid = fork()) < 0) //fork子进程
{
perror("fork error");
exit(-1);
} else if (pid == 0) //子进程中
{
close(pipe_fd[1]); //关闭写端
sleep(1);
read(pipe_fd[0],buf,sizeof(buf));
printf("%s",buf);
close(pipe_fd[0]); //关闭读端
exit(0); //正常退出
} else //父进程中
{
close(pipe_fd[0]); //关闭读端
write(pipe_fd[1],"hello world",strlen("hello world"));
close(pipe_fd[1]); //关闭写端
waitpid(pid,NULL,0); //等待子进程结束或中断,参数1为欲等待的子进程的标识码,参数2为
//子进程结束的状态值一般为NULL,参数3为等待何种子进程,为0时表示等待进程组识别码
//与目前进程相同的任务子进程
exit(0);
}
}
现在解释下之前创建无名管道基本步骤的第三部中的问题:
1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
注:之前一直没明白为什么在这里父进程要关闭管道读端,并且子进程要关闭管道写端,想了很久终于想通了...,原因如下:因为上面的这个程序是要模拟父进程和子进程的管道读写操作,其中父进程用于向管道中写入数据,子进程用于向管道中读取数据,因此开始要关闭父进程的读文件描述符filedes[0], 以及关闭子进程的写文件描述符filedes[1],这是为了模拟这个过程。
然后至于为什么父进程关闭管道的读文件描述符filedes[0]后子进程还能读取管道的数据,是因为系统维护的是一个文件的文件描述符表的计数,父子进程都各自有指向相同文件的文件描述符,当关闭一个文件描述符时,相应计数减一,当这个计数减到0时,文件就被关闭,因此虽然父进程关闭了其文件描述符filedes[0],但是这个文件的文件描述符计数还没等于0,所以子进程还可以读取。也可以这么理解,父进程和子进程都有各自的文件描述符,因此虽然父进程中关闭了filedes[0],但是对子进程中的filedes[0]没有影响
文件表中的每一项都会维护一个引用计数,标识该表项被多少个文件描述符(fd)引用,在引用计数为0的时候,表项才会被删除。所以调用close(fd)关闭子进程的文件描述符,只会减少引用计数,但是不会使文件表项被清除,所以父进程依旧可以访问
最后需要注意,在linux的pipe管道下,在写端进行写数据时,不需要关闭读端的缓冲文件(即不需要读端的文件描述符计数为0),但是在读端进行读数据时必须先关闭写端的缓冲文件(即写端的文件描述符计数为0)然后才能读取数据。