進程如果不是獨立進程,那麼它就需要和別的進程進行通信。在進程協作時可以採用共享一個緩衝區的方式來實現。當然,OS的IPC提供了一種機制,以允許不必通過共享地址空間來通信和同步其動作。這就不得不提Linux的的前身Unix。因爲Linux一開始就是從這兒借鑑的。加上Linux從一開始就遵守POSIX標準。
Unix最早是由AT&T的貝爾實驗室開發的,值得一提的是,在Unix操作系統發展的過程中,產生了許多副產物(POSIX標準也是副產物之一),其中最著名的應當是C語言。是的,它僅僅是個副產物。那個時候Ken Thompson 與Dennis Ritchie感到用彙編語言做移植太過於頭痛,他們想用高級語言來完成第三版。後來他們改造了B語言,就形成了今天大名鼎鼎的C語言。這個自發明到現在這個物聯網時代仍佔據編程語言榜前10的穩固位置。不得不感嘆其生命力的強大以及適應性的強大。當然,Ken Thompson 與Dennis Ritchie也是圖靈獎得主。
到了1980年,有兩個最主要的Unix的版本線,一個是UC Berkeley的BSD UNIX,另一個是AT&T的Unix。至今爲止UC Berkeley仍在維護Unix(這學校真牛逼)。
最初的Unix的IPC包括,管道,FIFO,信號。貝爾實驗室對Unix早期的進程通信進行了改進,形成了system V這個操作系統的IPC。它包括:system V消息隊列,system V信號燈,system V共享內存。當然POSIX IPC也有相應的一套。BSD Unix設計了socket(套接字)通信。這樣將進程之間的通信不僅僅限制在單機內。Linux繼承了這些。
進程間通信的目的:
- 數據傳輸:一個進程將數據發送給另一個進程
- 共享數據:多個進程操作共享數據(比如:售票系統),一個進程對共享數據進行了修改,另外一個進程應該立即看到,(否則票買完了,但是另一邊不知道,還在賣)
- 通知:一個進程告訴另外一個進程發生了某些事件。
- 資源共享
- 進程控制:一個進程控制另外一個進程的執行(例如debug程序)。它希望知道另一個進程的實時狀態。
Linux進程通信方式:
管道:管道(pipe)分爲無名管道和有名管道。無名管道用於具有親緣關係進程間的通信,有名管道則可以在任意的進程中間進行通信。
管道通信具有以下的特點:
- 管道是半雙工的。(雙向通信的,但是不能同時向雙方傳輸)
- 只能用於父子進程或者是兄弟進程之間(就是要具有親緣關係)
- 管道是一種文件(能讀寫),它只存在於內存之中。他是具有親緣關係的進程共享的。
- 寫入的內容每次都添加到管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀取數據。
Linux建立無名管道函數是pipe函數。它需要的頭文件是#include<unistd.h>.
函數原型:int pipe(int filedes[2]);
函數功能:pipe建立一個無名管道文件,若成功返回0,否則返回-1.錯誤原因由errno給出。管道文件的描述符由filedes數組返回。其中filedes[0]爲管道的讀取端,filedes[1]爲寫入端。
代碼測試如下:
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
#include<linux/limits.h> //這個頭文件中有PIPE_BUF
int main()
{
int filedes[2]; //保存管道文件的文件描述符
char str[30] = {"Hello World!"};
char temp[30] = {0};
if(0 != pipe(filedes)) //創建管道失敗
{
printf("errno=%d\n",errno);
return 0;
}
if(0 < fork()) //父進程
{
close(filedes[0]); //爲避免不必要的錯誤,關閉讀端
write(filedes[1],str,strlen(str));
close(filedes[1]);
wait(NULL); //回收子進程
exit(0);
}
else
{
sleep(3); //讓父進程先執行
close(filedes[1]); //爲避免不必要的錯誤,關閉寫端
read(filedes[0],temp,strlen(str));
close(filedes[0]);
printf("%s\n",temp);
exit(0);
}
return 0;
}
在讀寫管道文件的時候,最好是嚴格遵守文件的讀寫規則,在使用完畢後一定要關閉文件。爲了避免不必要的一些錯誤,在使用管道的文件的要先創建管道文件,然後創建新進程,這樣所有的進程才能共享這個管道文件。代碼中爲了避免向讀取端寫入和從寫入端讀取而引發的錯誤,在讀的時候關閉寫端,在寫的時候關閉讀端。
代碼中先讓父進程向管道文件中寫入了字符串“Hello World!”。然後子進程讀取管道文件中的字符串,並向屏幕打印。程序執行結果如下:
如果子進程讀取到的管道文件爲空,那麼read()函數將會使得進程阻塞,這時候父進程將會執行,然後完成對管道文件的寫入。之後wait()將父進程掛起,子進程完成讀取。同樣,管道已經滿時,進程再試圖寫管道,在其它進程從管道中移走數據之前,寫進程將一直阻塞。(典型的生產者——消費者模型)管道是存在於內存中的文件(實際上內核實現的一個隊列),他是進程的資源,會隨着進程的銷燬而銷燬。還有一點是管道中的東西在讀取後就會被刪除。管道文件有大小限制的,在我現在的內核版本下他是4KB。管道文件的大小由PIPE_BUF描述。它在#include<linux/limits.h>這個頭文件中給出。
#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */
向管道寫入數據的時候Linux不保證寫入的原子性,管道緩衝區一有空閒,寫進程就會去寫入。所以要及時讀取管道文件
同時管道還要求寫端對讀端的依賴性,示例代碼如下:
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
#include<linux/limits.h> //這個頭文件中有PIPE_BUF
int main()
{
int filedes[2]; //保存管道文件的文件描述符
char str[30] = {"Hello World!"};
char temp[30] = {0};
int num;
if (0 < fork())
{
sleep(1);
close(filedes[0]); //關閉讀端
num = write(filedes[1],str,strlen(str));
if(-1 == num)
{
printf("error!\n");
}
else
{
printf("write to pipe is %d\n",num);
}
close(filedes[1]);
wait(NULL);
//exit(0);
}
else
{
//子進程不讀,不寫,直接將管道文件兩端都關閉
close(filedes[0]);
close(filedes[1]);
exit(0);
}
return 0;
}
輸出結果如下:
這個時候,在父進程中將無法寫入。所以管道這個描述還是很形象的,當你向一段水管裏面裝水的時候,需要將另一端堵上,否則裝入的水全都流走了。因此在父進程寫的時候,需要先關閉讀;在子進程讀的時候需要先關閉寫。同時,不能在沒有讀的情況下將管子兩頭堵上。
當子進程結束的時候,父進程關閉讀,調用write寫數據,這時候父進程將會收到子進程SIGPIPE信號,當前進程將會中斷,而不是阻塞。