1.进程间通信(IPC):
把数据从用户空间拷到内核缓冲区,另一个进程再从内核缓冲区将数据读走,这种由内核提供的机制叫做进程间通信。
2.管道
管道是一种最基本IPC机制,由pipe函数创建
3.创建管道的函数pipe( )
int pipe( pipefd[2] );
①函数调用成功时返回0,失败返回-1②函数分别以读方式和写方式打开一个文件,使用3,4作为文件描述符;
③pipefd[0]指向读管道,保存读描述符;
pipefd[1]指向写管道,保存写描述符;
4.使用管道实现进程间通信的步骤
①父进程创建管道,得到两个文件描述符指向管道;
②父进程fork( )出子进程,子进程也会有两个文件描述符;
③父进程关闭读端pipefd[0],子进程关闭写端pipefd[1],即父子进程指向一个公共管道;
5.实现代码:
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int main()
{
int pipefd[2]={0,0};
if(pipe(pipefd)<0)//父进程创建管道
{
perror("pipe");
return 1;
}
pid_t pid=fork();//父进程fork( )出子进程
if(pid<0)
{
perror("fork");
return 2;
}
else if(pid==0)
{//child
close(pipefd[0]);//关闭子进程的读端
int i=0;
char* msg="Hello,i am child\n";
while(i<100)
{
write(pipefd[1],msg,strlen(msg)+1);
sleep(1);
i++;
}
}
else
{//father
close(pipefd[1]);//关闭父进程的写端
char buffer[100];
int j=0;
while(j<100)
{
if((read(pipefd[0],buffer,sizeof(buffer)))>0)
{
printf("father#%s",buffer);
}
j++;
}
}
}
结果如图:
子进程向管道中写“hello,i am child”,父进程从管道中读取到这句话,打印出来;
6.管道通信的特点:
①只能进行单向通信;
②只能为有血缘关系的进程间通信;
③管道通信依赖文件系统,生存周期随进程,即进程退出时,管道也退出;
④管道进行数据通信时,按数据流读写;
⑤管道自带同步机制;
7.管道的四种情况
(1)当关闭了管道的所有写端时,但仍然有进程要从管道的读端读数据,所以管道中的剩余数据都被读取后,再次读取时会返回0.
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int main()
{
int pipefd[2]={0,0};
if(pipe(pipefd)<0)
{
perror("pipe");
return 1;
}
pid_t pid=fork();
if(pid<0)
{
perror("fork");
return 2;
}
else if(pid==0)
{//child
close(pipefd[0]);
int i=0;
char* msg="Hello,i am child";
while(i<10)
{
write(pipefd[1],msg,strlen(msg)+1);
sleep(1);
i++;
}
close(pipefd[1]);
}
else
{//father
close(pipefd[1]);
char buffer[100];
int j=0;
while(j<100)
{
memset(buffer,'\0',sizeof(buffer));
int ret=read(pipefd[0],buffer,sizeof(buffer));
printf("father#%s,ret=%d\n",buffer,ret);
j++;
}
}
return 0;
}
结果如图所示:
读完10次后,返回0
(2)当指向管道的写端没关闭,而写端进程也没有向管道中写数据,但此时有进程从管道读端读数据,那么管道中剩余数据都被读完时,再次读取会被阻塞;
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int main()
{
int pipefd[2]={0,0};
if(pipe(pipefd)<0)
{
perror("pipe");
return 1;
}
pid_t pid=fork();
if(pid<0)
{
perror("fork");
return 2;
}
else if(pid==0)
{//child
close(pipefd[0]);
int i=0;
char* msg="Hello,i am child";
while(i<20)
{
if(i<10)
{
write(pipefd[1],msg,strlen(msg)+1);
}
sleep(1);
i++;
}
close(pipefd[1]);
}
else
{//father
close(pipefd[1]);
char buffer[100];
int j=0;
while(j<20)
{
memset(buffer,'\0',sizeof(buffer));
int ret=read(pipefd[0],buffer,sizeof(buffer));
printf("father#%s,ret=%d\n",buffer,ret);
j++;
}
}
return 0;
}
结果如图所示:
(3)当管道的所有读端被关闭,此时有进程向写端写入数据,此时会收到信号SIGPIPE,导致进程异常终止;
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int main()
{
int pipefd[2]={0,0};
if(pipe(pipefd)<0)
{
perror("pipe");
return 1;
}
pid_t pid=fork();
if(pid<0)
{
perror("fork");
return 2;
}
else if(pid==0)
{//child
close(pipefd[0]);
int i=0;
char* msg="Hello,i am child";
while(i<20)
{
if(i<10)
{
write(pipefd[1],msg,strlen(msg)+1);
}
sleep(1);
i++;
}
close(pipefd[1]);
}
else
{//father
close(pipefd[1]);
char buffer[100];
int j=0;
while(j<3)
{
memset(buffer,'\0',sizeof(buffer));
int ret=read(pipefd[0],buffer,sizeof(buffer));
printf("father#%s,ret=%d\n",buffer,ret);
j++;
}
close(pipefd[0]);
sleep(10);
}
return 0;
}
结果如图所示:
(4)当指向管道的读端未关闭,而且进程也没有读数据,此时有进程向写端写数据,那么管道被写满时,再次写入会被阻塞;
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int main()
{
int pipefd[2]={0,0};
if(pipe(pipefd)<0)
{
perror("pipe");
return 1;
}
pid_t pid=fork();
if(pid<0)
{
perror("fork");
return 2;
}
else if(pid==0)
{//child
close(pipefd[0]);
int i=0;
char* msg="Hello,i am child";
while(i<20)
{
if(i<10)
{
write(pipefd[1],msg,strlen(msg)+1);
}
sleep(1);
i++;
}
close(pipefd[1]);
}
else
{//father
close(pipefd[1]);
char buffer[100];
int j=0;
while(j<0)
{
memset(buffer,'\0',sizeof(buffer));
int ret=read(pipefd[0],buffer,sizeof(buffer));
printf("father#%s,ret=%d\n",buffer,ret);
j++;
}
close(pipefd[0]);
sleep(10);
}
return 0;
}
结果如图所示:
8.命名管道(FIFO)与匿名管道的区别
上面所描述的管道都为匿名管道;
①命名管道可以实现两个毫不相关的进程间通信,而匿名管道只能实现有血缘关系的进程间通信;② 命名管道允许在硬盘上进行文件创建,而匿名管道不允许;
③命名管道可以通过命令创建,也可以通过函数mkfifo( )创建,匿名管道通过pipe( )函数创建;
④命名管道是一个存在于硬盘上的文件,而匿名管道是存在于内存的特殊文件;
⑤使用命名管道前,必须先调用open( )将它打开;