每个进程都有各自不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到。所以进程之间要交换数据必须要通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间考到内核缓冲区,进程2载从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
如图:
管道是一种最基本的IPC机制,由pipe函数创建。
调用pipe函数,在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端和一个写端,然后通过函数参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端。所以,管道在用户程序看起来就像一个打开的文件。可通过read、write函数向文件读写数据。向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,失败返回-1。
pipe函数的调用过程:
1.父进程创建管道
2.创建一个子进程
3.父进程关闭读端fd[0],子进程关闭写端fd[1]
父进程调用pipe函数开辟管道,得到两个文件描述符指向管道的两端。
父进程调用fork创建一个子进程,子进程继承父进程的文件描述符集,则子进程也有两个文件描述符指向同一管道的两端。
父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读。管道是用环形队列实现的,数据从写端流入,从读端流出。这样就实现了进程间通信。
1.只能单向通信
2.匿名管道只适用于具有血缘关系的进程间通信
3.依赖于文件系统,生命周期随进程
4.自带同步功能,防止读到垃圾数据,以互斥为前提
5.基于无格式字节流
6.管道的缓冲区是有限的(管道存在于内存中,在管道创建时,为缓冲区分配一个页面大小)
下面解释一下
为什么管道只能单向通信?
来看下Linux的实现,数据只能单向移动的意思是FIFO,于是Linux实际中构建了一个循环队列。具体的则是,在内核申请一个缓冲区,作为pipe()操作中匿名管道的实体,缓冲区设置两个指针,一个读指针,一个写指针。并保证读指针向前移动不能超过写指针,否则唤醒写进称并让读指针睡眠,直到读满需要的字节数。同理,读指针向前移动也不能超过写指针,否则唤醒写进程并让读进程睡眠,直到写满要求的字节数。说白了,管道在操作系统内部就是一个环形队列。
pipe()返回的两个文件句柄最后指向的其实是一个inode,只不过是一个read only一个是write only。试想同时有两个进程读[或者写,假设只有两个进程]的后果。由于i_count会等于2--如果小于2则说明两个进程同时关闭了写句柄,因此会退出读函数。此时两个进程会分别认为对方才是写者而反复醒来,反复监测,然而没有数据,于是反复睡眠。如果有多个进程,两进程同时读[或者写],会造成数据混乱,因为读指针只有一个,而你不能保证读写的顺序。
所以,想要使用管道完成双向通信,只需要创建两个管道就可以了。
为什么管道只适用于有血缘关系的进程间通信?
因为管道是通过文件描述符来控制数据读写的。所以只有血缘关系的进程才会产生指向同一个管道的文件描述符。
管道容量是多少?
用命令 ulimit -a可以查看,----->512Bytes*8 = 4096Bytes
也可以用程序代码验证
四、管道的四种情况
1.读端不读了,但不关闭读文件描述符,当管道写满时
如果指向管道读端的文件描述符没有关闭,(管道写端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程从管道的写端写数据,那么管道在被写满时再次write会阻塞,直到管道中有了空位置才写入数据并返回。
2.写端不写了,但不关闭写文件描述符,当管道中数据读完
如果指向管道写端的文件描述符没有关闭,(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会被阻塞,直到管道中有数据可读了才会读取数据并返回。
3.写端一直写,读端关闭读文件描述符
如果所有指向管道读端的文件描述符都关闭了,(管道写端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
4.读端一直读,写端关闭写文件描述符
如果所有指向管道写端的文件描述符都关闭了,(管道写端的引用计数等于0),而仍有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
1、2体现了同步机制
可以用于两个无血缘关系的进程间通信。命名管道和管道不通的在于它提供一个路径名与之关联,以FIFO的文件形式存储于2文件系统中,命名管道是一个设备文件,因此,即使进程与创建命名管道的进程之间无任何血缘关系,只要可以访问该路径,就能够通过命名管道相互通信。命名管道总是按照先进先出的原则工作。
mypipe
/*************************************************************************
> File Name: mypipe.c
> Author: ZX
> Mail: [email protected]
> Created Time: Sat 13 May 2017 06:49:35 PM PDT
************************************************************************/
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe failed");
}
pid_t id = fork();
if(id == 0)
{
//child
close(fd[0]);//write -- close read
int i = 0;
char* msg = "hello world!";
while(i < 10)
{
ssize_t s = write(fd[1], msg, strlen(msg));
printf("i am writing! %d\n",i++);
fflush(stdout);
}
}
else
{
//father
// close(fd[1]);
// char buf[1024];
// while(1)
// {
// ssize_t s = read(fd[0], buf, sizeof(buf));
// if(s > 0)
// {
// buf[s] = 0;
// printf("father read: %s\n", buf);
// }
// if(s == 0)
// {
// printf("end of file!\n");
// }
// }
int status = 0;
waitpid(id, &status, 0);
}
return 0;
}
server
/*************************************************************************
> File Name: server.c
> Author: ZX
> Mail: [email protected]
> Created Time: Sat 13 May 2017 07:26:10 PM PDT
************************************************************************/
#include
#include
#include
#include
#include
#include
int main()
{
int ret = mkfifo("./mypipe", S_IFIFO | 0666);
if(ret < 0)
{
perror("mkfifo failed!");
return 1;
}
int fd = open("./mypipe", O_RDONLY);
if(fd < 0)
{
perror("open failed!");
return 2;
}
else
{
while(1)
{
char buf[1024];
memset(buf, '\0', sizeof(buf));
ssize_t s = read(fd, buf, sizeof(buf));
if(s > 0)
{
buf[s-1] = 0;
printf("server read:%s", buf);
}
else if(s == 0)
{
printf("client closed!server is closing!\n");
break;
}
}
}
return 0;
}
/*************************************************************************
> File Name: client.c
> Author: ZX
> Mail: [email protected]
> Created Time: Sat 13 May 2017 07:26:10 PM PDT
************************************************************************/
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("./mypipe", O_WRONLY);
if(fd < 0)
{
perror("open failed!");
return 1;
}
else
{
while(1)
{
printf("Please Enter: ");
fflush(stdout);
char msg[1024];
memset(msg, '\0', sizeof(msg)-1);
ssize_t s = read(0, msg, sizeof(msg));
write(fd, msg, sizeof(msg));
}
}
return 0;
}