Linux:进程间通信之管道

进程间通信----管道篇

一、进程间通信

进程间通信本质上就是让不同的进程看到一份共同的资源。
进程间通信的目的:

  • 数据传输:⼀个进程需要将它的数据发送给另⼀个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发⽣了某种事件(如进程终⽌时要通知⽗进程)。
  • 进程控制:有些进程希望完全控制另⼀个进程的执⾏(如Debug进程),此时控制进程希望能够拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变

二、管道

1.管道是什么?

管道是链接一个进程连接到另一个进程的数据流

2.管道的两种类型?

  • 匿名管道pipe:只能用于具有血缘关系的进程,常用于父子进程之间
  • 命名管道:用于两个毫无关系的进程之间的通信

三、匿名管道pipe

1.匿名管道的基本原理(fork)

匿名管道实际上是由父进程创建出管道,然后fork出子进程,然后父子进程关闭对应的读写端,也就是关闭各自不用的描述符。
如下图所示:
父进程创建好管道之后fork出子进程,然后父子进程各自都有自己的读端fd[0]和写端fd[1]。
在这里插入图片描述
此时我们如果想让子进程写,父进程读,那么我们关闭掉子进程的读端和父进程的写端即可,下图就是关闭不用的描述符之后的结果。
子进程往管道里写入,父进程从管道里读出,此时就完成了父子进程的通信。
在这里插入图片描述
但是我们通过图片可以看出,由于关闭了子进程的读端和父进程的写端,所以子进程只能往管道里写入,父进程只能从管道里读出,所以在这里我们得到了一条匿名管道的特点:只允许单向通信

2.在文件描述符的角度上深度理解匿名管道 ※

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0标准输出1标准错误2,0、1、2对应的设备分别为键盘显示器显示器

首先第一步,父进程调用pipe函数创建管道,fd[0]代表读端,fd[1]代表写端。

这里教给大家我记忆的小技巧:fd[0]是读端,可以把0想成是一个嘴巴,张大嘴巴读。fd[1]是写端,可以把1想成是一支笔在写数据。

在这里插入图片描述

第二步是父进程通过fork创建出子进程,此时父进程与子进程共享代码,fork之后数据各自私有一份。但是fork之前的代码数据都是相同的,所以此时子进程继承了父进程的文件描述符表,因此父进程打开了什么文件,子进程也同样打开了什么文件,同时父子进程的文件描述符表中也都具备了fd[0](读端)和fd[1](写端),也保证了父子进程看到了一份公共的资源(管道)。
在这里插入图片描述
第三步就是根据自己的需要完成关闭相应的描述符即可。假如此时我们让父进程写,子进程读,那么我们关闭父进程的fd[0],关闭子进程的fd[1]。这样就完成了父子进程之间的通信了。
在这里插入图片描述
所以我们不难发现,匿名管道只能完成父子进程之间的通信,因此我们得到匿名管道的另一条特点:只能用于具有血缘关系的进程,常用于父子进程之间。

3.匿名管道的特点 ※

① 只允许单向通信(如果想实现双向通信,可以利用两个管道)
② 只能用于具有血缘关系的进程,常用于父子进程之间
③ 是面向字节流的,提供流式服务
④ 一般来说,进程退出,管道释放,所以管道的生命周期随通信双方的进程(进程打开一些文件时,进程退出,文件也会关闭,所以文件的生命周期随进程,那管道也是文件,因此管道的生命周期也是随进程的)
⑤ 管道自带同步和互斥机制

深入理解匿名管道特点:

此时我们需要引入五个概念:
临界资源:多个进程看到的一份公共资源
临界区:在你写的代码中,访问临界资源的那一部分代码区域叫临界区
互斥:任何时刻只允许有一个进程进入临界资源进行资源访问,并且在其访问期间其他进程无法进入
同步:如果一个进程一直在申请锁释放锁,那么会导致其他想要访问临界资源的进程的饥饿问题,所以当某个进程想要第二次申请锁时,就需要在这些进程后面排队了,那么在保证安全的前提下,进程按照特定的顺序访问临界资源叫同步
原子性:一件事情要么做了,要么没做,不会有第三种形态

接下来看四种状态 ※:

扫描二维码关注公众号,回复: 5378905 查看本文章
  • 假如现在父子进程进行通信,父进程读,子进程写,但是父进程的读端不读,但是也不关,那么子进程的写端就会一直写,那么子进程一直写一直写,最后就会把管道写满,那么基于安全考虑操作系统就不让写端写了,这个时候操作系统就不会调度子进程,它会把子进程的R状态变为非R状态,然后它就会把子进程的PCB放置特定的等待队列中进行等待,给我们的感觉就是子进程被阻塞了,所以当读端不读并且不关的时候,写端写满的时候,写端进程就会阻塞式等待。
  • 假如现在写端不写,也不关,那么读端如果检测到管道里还有数据,它就会读,读完的时候,因为写端没有关,所以写端随时有可能来写,所以说读端就会等写端进行数据的写入,也就是读端会阻塞式等待。
    因此我们发现当管道有数据我读端就读,没数据我读端就不读,当管道有空间我写端就写,没空间我写端就不写,这就是让读写端以某种特定的顺序进行读写,所以我们发现管道自带同步机制。
  • 假如现在我们把写端关闭了,那么如果管道里有数据,读端就会读出来,那么读完之后呢?写端已经关闭了,那么已经不会再有数据写入了,读端等就没有任何意义了,我们知道操作系统绝对不会做浪费资源的事情,所以此时操作系统就会把读端返回。
  • 假如现在我们把读端关闭了,那么写端写就没有任何意义了,操作系统绝对不会做浪费资源的事情,所以操作系统就会给写端发送13号SIGPIPE信号然后立即kill掉写端。
    因此说管道是自带同步和互斥机制的。

4.匿名管道的实例代码

在这里插入图片描述
输出为
在这里插入图片描述

四、命名管道

命名管道可以用于毫无关系的两个进程之间的通信。我们可以通过mkfifo系统调用函数来创建命名管道,创建出来的是一个管道文件。让一个进程往管道文件里写入数据,另一个进程从该管道文件里读取数据,即可完成两个毫无关系的进程之间的通信。

学习一下mkfifo系统调用函数的使用

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

示例代码

server.c(读端)
在这里插入图片描述
client.c(写端)
在这里插入图片描述
当我们运行server之后,打开新的终端,用ll命令查看,发现已经创建了管道文件fifo,此时再运行client,此时就可以通过client进程将数据写入fifo管道文件里,同时server进程就会读取到fifo管道里的数据并打印出来,至此就完成了server和client两个进程之间的通信。
在这里插入图片描述

五、匿名管道和命名管道的区别

1.创建和打开方式不同:
匿名管道由pipe函数创建并打开,fork出子程序
命名管道由mkfifo创建,由open打开
2.适用于对象不同:
匿名管道用于具有亲缘关系的进程,常用于父子进程之间
命名管道可以用于两个毫无关系的进程之间

附上本文涉及到的代码:
匿名管道代码

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 int main()
  5 {
  6     int fd[2];
  7     pipe(fd);
  8     pid_t id=fork();
  9     if(id==0)
 10     {
 11         //child > write
 12         close(fd[0]);
 13         char *msg="I am a child!";
 14         int i=5;
 15         while(i--)
 16         {
 17             write(fd[1],msg,strlen(msg));
 18             sleep(1);
 19         }
 20     }
 21     else
 22     {
 23         //father > read
 24         close(fd[1]);
 25         char buf[1024];
 26         while(1)
 27         {
 28             size_t s=read(fd[0],buf,sizeof(buf));
 29             if(s>0)
 30             {
 31                 buf[s]=0;
 32                 printf("father get a massage :");
 33                 printf("%s\n",buf);
 34             }
 35         }
 36     }
 37     return 0;
 38 }

server.c

  1 #include <stdio.h>
  2 #include <sys/stat.h>
  3 #include <sys/types.h>
  4 #include <fcntl.h>
  5 #include <string.h>
  6 int main()
  7 {
  8     size_t fd=open("./fifo",O_WRONLY);
  9     if(fd<0)
 10     {
 11         printf("open error!\n");
 12         return 2;
 13     }
 14     char buf[1024];
 15     while(1)
 16     {
 17         printf("Please Enter:");
 18         scanf("%s",buf);
 19         write(fd,buf,strlen(buf));
 20     }
 21 
 22     close(fd);
 23     return 0;
 24 }

client.c

  1 #include <stdio.h>
  2 #include <sys/stat.h>
  3 #include <sys/types.h>
  4 #include <fcntl.h>
  5 #include <string.h>
  6 int main()
  7 {
  8     size_t fd=open("./fifo",O_WRONLY);
  9     if(fd<0)
 10     {
 11         printf("open error!\n");
 12         return 2;
 13     }
 14     char buf[1024];
 15     while(1)
 16     {
 17         printf("Please Enter:");
 18         scanf("%s",buf);
 19         write(fd,buf,strlen(buf));
 20     }
 21 
 22     close(fd);
 23     return 0;
 24 }

猜你喜欢

转载自blog.csdn.net/ETalien_/article/details/86097589