zoukankan      html  css  js  c++  java
  • linux 进程通信之 管道和FIFO

    进程间通信:IPC概念

    IPC:Interprocess Communication,通过内核提供的缓冲区进行数据交换的机制。

    IPC通信的方式:

    通信种类:

    • 单工(广播)
    • 单双工(对讲机)
    • 全双工(电话)

    一,管道PIPE

    pipe通信是单双工的。

    pipe通信,只能在有血缘关系的进程间通信。父子进程,兄弟进程,爷孙进程等。

    #include <unistd.h>
    int pipe(int pipefd[2]);
    
    
    • pipefd:【0】是读端,【1】是写端。
    • 返回值:成功返回0;失败返回-1。

    例子:

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      pid_t pid = fork();
    
      if(pid == 0){
        write(fds[1], "hello
    ", 6);
        char buf[10] = {0};
        int ret = read(fds[0], buf, sizeof buf);
        if(ret > 0){
          printf("%s", buf);
        }
      }
      if(pid > 0){
        char buf[10] = {0};
        int ret = read(fds[0], buf, sizeof buf);
        if(ret > 0){
          printf("%s", buf);
        }
        write(fds[1], "world
    ", 6);
        sleep(1);
      }
    }
    
    

    例子1:子进程写,父进程读。

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      pid_t pid = fork();
    
      if(pid == 0){
        write(fds[1], "hello
    ", 6);
        /*
        char buf[10] = {0};
        int ret = read(fds[0], buf, sizeof buf);
        if(ret > 0){
          printf("%s", buf);
        }
        */
      }
      if(pid > 0){
        char buf[10] = {0};
        int ret = read(fds[0], buf, sizeof buf);
        if(ret > 0){
          printf("%s", buf);
        }
        //write(fds[1], "world
    ", 6);
        //sleep(1);
      }
    }
    

    例子2:用管道实现【ps aux | grep bash】命令。

    实现办法,用dup2函数把标准输出,重定向到写端;再把标准输入重定向到读端。

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      pid_t pid = fork();
      int stdoutfd = dup(STDOUT_FILENO);
      if(pid == 0){
        //close(fds[0]);//------------①
        dup2(fds[1], STDOUT_FILENO);
        execlp("ps", "ps", "aux", NULL);
      }
      if(pid > 0){
        //close(fds[1]);//----------②
        dup2(fds[0], STDIN_FILENO);
        execlp("grep", "grep", "bash", NULL);
        dup2(stdoutfd, STDOUT_FILENO);
      }
    }
    
    

    运行结果:发现程序没有结束,阻塞住了,必须按ctol-c才能结束。

    ys@ys:~/test$ ./pi2 
    ys        1551  0.0  0.2  29692  5548 pts/0    Ss   10:05   0:00 bash
    ys        2316  0.0  0.2  29560  5328 pts/1    Ss+  11:33   0:00 bash
    ys        2486  0.0  0.0  21536  1060 pts/0    S+   11:56   0:00 grep bash
    
    
    

    用【ps aux】调查一下,发现,由于父进程【grep bash】没有结束还没有回收子进程,导致【ps】变成了僵尸进程。

    ys        2437  0.0  0.0  21536  1088 pts/0    S+   11:50   0:00 grep bash
    ys        2438  0.1  0.0      0     0 pts/0    Z+   11:50   0:00 [ps] <defunct>
    ys        2439  0.0  0.1  44472  3800 pts/1    R+   11:50   0:00 ps aux
    
    

    为什么父进程【grep bash】没有结束呢?确实在子进程里给父进程【ps aux】的输出结果了啊!

    这是grep命令本身的缘故,在终端执行【grep bash】的话,就变成了阻塞状态,grep在等待标准输入,如果输入了【bash】grep就会给出结果,但是还是在继续等待标准输入,所以这就是父进程没有结束,阻塞在【grep bash】那里的原因。

    解决办法:告诉【grep】,管道的写端不会再写入数据了后,grep就不会再继续等待,所以grep就会结束。grep的结束了,父进程也就结束了,所以僵尸进程也就自动消失了。

    需要改代码的地方是②处,加上【close(fds[1]);】,就告诉了grep,已经没有写入了,所以grep就不会阻塞,父进程就能够结束掉。

    注意:其实应该在子进程里也应该加上【close(fds[1]);】,才能达到写端全部关闭了,为什么没写也没错误呢,因为子进程先执行结束了,进程结束后,系统会自动把进程中打开的文件描述符全部关闭,所以没在子进程里写关闭写端的代码,也没出问题。

    管道有如下的规则:

    • 读管道时:
      • 写端全部关闭:read函数返回0,相当于没有再能读取到的了。
      • 写端未全部关闭:
        • 管道里有数据:read函数能够读到数据。
        • 管道里没有数据:read 阻塞。(可以用fcnlt设置成非阻塞)
    • 写管道时:
      • 读端全部关闭:write函数会产生SIGPIPE信号,程序异常结束。
      • 读端未全部关闭:
        • 管道已满:write函数阻塞等待。
        • 管道未满:write函数正常写入。

    例子1:写端全部关闭:read函数返回0。

    在①和②两处必须都关闭写端,read函数才能返回0.

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <sys/wait.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      pid_t pid = fork();
    
      if(pid == 0){
        char buf[10] = {0};
        int ret = read(fds[0], buf, sizeof buf);
        if(ret > 0){
          printf("%s", buf);
        }
        close(fds[1]);//----①
        sleep(1);
        if(read(fds[0],buf, sizeof buf) == 0){
          printf("all closed
    ");
        }
    
      }
      if(pid > 0){
        int ret = write(fds[1], "hello
    ", 6);
        close(fds[1]);//------②
        wait(NULL);    
      }
    }
    

    例子2:读端全部关闭:write函数会产生SIGPIPE信号,程序异常结束。

    在①和②两处必须都关闭读端,write函数会产生SIGPIPE信号。

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <sys/wait.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      pid_t pid = fork();
    
      if(pid == 0){
        close(fds[0]);//------②
        int ret = write(fds[1], "hello
    ", 6); 
      }
      if(pid > 0){
        close(fds[0]);//----①
        //close(fds[1]);
        int status;
        wait(&status);
        if(WIFSIGNALED(status)){
          printf("killed by %d
    ", WTERMSIG(status));
        }
      }
    }
    
    

    执行结果:【killed by 13】。13是SIGPIPE

    查看系统默认的管道缓冲区的大小:ulimit -a

    pipe size            (512 bytes, -p) 8
    

    查看系统默认的管道缓冲区的大小的函数:fpathconf

    #include <unistd.h>
    long fpathconf(int fd, int name);
    
    
    • fd:文件描述符
    • name:可以选择很多宏
      • _PC_PIPE_BUF:代表管道。

    例子:

    #include <unistd.h>
    #include <stdio.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      long ret = fpathconf(fds[0], _PC_PIPE_BUF);
      printf("size:%ld
    ", ret);
    }
    
    

    执行结果:size:4096

    上面的【例子:用管道实现【ps aux | grep bash】命令】有个问题,父进程直接调用了exec函数,导致无法在父进程中回收子进程的资源。下面的例子就去解决这个问题,方法是,不在父进程里调用exec函数,在2个兄弟子进程里分别调用exec函数,然后在父进程里回收资源。

    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int main(){
      int fds[2];
      pipe(fds);
      pid_t pid = fork();
      if(pid == 0){
        pid_t pid1 = fork();
        if(pid1 == 0){
          dup2(fds[1], STDOUT_FILENO);
          execlp("ps", "ps", "aux", NULL);
          
        }
        else if(pid1 > 0){
          close(fds[1]);//----①
          dup2(fds[0], STDIN_FILENO);
          execlp("grep", "grep", "bash", NULL);
          //dup2(stdoutfd, STDOUT_FILENO);
        }
      }
      else if(pid > 0){
        close(fds[1]);//----②
        wait(NULL);
      }
    }
    

    注意在①和②处的关闭代码。

    到此为止,可以看出来管道的

    • 优点:使用起来简单。
    • 缺点:只能在有血缘关系的进程间使用。

    二,FIFO通信

    创建FIFO伪文件的命令:【mkfifo】

    prw-r--r-- 1 ys ys     0 4月  29 15:59 myfifo
    

    文件类型为P,大小为0。

    也可以用函数:mkfifo创建

    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
    
    • pathname:文件名
    • mode:文件权限
    • 返回值:0成功;-1失败

    FIFO通信原理:内核对fifo文件开辟一个缓冲区,操作fifo伪文件,就相当于操作缓冲区,实现里进程间的通信。实际上就是文件读写。

    FIFO例子:传进一个事先用mkfifo 创建好的FIFO文件。可以同时打开多个读端和写端。

    • 写端:

      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      #include <stdio.h>
      #include <unistd.h>
      
      int main(int argc, char* argv[]){
      
        printf("begin write
      ");
        int fd = open(argv[1], O_WRONLY);
        printf("end write
      ");
      
        int num = 0;
        char buf[20] = {0};
        while(1){
          sprintf(buf, "num=%04d
      ", num++);
          write(fd, buf, strlen(buf));
          sleep(1);
        }
      
        close(fd);
      }
      
      
    • 读端:

      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      #include <stdio.h>
      #include <unistd.h>
      #include <string.h>
      
      int main(int argc, char* argv[]){
      
        printf("begin read
      ");
        int fd = open(argv[1], O_RDONLY);
        printf("end read
      ");
      
        int num = 0;
        char buf[20] = {0};
        while(1){
          memset(buf, 0x00, sizeof buf);
          int ret = read(fd, buf, sizeof buf);
          if(ret > 0){
            printf("%s
      ", buf);
          }
          else if(ret == 0){
            break;
          }
          sleep(1);
        }
      
        close(fd);
      }
      

    例子里有两个注意点:

    • open的时候是阻塞的,只有当读端和写端都打开后,open函数才会返回。非FIFO文件的open函数不是阻塞的。

      FIFOs
             Opening  the  read or write end of a FIFO blocks until the other end is
             also opened (by another process or thread).  See  fifo(7)  for  further
             details.
      
    • 强制终止读端进程后,写端会自动终止。理由是读端已经关闭了,再往里写就会收到SIGFIFO信号,这个和管道的原理是一样的。

    • 非常重要的一点:从fifo里读出数据后,这个被读出来的数据在fifo里就消失了。后面讲的mmap进程间通信就不一样,读完了,再读还有,因为是映射到内存了。

    A进程发送一个mp3文件,B进程接收这个mp3文件,并存储到磁盘上,代码如下:
    发送端:先取得mp3文件的大小,把文件的大小先发给接收端,然后在把文件的内容发过去。

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    
    int main(int argc, char* argv[]){
      struct stat sbuf;
      int ret = stat("02.mp3", &sbuf);
      if(ret < 0){
        perror("stat");
        return -1;
      }
      //get file size
      int sz = sbuf.st_size;
      printf("size:%d
    ", sz);
      char buf[20] = {0};
      sprintf(buf, "%d", sz);
      //open fifo file
      int fd = open(argv[1], O_RDWR);
      //send file size
      write(fd, buf, sizeof(buf));
    
      //open src mp3 file
      int src = open("02.mp3", O_RDONLY);
      char srcBuf[1024] = {0};
      //send file content to dec file
      int sent = 0;
      while((sent = read(src, srcBuf, sizeof(srcBuf))) > 0){
        write(fd, srcBuf, sent);
        memset(srcBuf, 0x00, sizeof(srcBuf));
      }
      close(fd);
      close(src);
    }
    

    接收端:先从发送端取得要发过来的MP3文件的大小,然后根据这个大小,先创建一个空的文件,然后再向这个空的文件里写内容。

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char* argv[]){
      //open fifo file
      int fd = open(argv[1], O_RDONLY);
      //send file size
      char buf[20] = {0};
      //get file size
      read(fd, buf, sizeof(buf));
      int sz = atoi(buf);
      printf("sz:%d
    ", sz);
    
      int dsc = open("des.mp3", O_RDWR|O_CREAT|O_TRUNC, 0666);
      int ret = ftruncate(dsc, sz);
      if(ret < 0){
        perror("ftruncate");
        return -1;
      }
    
      char srcBuf[1024] = {0};
      //recv file content from src file
      int sent = 0;
      while((sent = read(fd, srcBuf, sizeof(srcBuf))) > 0){
        write(dsc, srcBuf, sent);
        memset(srcBuf, 0x00, sizeof(srcBuf));
      }
    
      close(fd);
      close(dsc);
    }
    

    c/c++ 学习互助QQ群:877684253

    本人微信:xiaoshitou5854

  • 相关阅读:
    SASS常用方法
    Vue 全局过滤和局部过滤
    Java进阶知识点8:高可扩展架构的利器
    InnoDB MVCC RR隔离级别下的数据可见性总结
    记一次诡异的网络故障排除
    数据库的读锁和写锁在业务上的应用场景总结
    Gradle配置IDEA正常识别JPA Metamodel Generator动态生成的代码
    程序员容易读错的单词
    Java进阶知识点7:不要只会写synchronized
    Java进阶知识点6:并发容器背后的设计理念
  • 原文地址:https://www.cnblogs.com/xiaoshiwang/p/10794806.html
Copyright © 2011-2022 走看看