Linux中,每个进程都有各自的地址空间及自己的用户级页表,映射到物理内存的不同地方,因此进程间互不影响,即进程间相互独立。
那么不同的进程要如何实现进程间通信呢?也就是进程间通信的本质,就是不同的进程通过看到公共资源来实现进程间通信,而这里的公共资源一般由操作系统提供,不同的提供者、提供方式也决定了通信方式的不同。
进程间有五中通信方式,分别为:匿名管道、命名管道、信号量、消息队列、共享内存。这里先介绍一下匿名管道通信。
匿名管道(pipe)
函数:int pipe(int pipefd[2]);
注:调用pipe函数时,首先在内核中开辟一块缓冲区用于通信,它有一个读端和一个写端,然后通过pipefd参数传出给用户进程两个文件描述符,pipefd[0]指向管道的读端,pipefd[1]指向管道的写段。在用户层面看来,打开管道就是打开了一个文件,通过read()或者write()向文件内读写数据,读写数据的实质也就是往内核缓冲区读写数据。
返回值:成功返回0,失败返回-1。
管道特征:
1、管道只支持单向通信
2、管道通信只能用于有血缘关系的通信,常用于父子通信,只用于父子进程的管道(匿名管道)
3、管道的生命周期随进程的结束而终止
4、管道是基于字节流通信的通信方式
5、管道内部已经实现同步机制,能够保证数据一致性(保证数据的安全性,在写数据的时候不会被别人读,不会发生二义性)
实现进程通信步骤:
1、父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端
2、父进程调用fork创建子进程,子进程也有两个文件描述符指向管道的两端
3、父进程关闭fd[0](读端),子进程关闭fd[1](写端)
例:
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<string.h>
int main()
{
int fds[2];
if(pipe(fds)<0)
{
perror("pipe");
return 1;
}
pid_t id=fork();//fork创建子进程
if(id==0)
{
//child
close(fds[0]);//子进程关闭读端
const char* child="hello father,I'm child";
int i=0;
while(i<10)
{
sleep(1);
write(fds[1],child,strlen(child));
i++;
}
exit(0);
}
else
{
//father
close(fds[1]); //父进程关闭写端
char buf[1024];
while(1)
{
ssize_t s=read(fds[0],buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("father received:%s\n",buf);
}
}
}
pid_t ret=waitpid(id,NULL,0);
if(ret>0)
{
printf("wait success!\n");
}
}
运行结果:
父进程成功收到子进程写入的字符串。
使用管道时有4种特殊情况:
1、读端没关闭,读端没有读数据,写端在写数据,管道被写满时写会阻塞,直到有空了才写入数据返回(读端没有在读,写端一直在写)
代码验证:
运行结果:
父进程执行3次读操作后不再读取数据,而子进程一直在在写,管道写满时发生阻塞。
2、指向管道的进程描述符的写端没关闭,持有管道写端的进程没有向管道写数据,此时有进程从管道读端读数据,管道剩余数据读取后,再次read会阻塞,知道有数据可读才读取数据返回(读方一直在读,写方没有在写)
代码验证:
运行结果:
3、管道读端的文件描述符都关闭了,此时向管道的写端写,进程会收到信号SIGPIPE,通常导致进程异常终止
代码验证:
运行结果:
父进程的读端read3次后关闭读端,此时子进程依然在write,进程异常终止。
4、写方前期一直在写,某一时刻停止,并关闭写端,读方一直读,读到读完管道内所有数据管道结束,read返回0值,表示读到文件结尾
代码验证:
运行结果: