目录
操作系统为用户提供的几种用于进程间进行通信的方式:管道,共享内存,消息队列,信号量。
进程间通信原因 : 进程具有独立性(每个进程都有自己的虚拟地址空间,访问的都是自己的虚拟地址,将一个数据的地址就算交给其他进程,其他进程在自己的虚拟地址空间映射中实际上是没有这个数据的,进程的独立性可以让程序更加稳定的运行),因此进程间无法直接通信,但是在中大型项目中多个进程协同工作很常见,这时候进程间通信就尤为重要。进程间通信根据不同的应用场景,使用不同的方式。
管道pipe介绍
管道-PIPE : 具有半双工通信的特性,数据从哪端到哪端由用户来定。
管道的分类 : 匿名管道,命名管道 ( 外在用法不同,但是本质相同 )
本质:内核中开辟的一块缓冲区(内存),多个进程通过访问同一个缓冲区实现通信
匿名管道 : 内核中开辟的这块缓冲区,没有标识符,无法被其他进程找到(只能用于具有亲缘关系的进程间通信)
命名管道 : 内核中开辟的这块缓冲区,具有标识符,能够被其他进程找打(可以用于同一主机上任意进程间通信)
匿名管道只能用于具有亲缘关系的进程间通信:一个进程通过系统调用在内核中创建了一个匿名管道,为了能够让用户操作这个管道,因此调用返回了文件描述符作为这个管道的操作句柄,其他进程因为这个管道没有标识符,找不到所以无法通信,但是,如果这个创建管道的进程创建了一个子进程,这时候子进程复制了父进程(文件描述信息表),所以子进程相当于也有文件描述符可以操作这个管道,在匿名管道中,只有通过子进程复制父进程的方式才能获取到同一个管道的操作句柄。
命名管道内容 : 一个进程创建一个命名管道,这个命名管道会在文件系统中创建出一个管道文件(可以看得到的,实际上就是管道的名字),多个进程通过打开同一个管道文件,访问内核中同一个缓冲区实现通信。注意:命名管道文件只是一个名字,只是为了让进程能够找到同一个缓冲区。
接口:
匿名管道的创建: int pipe( int pipefd[2] )
pipefd[2] : 具有2个int元素的数组,用于接收管道创建成功所返回的文件描述符(操作句柄)
pipefd[0] : 用于从管道中读取数据;
pipefd[1] : 用于向管道中写入数据
返回值 : 成功返回0 ; 失败返回-1;
注意 : 匿名管道的创建一定要在创建子进程之前, 这样才能被子进程复制到操作句柄管道是内核的一块缓冲区,不能无限制增长,会造成资源耗尽,系统崩溃
pipe代码示例
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int pipefd[2]; //pipefd[0]用于读 pipefd[1]用于写
int ret = pipe(pipefd);
if (ret < 0)
{
perror("pipe error");
return -1;
}
pid_t pid = fork();
printf("%d\n",pid);
if (pid < 0)
{
perror("fork error");
return -1;
}
else if(pid == 0)
{
//child write
char *str = "hello world\n";
//int write(int fd ,void *data, int len)
write(pipefd[1],str,strlen(str));
}
else
{
//parent read
char buff[1024] = {0};
read(pipefd[0],buff,1023);
printf("read:%s",buff);
}
close(pipefd[0]);//当子进程复制了父进程之后,描述符各自有各自的,所以也需要各自进行关闭一次
close(pipefd[1]);//父进程关闭,并不代表子进程也关闭了 因为“各自独立” ”
return 0; //关闭文件描述符并不是释放缓冲区,也不是删除文件,只是断开了和管道的一端连接
}
关闭文件描述符并不是释放缓冲区,也不是删除文件,只是断开了和管道的一端连接而已,缓冲区是当连接数为0,也就是所有打开管道的句柄都关闭了才会被释放。
pipe的特性
管道的读写特性:
如果管道中没有数据,则read会阻塞 ; 如果管道中数据满了,则write会阻塞
管道的其他特性:
所有写端关闭,则read读完数据后不再阻塞,返回0
所有读端关闭,则write触发异常,进程退出
关闭写端代码
// pipe_close_read.c
int main()
{
signal(SIGPIPE,SIG_IGN);
int pipefd[2]; //pipefd[0]用于读 pipefd[1]用于写
int ret = pipe(pipefd);
if (ret < 0)
{
perror("pipe error");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
else if(pid == 0)
{
sleep(1); //child write
close(pipefd[0]); //关闭子进程的读
char *str = "hello world\n";
//int write(int fd ,void *data, int len)
write(pipefd[1],str,strlen(str));
perror("write---");
}
else
{
close(pipefd[0]); //关闭父进程的读
sleep(60);
}
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
<——运行结果
关闭读端代码
// pipe_close_write.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int pipefd[2]; //pipefd[0]用于读 pipefd[1]用于写
int ret = pipe(pipefd);
if (ret < 0)
{
perror("pipe error");
return -1;
}
pid_t pid = fork();
printf("%d\n",pid);
if (pid < 0)
{
perror("fork error");
return -1;
}
else if(pid == 0)
{
close(pipefd[1]); //关闭子进程的写
}
else
{
sleep(1);
close(pipefd[1]); //关闭父进程的写
char buff[1024] = {0};
int ret = read(pipefd[0],buff,1023);
if(ret == 0)
{
perror("all write closed");
return -1;
}
}
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
<——运行结果
Shell中管道符的实现 ps-ef | grep **
定义两个进程,ps-ef 以及 grep,ps-ef 将进程信息标准输出到屏幕上,grep在标准输入中捕捉含有“ssh”的信息,由于他们两个互不相干,ps-ef 输出的信息并非 grep 操作的来源。我们向标准输入中写带有ssh的字符串时,grep才能进行捕捉。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t ps_pid = fork();
if(ps_pid == 0)
{
execlp("ps","ps","-ef",NULL); //程序替换
exit(0); //失败退出
}
pid_t grep_pid = fork();
if(grep_pid == 0)
{
execlp("grep","grep","ssh",NULL);
exit(0);
}
wait(NULL);
wait(NULL);
return 0;
}
运行结果:
而shell中,使用了管道,将 ps-ef 标准输出的数据交给 grep
我们来模拟实现:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int pipefd[2];
if(pipe(pipefd)<0)
{
perror("pipe error");
return -1;
}
pid_t ps_pid = fork();
if(ps_pid == 0)
{
dup2(pipefd[1],1); //将标准输出重定向到pipefd[1],也就是pipe的写端
execlp("ps","ps","-ef",NULL); //程序替换 ps程序运行完毕后进程退出,所有资源被释放,包括描述符的关闭
exit(0); //失败退出
}
pid_t grep_pid = fork();
if(grep_pid == 0)
{
close(pipefd[1]); //grep是循环从管道中读取数据的,关闭写端,否则会阻塞
dup2(pipefd[0],0); //将标准输入重定向到pipefd[0],也就是管道的读端
execlp("grep","grep","ssh",NULL);
exit(0);
}
close(pipefd[1]); //关闭父进程的写
close(pipefd[0]); //关闭父进程的读
wait(NULL);
wait(NULL);
return 0;
}
运行结果:
此时,ps-ef 将获取到的数据写入管道,而非放到标准输出中;grep不再从标准输入中读取信息,而是从管道中读取带有“ssh”的信息,并放到标准输出中。
匿名管道总结
1.本质:内核中的一块缓冲区(没有标识符)
2.操作:int pipe(int pipefd[2])
3.特性:半双工通信
数据先进先出;(流式传输)
管道没有数据则read阻塞,数据满了则write阻塞
所有写端关闭,则read读完数据后不再阻塞,返回0
所有读端关闭,则write触发异常,进程退出
只能用于具有亲缘关系的进程间的通信