了解 Linux IPC 相關的概念和原理有助於我們理解 Binder 通信原理。因此,在介紹 Binder 跨進程通信原理之前,我們先聊聊 Linux 系統下傳統的進程間通信是如何實現。
一、基本概念
Linux 進程間通信的原理圖如下圖所示:
可以看出來,Liunx 中跨進程通信涉及到的一些基本概念如下:
- 進程隔離
- 進程空間划分:用戶空間(User Space)/內核空間(Kernel Space)
- 系統調用:用戶態/內核態
1. 進程隔離
簡單的說就是操作系統中,進程與進程間內存是不共享的。兩個進程就像兩個平行的世界,A 進程沒法直接訪問 B 進程的數據,這就是進程隔離的通俗解釋。
A 進程和 B 進程之間要進行數據交互就得采用特殊的通信機制:進程間通信(IPC)。
2. 進程空間划分
操作系統的核心是內核,獨立於普通的應用程序,可以訪問受保護的內存空間,也可以訪問底層硬件設備的權限。
為了保護用戶進程不能直接操作內核,保證內核的安全,操作系統從邏輯上將虛擬空間划分為用戶空間(User Space)和內核空間(Kernel Space)。簡單的說就是,內核空間(Kernel)是系統內核運行的空間,用戶空間(User Space)是用戶程序運行的空間。為了保證安全性,它們之間是隔離的。
3. 系統調用:用戶態與內核態
雖然從邏輯上進行了用戶空間和內核空間的划分,但不可避免的用戶空間需要訪問內核資源,比如文件操作、訪問網絡等等。為了突破隔離限制,就需要借助系統調用來實現。系統調用是用戶空間訪問內核空間的唯一方式,保證了所有的資源訪問都是在內核的控制下進行的,避免了用戶程序對系統資源的越權訪問,提升了系統安全性和穩定性。
Linux 使用兩級保護機制:0 級供系統內核使用,3 級供用戶程序使用。
當一個任務(進程)執行系統調用而在內核代碼中執行時,稱進程處於內核運行態(內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每個進程都有自己的內核棧。當進程在執行用戶自己的代碼的時候,我們稱其處於用戶運行態(用戶態)。此時處理器在特權級最低的(3級)用戶代碼中運行。
系統調用主要通過如下兩個函數來實現:
copy_from_user() //將數據從用戶空間拷貝到內核空間 copy_to_user() //將數據從內核空間拷貝到用戶空間
二、Linux下的傳統IPC通信原理
通常的做法是消息發送方將要發送的數據存放在內存緩存區中,通過系統調用進入內核態。然后內核程序在內核空間分配內存,開辟一塊內核緩存區,調用 copyfromuser() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。同樣的,接收方進程在接收數據時在自己的用戶空間開辟一塊內存緩存區,然后內核程序調用 copytouser() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通信。
三、傳統的IPC通信存在的問題
1. 性能低下:一次數據傳遞需要經歷 內存緩存區 --> 內核緩存區 --> 內存緩存區,需要 2 次數據拷貝;
2. 接收端無法確定存儲空間:接收數據的緩存區由數據接收進程提供,但是接收進程並不知道需要多大的空間來存放將要傳遞過來的數據,因此只能開辟盡可能大的內存空間或者先調用 API 接收消息頭來獲取消息體的大小,這兩種做法不是浪費空間就是浪費時間.