Linux:IPC之管道

文章目录

      • 管道
        • 匿名管道
          • 模拟实现以下操作:ls | grep make
        • 命名管道
          • 模拟实现传输一串字符到另一进程中
      • 管道读写特性总结

在这里,我们第一次提到了IPC(Inter-process communication)即 进程间通信,我们变来简单说说IPC以及它的使用意义:

  • 在操作系统中,进程间通信主要是为了应对以下场景而产生的:
    • 数据传输:一个进程需要将他的数据发送给另一个进程
    • 数据共享:一份数据有可能是被多个进程所需要的,当该数据发生变化时,这些进程需要被通知
    • 控制进程:有的进程需要完全控制另一个进程来完成任务,比如我们之前实现的minishell,本质就是控制他的子进程来完成命令,此时需要收集被控进程的一些信息,比如状态改变,错误退出码等
    • 事件通知:一个进程需要向另一方或多方进程通知某事件的发生,比如子进程退出时需要通知父进程,否则会导致僵尸进程。
  • 但由于进程间高度的独立性,他们之间无法直接沟通,需要操作系统提供一块公共的媒介实现通信,就好比抗日剧里的地下工作者们相互之间无法直接碰头时,往往在一个约定好的地方留下情报,其他人就可以从这里获取信息,根据通信场景的不同,操作系统也提供了不同的进程间通信方式,今天,我们组要介绍管道以及一些简单的实例;

管道

  • 就好像我们生活中所处可见的管道一样,Linux里的管道主要用于传输数据资源,本质上就是内核中的一块内存(构成了一个阻塞队列),使用一堆文件描述符来进行访问这个内存,读文件描述符就是在从队列中取数据;写文件就是往队列中插入数据且是半双工通信(双向选择,单向通信),就好比家里的污水管道,可能会连接不同的污水处理厂,但不论怎样,也只会让污水流向处理厂,而不会把处理场的污水流回家里;
  • 如何实现:进程间实现通信,在内核中创建管道,操作系统返回两个描述符(双向选择–读端/写端)作为管道的操作句柄,对管道操作就是基础IO操作

匿名管道

  • 因为无法被其他进程拿到,仅能用于具有亲缘关系的进程间通信,通过复制父进程获取管道的操作句柄
  • int pipe(int pipefd[2]);
    • pipefd:用于获取管道的操作句柄
    • pipefd[0]:管道读取端
    • pipefd[1]:管道写入端
  • 特性,
    • 管道中若没有数据,则read会阻塞,若管道中数据满了,write会阻塞;
    • 若所有写段被关闭,则read读完数据后返回0(而不是阻塞);
      • (read返回0,表示管道没人写了(写端全部被关闭),没必要继续读);
    • 若管道所有读端被关闭,则write写数据会触发异常(导致进程退出);
    • 因为管道的读写特性,没有用到哪一端就关掉哪一端;
模拟实现以下操作:ls | grep make
  • 首先我们先创建一个pipefd数组用于存放管道读写句柄,然后想实现minishell一样,创建两个子进程,分别实现ls和grep,在ls子进程里,使用重定向将原本输出到标准输出文件的内容通过写操作句柄写入到管道缓冲区,然后在进行程序替换到ls程序,在grep子进程里,将读操作句柄替换标准输入(grep默认从这里获取数据),然后进行程序替换到grep,这里注意:当进程只操作读句柄,关闭写句柄,只操作中写句柄,关闭读句柄,如果都不操作如本例中的父进程,则要将读写句柄都关闭,因为子进程会继承父进程的读写操作句柄,这意味着亲缘进程们都在操作着同一块管道缓冲区,它们都默认打开了该缓冲区读写操作端,当写端打开了却不写入数据,读端就会一直阻塞,当有读端未关闭时,写端会调用阻塞,直到缓冲区内数据被读走才继续写入
    Linux:IPC之管道_第1张图片
    参考代码:
void Pipe(){
  int pipefd[2];
  if(pipe(pipefd)<0){
    printf("pipe error");
    return;
  }
  int pid1 = fork();
  if(pid1<0){
    printf("fork error");
    exit(-1);
  }else if(pid1==0){
    close(pipefd[0]);
    dup2(pipefd[1],1);
    execlp("ls","ls",NULL);
    exit(-1);
  }
  int pid2 = fork();
  if(pid2<0){
    printf("fork error");
    exit(-1);
  }else if(pid2==0){
    close(pipefd[1]);
    dup2(pipefd[0],0);
    execlp("grep","grep","a",NULL);
    return ;
  }
  close(pipefd[0]);
  close(pipefd[1]);
  wait(NULL);//等待子进程ls退出防止制造僵尸
  wait(NULL);//等待子进程grep退出防止制造僵尸
  return;
}

命名管道

  • 管道有名字—名字体现在文件系统中具有一个管道文件
    • 管道文件—就是命名管道的名字—给任意进程提供通过打开同一个命名管道进而访问到内核中同一块管道缓冲区的功能
    • mkfifo—创建管道类型文件
      • int mkfifo(const char*pathname,mode_t mode)
        • pathname: 管道文件的路径名
        • mode: 管道文件的操作权限
  • 特性
    • 若管道文件没有以写方式打开,只以只读打开会阻塞
    • 若管道文件没有以读方式打开,只以只写打开会阻塞
    • 命名管道读写特性雷同于匿名管道特性
模拟实现传输一串字符到另一进程中
  • 若要实现这个功能,我们就必须要用到命名管道了,两个毫无关系的进程可以通过操作制定的命名管道文件来进行数据传输,第一步创建命名管道,这里注意当mkfifo检测到已存在该管道文件时,会返回一个EEXIST的异常码,遇到此种情况,需要处理成默认继续,然后使用open获取管道文件的操作句柄,再使用write/read打开读端和写端即可,当一端未打开时,另一端会阻塞,就好比水管子没水或者有水没出口,最终都是什么也不会发生;需要注意:因为需要随时获取字符串,所以这里使用的是while(1)始终获取字符串,当所有写段被关闭时,读端必能不会阻塞,会返回零,然后由于循环继续读,这里检测到返回0跳出即可:
    参考代码:
    写入部分
 char* path = "../test.fifo";
  umask(0);
  int ret = mkfifo(path,0660);
  if(errno!=EEXIST&&ret!=0){
    perror("mkfifo error");//搞清楚为啥这么秀
    exit(-1);
  }
  printf("prepare to openning file!\n");
  int handle = open(path,O_WRONLY|O_TRUNC);
  if(handle<0){
    perror("open error");
    return -1;
  }
  printf("open file sucess!\n");
  int count = 10;
  char* num[] = {"一号第10次写入","一号第9次写入","一号第8次写入","一号第7次写入","一号第6次写入","一号第5次写入","一号第4次写入","一号第3次写入","一号第2次写入","一号第1次写入"};
  while(count--)
  {
    sleep(3);
    char test[100] = "这是一个通信测试文本~~";
    strcat(test,num[count]);
    write(handle,test,strlen(test));
  }
  close(handle);
  return 0;

读取部分

 char *path = "../test.fifo";
  umask(0);
  int ret = mkfifo(path,0777);
  if(ret<0&& errno!=EEXIST){
    perror("mkfifo error");
    return ;
  }
  printf("prepare open file fifo\n");
  int handle = open(path,O_RDONLY);
  if(handle<0){
    perror("open error");
    return ;
  }
  printf("open fifo sucess\n");
  int count =1;
  while(1){
    sleep(2);
    char buf[1024] = {0};
    int check = read(handle,buf,1023);
    if(!check)
      break;
    printf("buf:[%d][%d]->%s\n",count,check,buf);
    count++;
  }
  close(handle);
  return;

当两个进程同时运行时,便会得到结果啦!
Linux:IPC之管道_第2张图片

管道读写特性总结

  • 管道自带同步与互斥(当管道读写大小小于PIPE_BUF-4096时,保证操作原子性)
    • 原子性操作:操做不可被打断
    • 同步:对邻接资源访问的时序可控性—(时序控制–我操作完了别人才能操作)
    • 互斥:对临界资源的同一时间唯一访问性保护–(我操作的时候别人不能操作)
  • 管道提供字节流服务–传输方式灵活–造成数据粘连(本质原因:数据之间没有边界)
  • 管道生命周期随进程
  • 匿名管道仅能够用于具有亲缘关系的进程间通信
  • 命名管道能用于任意进程间通信
    yo~
    yo~
    如果觉得不错~
    请别忘了点个赞~
    Bro你的鼓励~
    给我坚持的勇气~
    Peace out~

你可能感兴趣的:(Linux)