每個應用都是一個進程,進程與進程之間是不能相互同訊,並且資源不共享(為了避免非法訪問),但是仍有方法可以通過進程來通訊(eg. 複製應用 A 的文字到 應用 B 當中)
廣義的來說,進程通訊 (Inter-process communication, IPC) 是指運行在不同進程(不論是否在同一台機器)中的若干線程的數據交換
IPC 通訊有許多種實現方式,以下會介紹幾種常用的 IPC 通訊方式
管道適用於所有 POSIX 系統(Linux) & Windows 產品,Pipe 是 Linux 由 UNIX 繼承而來的進程通訊機制,管道有以下特性
分立管道的兩邊進行數據通訊
管道是單向的只能 單邊 讀 or 寫如,同水流(也就是半雙工),如果要讀寫同時就必須建立兩個管道
半雙工:雙向都可以通,但不能同時讀、寫
一根管道就有讀取&寫入的特性,不是讀就是寫,但無法同時
管道有 容量限制,若是管道滿了則會堵塞,若是 空的也會掛起等待
管道機制其實就是 內存中創建一個共享文件,兩個進程都會映射該文件到自己的進程,這是有關於記憶體的 檔案映射(請參考另外一篇)
Linux 有相對的函數可以實現管道 Pipe
Linux 函數 | 功能 | 返回 |
---|---|---|
int pipe(int[], int) | 創建管道 | 返回 -1 代表失敗 |
消息隊列是由一個一個 Message 所串成的 Linked 列表,每個 Message 都存放在內存中,由消息隊列 標示符號 進行標示,並允 1 ~ 多個進程訪問該消息(Read or Write)
由兩個進程直接共享同一塊內存(像是共享同一塊土地),這種共享需要經過以下幾個步驟
創建:創建內存共享區
映射:映射該內存共享區到兩個進程的記憶體
訪問:訪問內存共享區
通訊:通訊進程通訊
共享內存本身並沒有同步機制,所以同步方案需要協商
撤銷:雙方的內存映射區
刪除:內存用區
好處~ 方便回收內存
它與管道的概念類似,同樣是使用一塊內存空間,將該空間映射到司有的虛擬地址中(進程中),我們就可以對該進程進行直接讀寫
它的好處就很明顯,速度相當快,但壞處是 需要自己做內存保護(鎖),否則會發生資源混亂 (管道就不會有這個壞處,因為 PipeLine 是半雙工)
Linux 有相對的函數可以實現共享內存
Linux 函數 | 功能 | 返回 |
---|---|---|
int shmget(…) | 分配內存 | 返回內存區的 id |
char* shmat(…) | 記憶體映射 | 返回映射區的起始內存地址 |
int shmdt(…) | 刪除內存映射 | 返回 0 表示成功 |
int shmctl(…) | 控制內存區數據 | 返回內存區的 id |
Android 中最常使用的機制是 Binder 再來就是 UDS Socket
UNIX Domain Socket(UDS)是專門針對進程溝通設計出來的,也稱為 IPC Socket,IPC Socket & Network Socket 在實現上差異級大
UDS 的啟動與 Network Socket 差不多,只是在參數上有些區分
RPC 通訊涉及了雙方運行在 不同機台上的通訊,在 RPC 通訊時不須特別關注他是如何實現通訊的
客戶關進程調用 Sub 接口
Stub 根據操作系統進行打包數據,並執行 系統調用
由內核進行與服務器的互交,負責將客戶端的數據傳給服務器的內核
服務器 Sub 接收到數據,並批配解析
傳送到服務器進程
進程最難的問題在於,如何同步,只有達成同步才能 安全取得、設定 資源
同步定義:多個進程之間存在時序關係,必須要協同完成 一項 任務時,則稱為同步
Semaphore 包括了 1 信號量(Semaphore)、2 Operation P(Wait)、3 Operation V(Single),信號量代表個 共用的資源,P 表示消耗資源,V 回歸資源
要注意幾個點,就能了解
對 S 的操作都是 原子操作
P 在資源不足時會等待
V 在資源足夠時會去喚醒等待的 P 操作
P 是 Consumer、V 是 Producer
信號量可看做一個計時器,用來控制多個進程訪問 同一資源(符合定義),常用來作為一種同步手段
可以把它當成簡單版本的 Semaphore,資源 S 只有 0 & 1,資源只有佔用 (lcoked) 跟 被佔用中 (unlocked),本質上並沒有差異
信號其實也是上層軟體對於 interrupt 的一種模擬(在 MCU 中其實就有相當多的 interrut),同樣可以作為異步通訊,當接收到通知就代表可以使用該資源
Linux 內核可以用信號來通知用戶空間發生的 Event (信號資源由內核管理)
它是對 Semaphone 機制的延伸 & 改善,是控制更為簡單的同步手段(若是對於一個龐大的系統而言 Semaphone 就不容易做管理),為了使資源訪問可以 互斥,所以提出 管程的概念
Monitor 可以安全的讓線程 or 進程安全的訪問對象 (object) or 模塊(module),同一時間只能讓一個訪問者訪問 (鎖的概念),它有用以下特性:
流行語言 Delphi、Java、Python、Ruby、C# 都有實現 Monitor 機制
Futex 的特點就在於 Fast 快速~
Futex 在 Android 中使用在 ART 虛擬機 (mutex.cc),如果開啟了 ART_USE_FUTEXES 宏,則會使用 Linux Futex 來實現
Mutex 加鎖的邏輯是,如果獲取的到鎖,則直接返回,否則就呈現掛起(等待)的狀態
調用 futex 時使用指令 FUTEX_WAIT_PRIVATE(operation)通常代表 futext word 保持 val 值,可能會需要等待較長的時間
省略了 systemcall 的上下文切換時間 (CPU 切換 Process 的時間)
目前 android 封裝的同步類包括以下三種
/include/utils/Mutex.h
/include/utils/Condition.h
/surfaceflinger/Barrier.h
Mutex 實際上只是基於 pthread_mutex_t 類型的封裝
AutoMutex 類
另外 Mutex 有定義一個內部類 AutoMutex,用於 解構時 自動釋放鎖
核心思想是 條件滿足就返回,繼續執行動作,否則就休眠等待(掛起),直到有滿足條件的人將其喚醒
這就像是 Mutex 必須主動 去獲取鎖再判斷,而 Condition 是被動 被通知
Condition 是一個殼,它並不會去定義具體的喚醒條件,Condition 想要提供一種通用的解決方案,而不是針對某些具體條件去設計
Condition & Mutex 所持有是同一把鎖
為何 Condition wait 會需要 Mutex 會在 Barrier 看到範例
先說明一下 Barrier 累事專門為 SurfaceFlinger 設計的
Mutex 是互斥鎖,Condition 表示條件,而 Barrier 則是 Condition & Mutex 兩者的應用
Barrier#wait 函數為何需要先獲取 Mutex 鎖再調用 Control 的方法?
假設沒有 Mutex 這把鎖
接著看 Condition# wait 方法,pthread_cond_wait 的等待邏輯(休眠的過程)
如果在 wait 之前先使用鎖鎖住就不會發生無法喚醒的問題
Barrier 通常用在某現程式已經完成初始化的判斷,這種場景是不可逆的
Read 的特點是可以多個線程(進程)一起讀取同一個檔案,Write 則是相反,只能一次一個線程(進程)執行,所以 Write 就是排他鎖(Exclusive)
ReaderWriterMutex 可以有三種狀態
Free:還未被任何對象持有
Exclusive:當前被唯一一個對象持有
Shared:可被多個對象持有
State | ExclusiveLock | ExclusiveUnlock | SharedLock | SharedUnlock |
---|---|---|---|---|
Free | Exclusive | error | SharedLock(1) | error |
Exclusive | Block | Free | Block | error |
Shared(n) | Block | error | SharedLock(n+1)* | Shared(n-1) or Free |
Android 系統
進程