天天看點

UNIX(程序間通信):01---Linux程序通信方式

Linux下的程序通信手段基本上是從Unix平台上的程序通信手段繼承而來的。而對Unix發展做出重大貢獻的兩大主力AT&T的貝爾實驗室及BSD(加州大學伯克利分校的伯克利軟體釋出中心)在程序間通信方面的側重點有所不同。前者對Unix早期的程序間通信手段進行了系統的改進和擴充,形成了“system V IPC”,通信程序局限在單個計算機内;後者則跳過了該限制,形成了基于套接口(socket)的程序間通信機制。Linux則把兩者繼承了下來,如圖示:

其中,最初Unix IPC包括:管道、FIFO、信号;System V IPC包括:System V消息隊列、System V信号燈、System V共享記憶體區;Posix IPC包括:Posix消息隊列、Posix信号燈、Posix共享記憶體區。有兩點需要簡單說明一下:1)由于Unix版本的多樣性,電子電氣工程協會(IEEE)開發了一個獨立的Unix标準,這個新的ANSI Unix标準被稱為計算機環境的可移植性作業系統界面(POSIX)。現有大部分Unix和流行版本都是遵循POSIX标準的,而Linux從一開始就遵循POSIX标準;2)BSD并不是沒有涉足單機内的程序間通信(socket本身就可以用于單機内的程序間通信)。事實上,很多Unix版本的單機IPC留有BSD的痕迹,如4.4BSD支援的匿名記憶體映射、4.3+BSD對可靠信号語義的實作等等。

圖一給出了linux 所支援的各種IPC手段,在本文接下來的讨論中,為了避免概念上的混淆,在盡可能少提及Unix的各個版本的情況下,所有問題的讨論最終都會歸結到Linux環境下的程序間通信上來。并且,對于Linux所支援通信手段的不同實作版本(如對于共享記憶體來說,有Posix共享記憶體區以及System V共享記憶體區兩個實作版本),将主要介紹Posix API。

linux下程序間通信的幾種主要手段簡介:

  1. 管道(Pipe)及有名管道(named pipe):管道可用于具有親緣關系程序間的通信,有名管道克服了管道沒有名字的限制,是以,除具有管道所具有的功能外,它還允許無親緣關系程序間的通信;
  2. 信号(Signal):信号是比較複雜的通信方式,用于通知接受程序有某種事件發生,除了用于程序間通信外,程序還可以發送信号給程序本身;linux除了支援Unix早期信号語義函數sigal外,還支援語義符合Posix.1标準的信号函數sigaction(實際上,該函數是基于BSD的,BSD為了實作可靠信号機制,又能夠統一對外接口,用sigaction函數重新實作了signal函數);
  3. 封包(Message)隊列(消息隊列):消息隊列是消息的連結表,包括Posix消息隊列system V消息隊列。有足夠權限的程序可以向隊列中添加消息,被賦予讀權限的程序則可以讀走隊列中的消息。消息隊列克服了信号承載資訊量少,管道隻能承載無格式位元組流以及緩沖區大小受限等缺點。
  4. 共享記憶體:使得多個程序可以通路同一塊記憶體空間,是最快的可用IPC形式。是針對其他通信機制運作效率較低而設計的。往往與其它通信機制,如信号量結合使用,來達到程序間的同步及互斥。
  5. 信号量(semaphore):主要作為程序間以及同一程序不同線程之間的同步手段。
  6. 套接口(Socket):更為一般的程序間通信機制,可用于不同機器之間的程序間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支援套接字。 

一般來說,linux下的程序包含以下幾個關鍵要素:

  • 有一段可執行程式;
  • 有專用的系統堆棧空間;
  • 核心中有它的控制塊(程序控制塊),描述程序所占用的資源,這樣,程序才能接受核心的排程;
  • 具有獨立的存儲空間

程序的建立

新程序的建立,首先在記憶體中為新程序建立一個task_struct結構,然後将父程序的task_struct内容複制其中,再修改部分資料。配置設定新的核心堆棧、新的PID、再将task_struct 這個node添加到連結清單中。所謂建立,實際上是“複制”。

子程序剛開始,核心并沒有為它配置設定實體記憶體,而是以隻讀的方式共享父程序記憶體,隻有當子程序寫時,才複制。即“copy-on-write”。

fork都是由do_fork實作的,do_fork的簡化流程如下圖:

fork函數

fork函數時調用一次,傳回兩次。在父程序和子程序中各調用一次。子程序中傳回值為0,父程序中傳回值為子程序的PID。程式員可以根據傳回值的不同讓父程序和子程序執行不同的代碼。

一個形象的過程:

運作這樣一段示範程式:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


int main()
{
 pid_t pid;
 char *message;
 int n = 0;
 pid = fork();
 while(1){
 if(pid < 0){
 perror("fork failed\n");
 exit(1);
 }
 else if(pid == 0){
 n--;
 printf("child's n is:%d\n",n);
 }
 else{
 n++;
 printf("parent's n is:%d\n",n);
 }
 sleep(1);
 }
 exit(0);
}      

可以發現子程序和父程序之間并沒有對各自的變量産生影響。

一般來說,fork之後父、子程序執行順序是不确定的,這取決于核心排程算法。程序之間實作同步需要進行程序通信。

什麼時候使用fork呢?

一個父程序希望子程序同時執行不同的代碼段,這在網絡伺服器中常見——父程序等待用戶端的服務請求,當請求到達時,父程序調用fork,使子程序處理此請求。

一個程序要執行一個不同的程式,一般fork之後立即調用exec

vfork函數

vfork與fork對比:

相同:

傳回值相同

不同:

fork建立子程序,把父程序資料空間、堆和棧複制一份;vfork建立子程序,與父程序記憶體資料共享;

vfork先保證子程序先執行,當子程序調用exit()或者exec後,父程序才往下執行

為什麼需要vfork?