【Linux】--進程間通信(一) 管道

引言:進程相互之間獨立存在,每個進程都有自己的虛擬地址空間,兩個進程之間互不瞭解彼此虛擬地址中的數據內容,此外,兩個進程間通信需要藉助某些方式,進而可以訪問到彼此公共資源,接下來,我們一起來探索一下進程間通信的奧祕!

一、進程間通信的目的

  • 1.數據傳輸:一個進程需要將它的數據發送給另一個進程
  • 2.資源共享:多個進程之間共享同樣的資源。
  • 3.通知事件:一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。
  • 4.進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,並能夠及時知道它的狀態改變

二、進程間通信的分類

在這裏插入圖片描述
管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息隊列
  • System V 共享內存
  • System V 信號量

POSIX IPC

  • 消息隊列
  • 共享內存
  • 信號量
  • 互斥量
  • 條件變量
  • 讀寫鎖

三、管道

1.什麼是管道

  • 管道是Unix中最古老的進程間通信的形式。
  • 我們把從一個進程連接到另一個進程的一個數據流稱爲一個“管道”
    在這裏插入圖片描述

2.匿名管道

#include <unistd.h>
功能:創建一無名管道
原型
int pipe(int fd[2]);
參數
fd:文件描述符數組,其中fd[0]表示讀端, fd[1]表示寫端
返回值:成功返回0,失敗返回錯誤代碼

在這裏插入圖片描述

實例化代碼如下所示
//例子:從鍵盤讀取數據,寫入管道,讀取管道,寫到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
int fds[2];
char buf[100];
int len;
if ( pipe(fds) == -1 )
perror("make pipe"),exit(1);
// read from stdin
while ( fgets(buf, 100, stdin) ) {
len = strlen(buf);
// write into pipe
if ( write(fds[1], buf, len) != len ) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// read from pipe
if ( (len=read(fds[0], buf, 100)) == -1 ) {
perror("read from pipe");
break;
}
// write to stdout
if ( write(1, buf, len) != len ) {
perror("write to stdout");
break;
    }
  }
}
用fork來共享管道原理

在這裏插入圖片描述

3.管道讀寫規則

  • 當沒有數據可讀時

    • O_NONBLOCK disable:read調用阻塞,即進程暫停執行,一直等到有數據來到爲止。
    • O_NONBLOCK enable:read調用返回-1,errno值爲EAGAIN。
  • 當管道滿的時候

    • O_NONBLOCK disable: write調用阻塞,直到有進程讀走數據
    • O_NONBLOCK enable:調用返回-1,errno值爲EAGAIN
  • 如果所有管道寫端對應的文件描述符被關閉,則read返回0

  • 如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE,進而可能導致write進程退出

  • 當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。

  • 當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。

4.管道的特點(重點)

  1. 只能用於具有共同祖先的進程(具有親緣關係的進程)之間進行通信;通常,一個管道由一個進程創建,然後該進程調用fork,此後父、子進程之間就可應用該管道。
  2. 管道提供流式服務(可能會發生數據粘連)。
  3. 一般而言,進程退出,管道釋放,所以管道的生命週期隨進程
  4. 一般而言,內核會對管道操作進行同步與互斥
  5. 管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道
  6. 同步:通過一些條件的判斷,來實現對臨界條件資源訪問的時序合理性
  7. 互斥:同一時間只有一個執行流能夠操作臨界資源,實現對數據的安全操作。
    在這裏插入圖片描述

5.命名管道

  • 管道應用的一個限制就是隻能在具有共同祖先(具有親緣關係)的進程間通信。
  • 如果我們想在不相關的進程之間交換數據,可以使用FIFO文件來做這項工作,它經常被稱爲命名管道。
  • 命名管道是一種特殊類型的文件

6.創建命名管道

命名管道可以從命令行上創建,命令行方法是使用下面這個命令:

$ mkfifo filename

命名管道也可以從程序裏創建,相關函數有:

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

創建命名管道:

int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}

7.匿名管道與命名管道的區別

對 上面兩個管道的相同點

  • 1、依賴文件系統,生命週期隨進程;
  • 2、都是單向通信的;
  • 3、按照數據流的方式通信;
  • 4、讀寫之間都是按照同步機制來訪問的。

不同點

  • 1、管道只能應用於有血緣關係的進程,命名管道可應用於所有的進程
  • 2、管道依賴於文件描述符表,不需要明確的創建文件,命名管道需要明確的創建fifo文件存於文件系統中。

8.命名管道的打開規則

  • 如果當前打開操作是爲讀而打開FIFO時
    • O_NONBLOCK disable:阻塞直到有相應進程爲寫而打開該FIFO
    • O_NONBLOCK enable:立刻返回成功
  • 如果當前打開操作是爲寫而打開FIFO時
    • O_NONBLOCK disable:阻塞直到有相應進程爲讀而打開該FIFO
    • O_NONBLOCK enable:立刻返回失敗,錯誤碼爲ENXIO

9. 舉兩個命名管道的栗子

  • 例子1-用命名管道實現文件拷貝

讀取文件,寫入命名管道:

//寫端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
int main()
{
char *file="./tp";
int ret=mkfifo(file,0664);//創建管道
if(ret<0){
if(errno!=EEXIST){
perror("mkfifo error");
return -1;
}
}
int fd=open(file,O_WRONLY);//將文件以只寫方式打開
if(fd<0){
perror("open error");
}
printf("open success\n");

while(1){
char buf[1024]={0};
scanf("%s",buf);
ret=write(fd,buf,strlen(buf));//向fd所引用的文件中寫入buf的內容
if(ret<0){
perror("write error");
return -1;
}else if(ret==0){
printf("沒人讀就關閉");
return 0;
}
}
return 0;
}

讀取管道,寫入目標文件:

//讀端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
int main()
{
char *file="./tp";
int ret=mkfifo(file,0664);
if(ret<0){
if(errno!=EEXIST){
perror("mkfifo error");
return -1;
}
}
int fd=open(file,O_RDONLY);
if(fd<0){
perror("open error");
}
printf("open success\n");
while(1){
char buf[1024]={0};
ret=read(fd,buf,1023);//從fd中讀取數據放到buf中
if(ret<0){
perror("read error");
return -1;
}else if(ret==0){
printf("沒人寫就關閉\n");
return 0;
}
printf("buf:[%s]\n",buf);
}
return 0;
}

  • 例子2-用命名管道實現server&client通信
# ll
total 12
-rw-r--r--. 1 root root 46 Sep 18 22:37 clientPipe.c
-rw-r--r--. 1 root root 164 Sep 18 22:37 Makefile
-rw-r--r--. 1 root root 46 Sep 18 22:38 serverPipe.c
# cat Makefile
.PHONY:all
all:clientPipe serverPipe
clientPipe:clientPipe.c
gcc -o $@ $^
serverPipe:serverPipe.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f clientPipe serverPipe

serverPipe.c

 1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<fcntl.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #define FIFO "fifo"
  7 
  8 int main()
  9 {
 10     if(mkfifo(FIFO, 0644)<0)
 11     {
 12         perror("mkfifo error");
 13         return 1;
 14     }
 15 
 16     int fd = open(FIFO, O_RDONLY);
 17     if(fd < 0)
 18     {
 19         perror("open error");
 20         return 2;
 21     }
 22 
 23     char buf[1024];
 24     while(1)
 25     {
 26         ssize_t s = read(fd, buf, sizeof(buf)-1);
 27         if(s > 0)
 28         {
 29             buf[s] = 0;
 30             printf("proc_two: %s\n", buf);
 31         }
 32         else if(s==0)
 33         {
 34             printf("proc_two quit, me too...");
 35             break;
 36         }
 37         else
 38         {
 39             break;
 40         }
 41     }
 42     close(fd);
 43     return 0;
 44 }

clientPipe.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 
  7 #define FIFO "fifo"
  8 int main()
  9 {
 10     int fd = open(FIFO, O_WRONLY);
 11     if(fd < 0)
 12     {
 13         perror("open error");
 14         return 1;
 15     }
 16 
 17     char buf[1024];
 18     while(1)
 19     {
 20         printf("eoch# ");
 21         fflush(stdout);
 22         ssize_t s = read(0, buf, sizeof(buf)-1);
 23         if(s>0)
 24         {
 25             buf[s] = 0;
 26             write(fd, buf, strlen(buf));
 27         }
 28     }
 29     close(fd);
 30     return 0;
 31 }

運行結果如下所示:
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章