Linux:程序間通訊之管道通訊詳解
在學習程序的時候,我們瞭解到了程序的獨立性:程序之間是相互獨立的,每個程序有自己的虛擬地址空間,並且虛擬地址空間通過頁表的對映,對映到屬於自己的實體記憶體上。並且各個程序之間互相不影響,執行自己的程式碼。
但是正因為程序的獨立性,所以導致程序間進行資料通訊將變得非常麻煩。作業系統為了使程序間能夠通訊,會提供一個介質能夠讓多個程序都能訪問。也就是在記憶體上開闢一塊公共資源,讓程序在這塊公共資源上面交流聯絡。就像我們人一樣,每個人都是一個獨立的個體,但是到了一個公共場所,人與人就會不由的交流聯絡。
程序間通訊:
一、目的:
程序之間的通訊能夠讓程序之間進行資料的交換,並且這些程序共用一塊公共資源,共同享有這些資源,程序之間進行資訊的通知,如fork之後,子程序若退出則需要向父程序傳送退出資訊,讓父程序來維護子程序的退出資訊。同時,程序間如果能夠通訊交流,那麼可以達到對程序的控制。
二、分類:
程序間通訊分為管道通訊、System V程序間通訊、POSIX程序間通訊。
管道通訊又分為匿名管道與命名管道通訊。
System V程序間通訊有:訊息佇列、共享記憶體、訊號量,其中共享記憶體通訊是速度最快的程序間通訊方式。
POSIX程序間通訊方式有:訊息佇列、共享記憶體、訊號量、互斥量、條件變數、讀寫鎖。
今天我們先來理解程序間通訊中的管道通訊:
管道通訊
一、管道:在理解管道通訊之前,我們要先理解管道這個概念:
所謂“管道”,是指用於連線一個讀程序和一個寫程序以實現它們之間通訊的一個共享檔案,就像現實中的水管,水就像資料。(連線程序,相當於在程序間連線一個通路,用來傳遞資訊)
“Linux下⼀切皆⽂件”,所以我們可以把管道看做是檔案,是服務於管道通訊的特殊檔案,而管道通訊是一種通訊方式。
二、管道通訊的基本概念:
1.原理:作業系統為程序提供一個雙方都能訪問的緩衝區。
2.功能:傳輸資料資源。
3.特性:管道通訊是一個半雙工通訊方式(單向通訊),它不能在建立時就確定確定資料流向(作業系統無法確定誰讀誰寫),而是在使用的時候確定,因此作業系統會提供兩個描述符供使用,一個讀一個寫,這樣的確定方向就是將對應的一段關閉掉即可,這樣方向的控制權就交給了使用者。
4.分類:管道通訊分為兩類:匿名管道、命名管道。
5.管道通訊是訊息傳遞的一種特殊方式,管道機制必須提供以下三方面的協調能力:互斥、同步和確定對方的存在。
匿名管道:
一、概念:
僅僅適用於具有親緣關係(如父子程序)的程序間通訊,因為匿名管道無法被其他程序找到,也就無法通訊,所以只能通過子程序複製父程序的方法,讓子程序能夠訪問到相同的管道來實現通訊。(管道的操作:io操作------檔案描述符)
二、建立
介面:pipe(int fd[2]),其中fd[1]用於讀,fd[2]用於寫。建立匿名管道必須在建立子程序之前,否則子程序將無法複製。
三、讀寫特性:
-
如果管道沒有資料,那麼read一直等待,直到有資料;
-
如果管道資料滿了,那麼write一直等待,直到有資料被讀取出去。
-
如果所有管道寫入端關閉,read讀完所有資料之後返回0;
-
如果所有管道讀寫端關閉,write將觸發異常,作業系統此時會發送SIGPIPE訊號,通知我們讀取端都被關閉了。 (這個訊號會導致write端程序退出)。
四、管道自帶同步與互斥特性:
- 同步:保證一個操作訪問的時序性。
- 互斥:保證操作的同一時間唯一訪問。
五、實現:
例:這裡利用fork產生一個子程序, 然後讓父程序在之前建立的管道內進行寫入內容,寫入一個hello world,然後子程序去讀這個hello world到buf裡面。這裡注意,由於管道是單向的,所以父程序在寫入內容之前,需將管道的讀入,也就是fd[0]關閉,在寫完之後再將fd[1]關閉,防止外來因素影響,而子程序同理,在讀之前將fd[1]關閉。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
pid_t pid;
int pipefd[2] = { 0 };
if (pipe(pipefd) < 0)
{
perror("pipe error");
return -1;
}
pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
else if (pid == 0) //子程序 讀
{
close(pipefd[1]);//關閉寫端
char buff[1024] = { 0 };
read(pipefd[0], buff, 1024);
printf("child:%s\n", buff);
close(pipefd[0]);
}
else //父程序 寫
{
close(pipefd[0]);//關閉讀端
char *ptr = "hello world";
write(pipefd[1],ptr,strlen(ptr);
close(pipefd[1]);
}
return 0;
}
執行程式碼:
這個例子中利用fork來共享管道的原理:
命名管道:
未完待續...