?IPC ? 進程間通信 ?interprocess communicate
IPC(Inter-Process Communication),即進程間通信,其產生的原因主要可以歸納為以下幾點:
進程空間的獨立性
-
資源隔離:在現代操作系統中,每個進程都擁有自己獨立的代碼和數據空間。這種隔離機制確保了進程之間的安全性和穩定性,但同時也使得進程間無法直接訪問對方的資源。
-
資源無法共享:由于進程空間的獨立性,一個進程無法直接讀取或修改另一個進程的內存空間中的數據。這種情況下,如果需要進行數據交換或共享資源,就需要通過特定的機制來實現。
進程間協作的需求
-
數據交換:在多進程操作系統中,不同的進程可能需要交換數據以完成共同的任務。例如,一個進程可能負責生成數據,而另一個進程則負責處理這些數據。為了實現這種數據交換,就需要使用IPC機制。
-
資源共享:進程間通信還允許進程共享資源,如文件、數據庫、內存等。通過IPC,進程可以協調對共享資源的訪問,避免沖突和競爭條件。
-
任務協調:在分布式系統或并發編程中,多個進程可能需要協同工作以完成復雜的任務。IPC提供了進程間同步和互斥的機制,確保任務能夠按照預定的順序和條件執行。
IPC的必要性
-
提高系統并發性:通過IPC,多個進程可以并行處理不同的任務,從而提高系統的并發處理能力。
-
優化資源利用:IPC允許進程共享資源,減少了資源的重復分配和浪費,提高了資源利用率。
-
實現復雜功能:在構建復雜的軟件系統時,往往需要多個進程協同工作。IPC為這些進程之間的通信和協作提供了必要的支持。
IPC的實現方式
IPC的實現方式多種多樣,包括但不限于以下幾種:
-
管道(Pipe):一種單向通信方式,常用于具有親緣關系的進程間通信。
-
消息隊列(Message Queue):允許多個進程從同一個隊列中讀取數據,具有獨立性和異步性。
-
共享內存(Shared Memory):一段可以被多個進程同時訪問的物理內存區域,提高了進程間的數據交換效率。
-
信號(Signal):一個進程向另一個進程發送信號來傳遞某種信息或通知某個事件的發生。
-
套接字(Socket):用于不同主機之間的進程通信,提供了網絡通信的能
管道==》無名管道、有名管道
?? ?無名管道 ===》pipe ==》只能給有親緣關系進程通信
?? ?有名管道 ===》fifo ==》可以給任意單機進程通信
?? ?管道的特性:
?? ?1、管道是 半雙工的工作模式(收或者發)
?? ?2、所有的管道都是特殊的文件不支持定位操作。不支持lseek->> fd ?fseek ->>FILE*?
?? ?3、管道是特殊文件,讀寫使用文件IO。fgets,fread,fgetc,
? ? ? ?一般情況下使用open,read,write,close;
主要看是否是二進制文件,如果是文本文件使用fges之類的標準io。
注意事項:??
? ?1,讀端存在,一直向管道中去寫,超過64k,寫會阻塞。(寫的快,讀的慢)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");exit(1);}pid_t pid = fork();if(pid>0){char buf[]="hello,world";close(pipefd[0]);sleep(3);write(pipefd[1],buf,strlen(buf));}else if(0 == pid){close(pipefd[1]);char buf[100]={0};read(pipefd[0],buf,sizeof(buf));printf("pipe %s\n",buf);}else {perror("fork");exit(1);}return 0;
}
?? 2,寫端是存在的,讀管道,如果管道為空的話,讀會阻塞(讀的快,寫的慢)
? ? 讀阻塞和寫阻塞都是正常情況。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");exit(1);}pid_t pid = fork();if(pid>0){char buf[1024]={0};close(pipefd[0]);memset(buf,'a',1024);int i = 0 ;for(i=0;i<65;i++){write(pipefd[1],buf,sizeof(buf));printf("i is %d\n",i);}}else if(0 == pid){close(pipefd[1]);char buf[100]={0};
// read(pipefd[0],buf,sizeof(buf));// printf("pipe %s\n",buf);while(1)sleep(1);}else {perror("fork");exit(1);}return 0;
}
??3.管道破裂,讀端關閉,寫管道。
(會導致寫關閉,接收端關閉,導致發送端關閉)類似于段錯誤
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");exit(1);}pid_t pid = fork();if(pid>0){char buf[]="hello,world";close(pipefd[0]);sleep(3);// 管道破裂 gdb 跟蹤write(pipefd[1],buf,strlen(buf));printf("aaaa\n");}else if(0 == pid){close(pipefd[1]);close(pipefd[0]);}else {perror("fork");exit(1);}return 0;
}
?? 4. read 0 ,寫端關閉,如果管道沒有內容,read 0 ;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");exit(1);}pid_t pid = fork();if(pid>0){char buf[]="hello,world";close(pipefd[0]);write(pipefd[1],buf,strlen(buf));write(pipefd[1],buf,strlen(buf));close(pipefd[1]);}else if(0 == pid){close(pipefd[1]);char buf[5]={0};while(1){//memset(buf,0,5);bzero(buf,sizeof(buf));int rd_ret = read(pipefd[0],buf,sizeof(buf)-1);if(rd_ret<=0){break;}printf("pipe %s\n",buf);}}else {perror("fork");exit(1);}return 0;
}
?if(rd_ret<=0)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
如果讀端進程調用read()
函數從管道中讀取數據,并且管道已經為空(即沒有任何待讀取的數據),同時寫端已經被關閉,那么read()
函數將會立即返回,并且返回值為0。這個返回值0是一個特殊的信號,它告訴讀端進程,管道的寫端已經被關閉,并且管道中沒有更多的數據可以讀取了。
使用框架:
?? ?創建管道 ==》讀寫管道 ==》關閉管道
1、無名管道 ===》管道的特例 ===>pipe函數
?? ?特性:
?? ?1.1 ?親緣關系進程使用
?? ?1.2 ?有固定的讀寫端
? ?
流程:
?? ?創建并打開管道: pipe函數
先創建管道再fork();
關閉管道1或者0用,close(xx);
#include <unistd.h>
int pipe(int pipefd[2]);
功能:創建并打開一個無名管道
參數:pipefd[0] ==>無名管道的固定讀端
?? ? ?pipefd[1] ==>無名管道的固定寫端
返回值:成功 0
?? ??? ?失敗 -1;
注意事項:
?? ?1、無名管道的架設應該在fork之前進行。
?? ?
無名管道的讀寫:===》文件IO的讀寫方式。
?? ?讀: read()
?? ?寫: write()
關閉管道: close();
通過管道的方式實現文件的復制:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");exit(1);}pid_t pid = fork();if(pid>0){close(pipefd[0]);int fd = open("/home/linux/1.png",O_RDONLY);if(-1 == fd){perror("open");exit(1);}while(1){char buf[4096]={0};int rd_ret = read(fd,buf,sizeof(buf));if(rd_ret<=0){break;}write(pipefd[1],buf,rd_ret);}close(fd);close(pipefd[1]);}else if(0 == pid){close(pipefd[1]);int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);if(-1==fd){perror("open");exit(1);}while(1){char buf[1024]={0};int rd_ret = read(pipefd[0],buf,sizeof(buf));if(rd_ret<=0){break;}write(fd,buf,rd_ret);}close(fd);close(pipefd[0]);}else {perror("fork");exit(1);}return 0;
}
驗證如下問題:
1、父子進程是否都有fd[0] fd[1],
? ?如果在單一進程中寫fd[1]能否直接從fd[0]中讀到。
? ?可以,寫fd[1]可以從fd[0]讀
2、管道的數據存儲方式是什么樣的
? ?數據是否一直保留?
?? ?棧, 先進后出
? ?隊列形式存儲 讀數據會剪切取走數據不會保留
? ?先進先出
3、管道的數據容量是多少,有沒有上限值。
?? ?操作系統的建議值: 512* 8 = 4k
?? ?代碼測試實際值: ? 65536byte= 64k
4、管道的同步效果如何驗證?讀寫同步驗證。
?? ?讀端關閉能不能寫? 不可以 ===>SIGPIPE 異常終止 (觸發管道破裂)
?? ?寫端關閉能不能讀? 可以,取決于pipe有沒有內容,===>read返回值為0 不阻塞
?? ?結論:讀寫端必須同時存在,才能進行
?? ??? ? ?管道的讀寫。
5、固定的讀寫端是否就不能互換?
?? ?能否寫fd[0] 能否讀fd[1]? ? 不可以,是固定讀寫端。
?? ?
練習:
?? ?如何用管道實現一個雙向通信功能
?? ?將從父進程發送的消息在發回給父
?? ?進程。
?? ?
?? ?
?? ?pipe,
?? ?
?? ?fork()
?? ?
?? ?if(pid>0)
?? ?{
?? ??? ?read(file,,)
?? ??? ?wirte(fd[1]);
?? ?}
?? ?
?? ?if(0 == pid)
?? ?{
?? ??? ??? ?read(fd[0]);
?? ??? ??? ?write(newfile);
?? ?}
?? ?
?? ?
?? ?
?? ?person
?? ?{
?? ??? ?char name[];
?? ??? ?int age;
?? ??? ?char phone;
?? ?}
?? ?
?? ?
?? ?pipe[];
?? ?fork();
?? ?
?? ?> 0
?? ?fgets 獲得輸入
?? ?填結構體,
?? ?寫入管道,
?? ?==0
?? ?
?? ?read,
?? ?填結構體,,
?? ?
?? ?顯示到。。。
?? ?
?? ?hello
?? ?pipe:hello?
?? ?wordld
?? ?pipe2 ::world;
?? ?
?? ?
?? ?
?? ?
?? ?
?? ?
有名管道===》fifo ==》有文件名稱的管道。
?? ??? ??? ??? ??? ? ?文件系統中可見
框架:
?? ?創建有名管道 ==》打開有名管道 ==》讀寫管道
?? ?==》關閉管道 ?==》卸載有名管道
1、創建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
?remove();
int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路徑+名稱下創建一個權限為
? ? ? mode的有名管道文件。
參數:pathname要創建的有名管道路徑+名稱
?? ? ?mode ?8進制文件權限。
返回值:成功 0
?? ??? ?失敗 ?-1;
2、打開有名管道 open
?? ?注意:該函數使用的時候要注意打開方式,
?? ?因為管道是半雙工模式,所有打開方式直接決定
?? ?當前進程的讀寫方式。
?? ?一般只有如下方式:
?? ?int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定讀端
?? ?int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定寫端
?? ?不能是 O_RDWR 方式打開文件。
?? ?不能有 O_CREAT 選項,因為創建管道有指定的mkfifo函數
3、管道的讀寫: 文件IO
?? ?讀: read(fd-read,buff,sizeof(buff));
?? ?寫: write(fd-write,buff,sizeof(buff));
4、關閉管道:
?? ??? ?close(fd);
5、卸載管道:remove();
?? ??? ?int unlink(const char *pathname);
?? ??? ?功能:將指定的pathname管道文件卸載,同時
?? ??? ??? ? ?從文件系統中刪除。
?? ??? ?參數: ptahtname 要卸載的有名管道?
?? ??? ?返回值:成功 0
?? ??? ??? ??? ?失敗 ?-1;
open會阻塞,等到另一端讀端打開,解除阻塞。
函數是否會阻塞,具體根據操作的對象有關。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo",0666); if(-1 == ret){//如果是管道文件已存在錯誤,讓程序繼續運行if(EEXIST== errno){}else {perror("mkfifo");exit(1);}}//open 會阻塞,等到另一端讀段打開,解除阻塞int fd = open("myfifo",O_WRONLY);if(-1 == fd){perror("open");exit(1);}char buf[256]="hello,fifo,test";write(fd,buf,strlen(buf));close(fd);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo",0666); if(-1 == ret){//如果是管道文件已存在錯誤,讓程序繼續運行if(EEXIST== errno){}else {perror("mkfifo");exit(1);}}int fd = open("myfifo",O_RDONLY);if(-1 == fd){perror("open");exit(1);}char buf[256]={0};read(fd,buf,sizeof(buf));printf("fifo %s\n",buf);close(fd);//remove("myfifo");return 0;
}
remove可以在命令行調用。
運行方法:
練習:
?? ?編寫一個非親緣關系進程間通信程序,可以從
?? ?A程序向B程序連續發送不同的數據,并從B中
?? ?將發送的信息打印輸出,當雙方收到quit的時候
?? ?程序全部退出。
思考題:
?? ?是否每次啟動程序必須進行有名管道的創建工作
?? ?能否有一個獨立的維護工具,可以任意創建并刪除
?? ?有名管道?
?? ?比如:
?? ??? ?./fifo_tool -A fifo ?==>創建一個有名管道fifo
?? ??? ?./fifo_tool -D fifo ?==>刪除一個有名管道fifo
作業:
?? ?有名管道的操作函數封裝:
?? ?int fifo_read(char *fifoname,void *s,int size);
?? ?int fifo_write(char *fifoname,void *s,int size);
?? ?編寫測試程序驗證以上兩個函數效果。
?? ?gstream
? 有名管道 ===》
?? ?1、是否需要同步,以及同步的位置。
?? ??? ?讀端關閉 是否可以寫,不能寫什么原因。
?? ??? ?寫端關閉 是否可以讀。
?? ??? ?結論:有名管道執行過程過必須有讀寫端同時存在。
?? ??? ??? ? ?如果有一端沒有打開,則默認在open函數部分阻塞。
?? ?2、有名管道是否能在fork之后的親緣關系進程中使用。
?? ??? ?結論: 可以在有親緣關系的進程間使用。
?? ??? ?注意: 啟動的次序可能會導致其中一個稍有阻塞。
?? ?3、能否手工操作有名管道實現數據的傳送。
?? ??? ?讀: cat ?fifoname
?? ??? ?寫: echo "asdfasdf" > fifoname
?