處理序間通訊的目的
資料轉送:一個進程需要將它的資料發送給另一個進程,發送的資料量在一個位元組到幾MB之間。
共用資料:多個進程想要操作共用資料,一個進程對共用資料的修改,別的進程應該立刻看到。
通知事件:一個進程需要向另一個或一組進程發送訊息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。
資源共用:多個進程之間共用同樣的資源。為了作到這一點,需要核心提供鎖和同步機制。
進程式控制制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,並能夠及時知道它的狀態改變。
進程通訊方式
linux下處理序間通訊的幾種主要方式:
(1)管道(pipe)和有名管道(FIFO)
(2)訊號(signal)
(3)訊息佇列
(4)共用記憶體(shared memory)
(5)訊號量(semaphore)
(6)通訊端(socket)
管道
管道(pipe)及有名管道(named pipe):管道可用於具有親緣關係進程間的通訊,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係進程間的通訊。
管道是單向的、先進先出的、無結構的、固定大小的位元組流,它把一個進程的標準輸出和另一個進程的標準輸入串連在一起。寫進程在管道的尾端寫入資料,讀進程在管道的首端讀出資料。資料讀出後將從管道中移走,其它讀進程都不能再讀到這些資料。管道提供了簡單的流量控制機制。進程試圖讀空管道時,在有資料寫入管道前,進程將一直阻塞。同樣,管道已經滿時,進程再試圖寫管道,在其它進程從管道中移走資料之前,寫進程將一直阻塞。通常有種限制,一是半雙工,只能單向傳輸;二是只能在父子進程間使用。
有名管道(也叫FIFO,因為管道工作在先入先出的原則下,第一個寫入管道的資料也是第一個被讀出的資料)。與管道不同,FIFO不是臨時的對象,它們是檔案系統中真正的實體,可以用mkfifo命令建立。只要有合適的存取權限,進程就可以使用FIFO。FIFO的開啟檔案和管道稍微不同。一個管道(它的兩個file資料結構、VFS I節點和共用資料頁)是一次性建立的,而FIFO已經存在,可以由它的使用者開啟和關閉。Linux必須處理在寫進程開啟FIFO之前讀進程對它的開啟,也必須處理在寫進程寫資料之前讀進程對管道的讀。除此以外,FIFO幾乎和管道的處理完全一樣,而且它們使用一樣的資料結構和操作。
訊號
訊號(signal):訊號是比較複雜的通訊方式,用於通知接受進程有某種事件發生,除了用於處理序間通訊外,進程還可以發送訊號給進程本身;linux除了支援Unix早期訊號語義函數sigal外,還支援語義符合Posix.1標準的訊號函數sigaction(實際上,該函數是基於BSD的,BSD為了實現可靠訊號機制,又能夠統一對外介面,用sigaction函數重新實現了signal函數)。
訊號是在軟體層次上對中斷機制的一種類比,是一種非同步通訊方式。
訊號可以直接進行使用者空間進程和核心進程之間的互動,核心進程也可以利用它來通知使用者空間進程發生了哪些系統事件。它可以在任何時候發給某一進程,而無需知道該進程的狀態。
如果該進程當前並未處於執行態,則該訊號就由核心儲存起來,直到該進程恢複執行再傳遞給它;如果一個訊號被進程設定為阻塞,則該訊號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程 。
進程執行訊號的方式:
忽略訊號,即對訊號不做任何處理,其中,有兩個訊號不能忽略:SIGKILL及SIGSTOP。
捕捉訊號,定義訊號處理函數,當訊號發生時,執行相應的處理函數。
執行預設操作,Linux對每種訊號都規定了預設操作。
訊息佇列
訊息佇列:訊息佇列是訊息的連結資料表,包括Posix訊息佇列System V訊息佇列。有足夠許可權的進程可以向隊列中添加訊息,被賦予讀許可權的進程則可以讀走隊列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺點。
訊息佇列的實現包括建立或開啟訊息佇列、添加訊息、讀取訊息和控制訊息隊列這四種操作:
建立或開啟訊息佇列使用的函數是msgget,這裡建立的訊息佇列的數量會受到系統訊息佇列數量的限制。
添加訊息使用的函數是msgsnd函數,它把訊息添加到已開啟的訊息佇列末尾。
讀取訊息使用的函數是msgrcv,它把訊息從訊息佇列中取走,與FIFO不同的是,這裡可以指定取走某一條訊息。
控制訊息隊列使用的函數是msgctl,它可以完成多項功能。
訊號量/號誌
訊號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步手段。訊號量是用來解決進程之間的同步與互斥問題的一種進程之間通訊機制,包括一個稱為訊號量的變數和在該訊號量下等待資源的進程等待隊列,以及對訊號量進行的兩個原子操作(PV操作)。其中訊號量對應於某一種資源,取一個非負的整型值。訊號量值指的是當前可用的該資源的數量,若它等於0則意味著目前沒有可用的資源。
P操作:如果有可用的資源(訊號量值>0),則佔用一個資源(給訊號量值減去一,進入臨界區代碼)。如果沒有可用的資源(訊號量值等於0),則被阻塞到,直到系統將資源分派給該進程(進入等待隊列,一直等到資源輪到該進程)。
V操作:如果在該訊號量的等待隊列中有進程在等待資源,則喚醒一個阻塞進程。如果沒有進程等待它,則釋放一個資源(給訊號量值加一)。
共用記憶體
共用記憶體(shared memory)可以說是最有用的處理序間通訊方式,也是最快的IPC形式。兩個不同進程A、B共用記憶體的意思是,同一塊實體記憶體被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共用記憶體中資料的更新,反之亦然。由於多個進程共用同一塊記憶體地區,必然需要某種同步機制,互斥鎖和訊號量都可以。
採用共用記憶體通訊的一個顯而易見的好處是效率高,因為進程可以直接讀寫記憶體,而不需要任何資料的拷貝。對於像管道和訊息佇列等通訊方式,則需要在核心和使用者空間進行四次的資料拷貝,而共用記憶體則只拷貝兩次資料:一次從輸入檔案到共用記憶體區,另一次從共用記憶體區到輸出檔案。實際上,進程之間在共用記憶體時,並不總是讀寫少量資料後就解除映射,有新的通訊時,再重建立立共用記憶體地區。而是保持共用地區,直到通訊完畢為止,這樣,資料內容一直儲存在共用記憶體中,並沒有寫迴文件。共用記憶體中的內容往往是在解除映射時才寫迴文件的。因此,採用共用記憶體的通訊方式效率是非常高的。
共用記憶體實現的步驟:
1.建立共用記憶體,這裡用到的函數是shmget,也就是從記憶體中獲得一段共用記憶體地區。
2.映射共用記憶體,也就是把這段建立的共用記憶體映射到具體的進程空間中去,這裡使用的函數是shmat。
3.使用不帶緩衝的I/O讀寫命令對其進行操作。
4.撤銷映射的操作,其函數為shmdt。
套介面
套介面(socket):更為一般的處理序間通訊機制,可用於不同機器之間的處理序間通訊。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支援通訊端。