常见的面试题:
1、你知道进程间通信的方式有哪几种吗?
2、这个通信方式具体的实现原理是什么?怎么实现的?
复习时候需要使用的问题:
1、进程间的通信是什么?进程间为什么需要通信?
2、进程间通信的目的是什么?(有四个理由)
一、进程间为什么需要通信?
进程是一个独立的分配资源的单位,这意味着,进程与进程之间是无法访问对方的资源的。但是进程之间的协作需要知道其他进程之间的状态信息,这需要通过通信来交换数据。(共享一个数据块里头的信息)
二、进程间通信的目的是什么?
1、数据传输
2、事件通知(比如一个进程终止的时候需要发送消息给父进程)
3、资源共享(进程之间共享同一块数据资源,需要内核提供互斥和同步的机制)
4、进程控制(有些进程需要完全控制另外一个进程的运行,比如 用 gcc 来 debug。此时这个进程需要知道另外一个进程的运行状态,异常和终端的情况)
三、进程之间的通信方式有哪些?
匿名管道的使用例子:
四、管道的特点
管道其实是在内核区域中能够存储数据的一块缓冲区。
管道的文件特质体现在哪里?
管道两端是文件操作符:输入和输出的文件操作符。
匿名管道多用于两个有关系的进程之间的通信,如父子进程。
有名管道多用于没有关系的进程之间的通信。
如何理解字节流:
流里面的数据是没有格式的,也没有所谓开头和结尾的分隔符,读取的时候可以按照基本的数据类型的格式读取。也可以按照任意的大小读取。
五、为什么匿名管道可以用于有关系的进程之间的通信?
父进程在 fork 子进程的时候会拷贝一份文件描述符,因此父子进程的文件描述符指向同样的文件。而匿名管道就是通过文件描述符来读取和写入数据的。拥有同样的文件描述符说明可以对同一个匿名管道进行操作。
六、管道的数据结构
管道的数据结构是一个环形的队列,是半双工的。
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc)
pipefd[0] 读端的文件描述符
pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)
*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
//管道创建,需要在 fork 之前还是在 fork 之后?
//需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
pid_t pid = fork();
if(pid > 0){
char buf[1024] = {0};
int len = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s ,pid : %d.\n",buf,getpid());
}
else if(pid == 0){
char *str = "hello, i am child.";
int len = write(pipefd[1],str,strlen(str));
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
}
}
管道默认是阻塞的,如果管道中没有数据,则 read 阻塞,如果管道已经写满了数据,则 write 阻塞。
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc,不用)
pipefd[0] 读端的文件描述符
pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)
*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
//管道创建,需要在 fork 之前还是在 fork 之后?
//需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
pid_t pid = fork();
if(pid > 0){
char buf[1024] = {0};
int len = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s ,pid : %d.\n",buf,getpid());
}
else if(pid == 0){
sleep(10);//通过延后写入数据来达到管道阻塞的效果
//延后写入数据会造成父进程在读数据的时候无数据可以读,父进程阻塞,终端显示程序输出滞后
char *str = "hello, i am child.";
int len = write(pipefd[1],str,strlen(str));
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
}
}
观察运行效果的时候,肉眼可见的阻塞了好久。
子进程持续向父进程发送数据,
父子程序同时写入和读出数据,会导致读到自己写入的数据。
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc)
pipefd[0] 读端的文件描述符
pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)
*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
//管道创建,需要在 fork 之前还是在 fork 之后?
//需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
pid_t pid = fork();
if(pid > 0){
printf("i am parent process, pid : %d .\n",getpid());
while (1)
{
char buf[1024] = {0};
int len = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s ,pid : %d.\n",buf,getpid());
}
}
else if(pid == 0){
printf("i am child process, pid : %d .\n",getpid());
// sleep(10);//通过延后写入数据来达到管道阻塞的效果
while (1)
{
char *str = "hello, i am child.";
int len = write(pipefd[1],str,strlen(str));
sleep(1);
}
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
}
}
父子进程能够通过管道向对方发送数据。
但是不可以同时先读,同时先读管道里面没有数据会造成阻塞。
如果同时先写会怎么样?
同时先写的运行结果,会得到自己写入的数据。
在写入数据之后都使用 sleep(1)来达到进程切换的目的,保证自己能够读到对方写入的数据。
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
//管道创建,需要在 fork 之前还是在 fork 之后?
//需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
pid_t pid = fork();
if(pid > 0){
printf("i am parent process, pid : %d .\n",getpid());
while (1)
{
char buf[1024] = {0};
int len1 = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s ,pid : %d.\n",buf,getpid());
char *str = "hello, i am parent.";
int len2= write(pipefd[1],str,strlen(str));
sleep(1);
}
}
else if(pid == 0){
printf("i am child process, pid : %d .\n",getpid());
// sleep(10);//通过延后写入数据来达到管道阻塞的效果
while (1)
{
char *str = "hello, i am child.";
int len1 = write(pipefd[1],str,strlen(str));
sleep(1);
char buf[1024] = {0};
int len2 = read(pipefd[0],buf,sizeof(buf));
printf("child recv : %s ,pid : %d.\n",buf,getpid());
}
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
}
}
查看管道的大小
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
//fpathconf()获取文件相关的配置信息
long size = fpathconf(pipefd[0],_PC_PIPE_BUF);
printf("pipe size : %ld \n",size);
}
使用 ulimits -a命令来查看
两者的结果是一致的。
三、匿名管道通信案例
如果子进程和父进程同时写,或者是写完之后读取的速度太块,会读到自己写的内容,这个是为什么?
正常的数据流向:
读到自己的内容的数据的流向:
个人觉得应该采用两个管道。。。。。
一般采用匿名管道都是一个进程读取一个进程写,如果两个进程都读都写会混乱。
上面的第一个示例程序当中会让进程休眠,交出 CPU时间片,所以不会出现这样的幺蛾子。
可以通过关闭文件操作符来关闭进程对管道读端和写端的控制。
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
pid_t pid = fork();
if(pid > 0){
printf("i am parent process, pid : %d .\n",getpid());
//父进程如果不写数据可以将写端关闭了
close(pipefd[1]);
while (1)
{
char buf[1024] = {0};
int len1 = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s ,pid : %d.\n",buf,getpid());
}
}
else if(pid == 0){
printf("i am child process, pid : %d .\n",getpid());
//子进程如果不读数据可以将读端关闭了
close(pipefd[0]);
while (1)
{
char *str = "hello, i am child.";
int len1 = write(pipefd[1],str,strlen(str));
}
}
}
四、使用管道进行通信的案例
/*
实现 ps aux | grep XXX 父子进程间的通信
子进程 : ps aux,子进程结束后, 将数据发送给父进程
父进程:获取到数据,过滤
pipe()
execlp()
子进程将指向终端的标准输出重定向到管道的写端
*/
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
//创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(0);
}
// return 0;
//创建子进程
pid_t pid = fork();
if(pid > 0){
//父进程
//关闭写段
close(pipefd[1]);
//从管道中读取,但是这种写法只能读取一次
char buf[1024];
// int len = read(pipefd[0],buf,sizeof(buf)-1);
// printf("%s",buf);
//循环读取
int len = -1;
while ((len = read(pipefd[0],buf,sizeof(buf)-1)) > 0){
printf("%s",buf);
}
//
}
else if(pid == 0){
//子进程
//关闭读端
close(pipefd[0]);
//文件描述符的重定向 STDOUT_FILENO->fd[1]
//否则会在当前的终端中输出
dup2(pipefd[1],STDOUT_FILENO);
//执行 ps aux
execlp("ps","ps","aux",NULL);
perror("execlp");
exit(0);
}
else{
perror("fork");
exit(0);
}
return 0;
}
输出的内容有限,因为管道的缓冲区的大小有限,写满了之后就无法继续写入。