目錄&索引
- 致讀者
- 0 前言
- 1 概述
- 2 行程、執行緒與資訊共享
- 3 名字空間
- 4 出錯處理
- 5 小結
致讀者
剛接觸該書時甚是興奮,書中思想令人振奮,以致深夜久不能寐,但畢竟書籍年代久遠,剛好博主處于學習階段,本著探索精神,搭配 man 手冊進行編碼學習,獻上 W. Richard Stevens 所寫書籍中的一句話——"理解某種特性的實作通常有助于了解如何使用該特性”,這不禁讓人回想起侯捷先生所著《STL 原始碼剖析》的開篇,“原始碼之前,了無秘密”,
本文提煉自 UNIX 網路編程第 2 卷,下面會參考原書前言以明確內容提綱,致敬原書作者 W. Richard Stevens,
0 前言
行程間通信(IPC)幾乎是所有 UNIX 程式性能的關鍵,理解 IPC 也是理解如何開發不同主機間網路應用程式的必要條件,
原書從Posix IPC 和 System V IPC 的內部結構開始討論,全面深入地介紹 4 種 IPC 形式,
- 原書前言
大多數重要的程式都涉及行程間通信(Interprocess Communication, 簡稱 IPC),這是受下述設計原則影響的自然結果:把應用程式設計為一組相互通信的小片段比將其設計為單個龐大的程式更好,從歷史的角度看,應用程式有如下幾種構建方法:
1)用一個龐大的程式完成全部作業,程式的各部分可以實作為函式,函式之間通過引數、回傳值和全域變數來交換資訊;
2)使用多個程式,程式之間用某種形式的 IPC 進行通信,許多標準的 UNIX 工具都是按照這種風格設計的,它們使用 shell 管道(IPC的一種形式)在程式之間傳遞資訊;
3)使用一個包含多個執行緒的程式,執行緒之間使用某種 IPC,這里仍然使用術語 IPC,盡管通信是在執行緒之間而不是在行程之間進行的,
還可以把后兩種設計形式結合起來——用多個行程來實作,其中每個行程包含幾個執行緒,在這種情況下,行程內部的執行緒之間可以通信,不同行程之間也可以通信,
上面講述了可以把完成給定任務所需的作業分到多個行程中,或許還可以進一步分到行程內的多個執行緒中,在包含多個處理器(CPU)的系統中,多個行程也許可以(在不同的 CPU 上)同時運行,或許給定行程內的多個執行緒也能同時進行,因此,把任務分到多個行程或多個執行緒中,有望減少完成指定任務的時間,
本書詳細描述了以下 4 種不同的 IPC 形式:
1)訊息傳遞(匿名管道、FIFO 和訊息佇列);
2)同步(互斥鎖、條件變數、讀寫鎖、檔案與記錄鎖、信號量);
3)共享記憶體(匿名的與具名的);
4)遠程程序呼叫(Solaris 門和 Sun RPC),
本書不討論如何撰寫通過計算機網路通信的程式,這種通信通常涉及使用 TCP/IP 協議簇的套接字 API,相關主題在第 1 卷中詳細討論,
有人可能會提出質疑:不應該使用單主機或非網路 IPC(本卷的主題),所有程式都應該在網路上的多臺主機上同時運行,但在日常實踐中,單主機 IPC 往往比網路通信快得多,而且有時還簡單些,共享記憶體、同步等方法通常也只能用于單主機,跨網路時可能無法使用,經驗和歷史表明,非網路 IPC(本卷)與跨網路 IPC(第 1 卷)都是需要的,
1 概述
IPC 是行程間通信(interprocess communication)的簡稱,傳統上該術語描述的是運行在某個作業系統之上的不同行程間各種訊息傳遞(message passing)的方式,還講述多種形式的同步(synchronization),因為像共享記憶體區需要某種形式的同步參與,
- 管道(pipe):一個廣泛使用的 IPC 形式,既可在程式中使用,也可以在 shell 中使用,管道的問題在于它們最初只能在具有共同祖先(指父子行程關系)的行程間使用,不過該問題已隨命名管道(named pipe)即 FIFO 的引入而解決,匿名管道與命名管道均為半雙工,資料只能單向流動,
- 訊息佇列(message queue):訊息的佇列,存放在內核中并由訊息佇列識別符號標識,訊息佇列克服了信號傳遞資訊少、管道只能承載無格式位元組流以及緩沖區大小受限等缺點,
- 共享記憶體(shared memory):映射一段能被其他行程所訪問的記憶體,這段共享記憶體由一個行程創建,但多個行程都可以訪問,共享記憶體是最快的 IPC 方式,它是針對其他行程間通信方式運行效率低而專門設計的,它往往與其他通信機制,如信號量,配合使用,來實作行程間的同步和通信,
- 信號量(semaphore):信號量是一個計數器,可以用來控制多個行程對共享資源的訪問,它常作為一種鎖機制,防止某行程正在訪問共享資源時,其他行程也訪問該資源,因此,主要作為行程間以及同一行程內不同執行緒之間的同步手段,
- 套接字(socket):對網路中不同主機上的應用行程之間進行雙向通信的端點的抽象,一個套接字就是網路上行程通信的一端,提供了應用層行程利用網路協議交換資料的機制,從所處的地位來講,套接字上聯應用行程,下聯網路協議堆疊,是應用程式通過網路協議進行通信的介面,是應用程式與網路協議根進行互動的介面,(非該系列文章詳細介紹內容,將在后續專案中單獨介紹)
- 遠程程序呼叫(remote procedure call,簡稱 RPC):出現在 20 世紀 80 年代中期,它是從一個系統(客戶主機)上某個程式呼叫另一個系統(服務器主機)上某個函式的一種方法,
2 行程、執行緒與資訊共享
按照傳統的 UNIX 編程模型,我們在一個系統上運行多個行程,每個行程都有各自的地址空間,UNIX 行程間的資訊共享可以有多種方式,UNIX 行程間共享資訊的三種方式如圖:
- 1)左邊的兩個行程共享存留于檔案系統中某個檔案的某些資訊,為了訪問這些資訊,每個行程都得穿過內核(例如 read、write、lseek等),當一個檔案有待更新時,某種形式的同步是必要的,這樣既可保護多個寫入者,防止相互干擾,也可保護多個讀出者,防止寫入者的干擾,
- 2)中間的兩個行程共享駐留于內核中的某些資訊,管道是這種共享型別的例子,訊息佇列和信號量也是,當前情況下,訪問共享資訊的每次操作涉及對內核的一次系統呼叫,
- 3)右邊的兩個行程有一個雙方都能訪問的共享記憶體區,每個行程一旦設定好該共享記憶體區,就能不涉及內核而直接訪問其中的資料,共享記憶體區的行程需要某種形式的同步,
- 注意:沒有任何東西限制任何 IPC 只能使用兩個行程,IPC 適用于任何數目的行程,上圖展示兩個行程進行說明,是為了簡單起見,
執行緒
從 IPC 角度看來,一個給定行程內的所有執行緒共享同樣的全域變數,然而我們必須關注各個執行緒間對全域資料的同步訪問,盡管同步不是一種明確的 IPC 形式,但它確實伴隨許多形式的 IPC 使用,以控制對某些共享資料的訪問,
3 名字空間
當兩個或多個無親緣關系的行程使用某種型別的 IPC 物件來彼此交換資訊時,該 IPC 物件必須有一個某種形式的名字(name)或識別符號(identifier,簡稱 ID),這樣其中要給行程(往往是服務器)可以創建該 IPC 物件,其余行程則可以指定同一個 IPC 物件,
管道沒有名字(因此不能用于無親緣關系的行程間),但是 FIFO 有一個在檔案系統中的路徑名作為其識別符號(因此可用于無親緣關系的行程間),對于一種給定的 IPC 型別,其可能的名字的集合稱為它的名字空間(name space),名字空間很重要,因為對于除匿名管道以外的所有形式的 IPC 來說,名字是客戶與服務器彼此連接以交換資訊的手段,
key_t 鍵與 ftok 函式
以 key_t 鍵與 ftok 函式為例,在System V 訊息佇列、System V 信號量、System V 共享記憶體區,這三種 IPC 使用 key_t 值作為它們的名字,頭檔案 <system/types.h> 把 key_t 這個資料型別定義為一個整數,它通常是一個至少 32 位的整數,這些整數值通常是由 ftok 函式賦予的,
函式 ftok 把一個已存在的路徑名和一個整數識別符號轉換成一個 key_t 值,稱為 IPC 鍵,
#include <sys/ipc.h>
key_t ftok(const char *pathname, int id); // 回傳: 若成功則為 IPC 鍵, 若出錯則為 -1
4 出錯處理
包裹函式
在現實程式中,我們必須檢查每個函式呼叫是否回傳錯誤,由于碰到錯誤時終止程式執行是個慣例,因此我們可以通過定義包裹函式(wrapper function)來縮短程式的長度,包裹函式執行實際的函式呼叫,測驗其回傳值,并在碰到錯誤時終止行程,我們使用的命名約定是將函式名的第一個字母改成大寫字母,例如:
Sem_post(ptr);
定義這個包裹函式:
void Sem_post(sem_t *sem) {
if (sem_post(sem) == -1) {
err_sys("sem_post error");
}
}
每當你遇到一個大寫字母開頭的函式名時,它就是我們所說的包裹函式,它呼叫一個名字相同但相應小寫字母開頭的實際函式,當碰到錯誤時,包裹函式總是輸出一個錯誤訊息后終止,
UNIX errno 值
每當在一個 UNIX 函式(同 Linux 函式)中發生錯誤時,全域變數 errno 將被設定成一個指示錯誤型別的正數,函式本身則通常回傳 -1,
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid;
if ((pid = fork()) < 0) { // 出錯處理
perror("fork()");
exit(1);
}
if (pid) { // 子行程 fork() 回傳 0, 父行程 fork() 回傳正的子行程 pid
printf("In Parent printf Process! <%d>--><%d>--><%d>\n", getppid(), getpid(), pid);
} else {
printf("In Child printf Process! <%d>--><%d>\n", getppid(), getpid());
}
return 0;
}
5 小結
IPC 傳統上是 UNIX 中一個雜亂不堪的領域,雖然有了各種各樣的解決辦法,但沒有一個是完美的,討論分為 4 個主要領域:
- 1)訊息傳遞(匿名管道、FIFO、訊息佇列);
- 2)同步(互斥鎖、條件變數、讀寫鎖、信號量);
- 3)共享記憶體區(匿名共享記憶體區、有名共享記憶體區);
- 4)程序呼叫(Solaris 門、Sun RPC),
考慮單個行程中的多個執行緒間 IPC 以及多個行程間的 IPC,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/280534.html
標籤:區塊鏈
