linux 進程通訊之 管道和FIFO

進程間通訊:IPC概念html

IPC:Interprocess Communication,經過內核提供的緩衝區進行數據交換的機制。c++

IPC通訊的方式:shell

通訊種類:bash

  • 單工(廣播)
  • 單雙工(對講機)
  • 全雙工(電話)

一,管道PIPE

pipe通訊是單雙工的。微信

pipe通訊,只能在有血緣關係的進程間通訊。父子進程,兄弟進程,爺孫進程等。

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

例子:socket

#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\n", 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\n", 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\n", 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\n", 6);
    //sleep(1);
  }
}

例子2:用管道實現【ps aux | grep bash】命令。學習

實現辦法,用dup2函數把標準輸出,重定向到寫端;再把標準輸入重定向到讀端。code

#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才能結束。htm

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\n");
    }

  }
  if(pid > 0){
    int ret = write(fds[1], "hello\n", 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\n", 6); 
  }
  if(pid > 0){
    close(fds[0]);//----①
    //close(fds[1]);
    int status;
    wait(&status);
    if(WIFSIGNALED(status)){
      printf("killed by %d\n", 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\n", 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\n");
      int fd = open(argv[1], O_WRONLY);
      printf("end write\n");
    
      int num = 0;
      char buf[20] = {0};
      while(1){
        sprintf(buf, "num=%04d\n", 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\n");
      int fd = open(argv[1], O_RDONLY);
      printf("end read\n");
    
      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\n", 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\n", 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\n", 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

相關文章
相關標籤/搜索