文章目錄
背景
在插件化使用時,進程間通訊使用了AIDL
進行跨進程通訊,而AIDL
底層的實現是使用Binder
機制。
在深刻了解了AIDL
以後,咱們還須要再深刻學習Binder
。html
爲何須要跨進程通訊(IPC)
一個進程通常是對應一個App
,你不會但願別的進程(App
)可以垂手可得的能操做你的App
吧,因此你的App
只能訪問App
內部的數據。
可是有些場景是須要經過一個App
去操做另外一個App
的,好比:從App
中調用系統的文件管理,實現文件讀寫。好比從App
中讀取手機通訊錄的聯繫人信息。這種狀況就須要實現進程間通訊了。web
爲何是Binder?
Android
使用的 Linux
內核擁有着很是多的跨進程通訊機制,好比:管道,消息隊列,共享內存,System V,Socket
等;segmentfault
那麼Android
系統中的Binder
究竟有何過人之處呢?緩存
上述的進程間通訊存在的問題:安全
Socket
做爲一款通用接口,其傳輸效率低,開銷大,主要用在跨網絡的進程間通訊和本機上進程間的低速通訊。- 消息隊列和管道採用
存儲-轉發
方式,即數據先從發送方緩存區
拷貝到內核開闢的緩存區
中,而後再從內核緩存區
拷貝到接收方緩存區
,至少有兩次拷貝
過程。 - 共享內存雖然無需拷貝,但控制複雜,難以使用。
- 傳統
IPC
沒有任何安全措施,徹底依賴上層協議來確保。
Binder
的優點是:性能、穩定、安全。網絡
-
性能
| IPC方式 | 數據拷貝次數|
| ---- | ---- |
| 共享內存 | 0 |
| Binder | 1 |
| Socket/管道/消息隊列 | 2 |架構 -
穩定
Binder
是基於C/S
架構。經過客戶端(Client
)給服務端(Server
)發送指令而服務端根據指令返回數據的方式實現。
職責明確且互相獨立,所以不易出錯穩定性高。svg -
安全
傳統Linux IPC
的接收方沒法得到發送方進程可靠的UID/PID
,從而沒法鑑別對方身份;
而Android做爲一個開源系統,擁有很是多的開發平臺,App來源甚廣,所以手機的安全顯得額外重要;
對於普通用戶,毫不但願從商店下載的App
能偷窺隱私數據、後臺形成手機耗電等等問題。函數
Android
爲每一個安裝好的應用程序分配了本身的UID
,故進程的UID
是鑑別進程身份的重要標誌
而Binder
通訊能夠得到通訊進程的UID
,有了UID
就能夠鑑別進程的身份。
同時 Binder
支持實名 Binder
, 保證了安全性。性能
在分析性能時,談到了App數據緩存區
和內核緩存區
。
App數據緩存區
用於進程間數據隔離,而內核緩存區
的數據是能夠共享的。
爲了進一步瞭解進程間通訊
,咱們還須要去了解
- 用戶空間/內核空間
- 內核態/用戶態
- 內核模塊/驅動
用戶空間/內核空間
內核空間(Kernel Space
)是系統內核運行的空間
用戶空間(User Space
)是用戶程序運行的空間。
爲了保證安全性,它們之間是隔離的。可是有的時候用戶空間是須要去訪問內核空間的。
好比:文件讀寫操做。
而用戶空間訪問內核空間的惟一方式就是系統調用
。
系統調用:內核態/用戶態
Linux
使用兩級保護機制:0
級供系統內核使用,3
級供用戶程序使用。
經過系統調用
這個統一入口接口,全部的資源訪問都是在內核的控制下執行,以避免致使對用戶程序對系統資源的越權訪問,從而保障了系統的安全和穩定。
當一個進程執行系統調用而陷入內核代碼中執行時,咱們就稱進程處於內核態。此時處理器處於特權級最高的(0級)內核代碼中執行
當進程在執行用戶本身的代碼時,則稱其處於用戶態。此時處理器在特權級最低的(3級)用戶代碼中運行。
系統調用
主要經過以下兩個函數來實現:
copy_from_user()
//將數據從用戶空間拷貝到內核空間copy_to_user()
//將數據從內核空間拷貝到用戶空間
傳統的IPC
就是使用上述兩個系統調用
的方法來實現進程間通訊 。
傳統 IPC 的原理
- 消息發送方將要發送的數據存放在
用戶的內存緩存區
中,經過系統調用
進入內核態。 - 內核程序在內核空間開闢一塊
內核緩存區
,操做系統調用copy_from_user() 函數
將數據從用戶空間的內存緩存區
拷貝到內核空間的內核緩存區
中。 - 接收方進程在本身的
用戶空間開闢一塊內存緩存區
,而後內核程序調用copy_to_user() 函數
將數據從內核緩存區拷
貝到接收進程的內存緩存區
。
這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,也就是進程間通訊。
內核模塊 / 「驅動」
經過系統調用,用戶空間能夠訪問內核空間,
那麼若是一個用戶空間想與另一個用戶空間進行通訊怎麼辦呢?
很天然想到的是讓操做系統內核添加支持;
傳統的Linux
通訊機制,好比Socket, 管道等
都是內核支持的;
可是Binder
並非Linux
內核的一部分,它是怎麼作到訪問內核空間的呢?
Linux
的動態可加載內核模塊(Loadable Kernel Module,LKM)
機制解決了這個問題;
該模塊是具備獨立功能的程序,它能夠被單獨編譯,但不能獨立運行。
它在運行時被連接到內核做爲內核的一部分在內核空間運行。
Android
系統經過添加一個內核模塊運行在內核空間,用戶進程之間經過這個模塊做爲橋樑,就能夠完成通訊。
在Android
系統中,這個運行在內核空間的,負責各個用戶進程經過 Binder
通訊的內核模塊叫作Binder驅動
;
TIP:
驅動程序
通常指的是設備驅動程序(Device Driver
),是一種可使計算機和設備通訊的特殊程序。
至關於硬件的接口,操做系統只有經過這個接口,才能控制硬件設備的工做;
驅動
就是操做硬件的接口,爲了支持Binder
通訊過程,Android
經過軟件層面實現的Binder驅動
,它相似於硬件接口用於和內核交互。
所以這個模塊被稱之爲驅動。
前面說到了Binder
的數據拷貝只有一次,而傳統的IPC
除了共享文件外都是最少兩次數據拷貝
那麼Binder
驅動是如何實現的呢?
Binder IPC 機制實現原理
有點深奧,晦澀難懂。之後慢慢啃
Binder IPC
機制中實現數據拷貝僅一次的原理是用到了內存映射
。數據拷貝是在
內存映射:
首先
映射
是指創建一種關係,而這裏的內存映射
顧名思義就是將用戶空間
的一塊內存區域映射到內核空間
。在映射的過程當中數據並無拷貝,只是雙方創建了連接。這個連接在物理上是不存在,只是邏輯上存在。也就是說這個聯繫咱們看不見摸不着,只是咱們代碼上給它們進行了關聯。
映射關係創建後,用戶對這塊內存區域的修改能夠直接反應到內核空間;反以內核空間對這段區域的修改也能直接反應到用戶空間。
而咱們如何在代碼上進行關聯呢?
答案是經過操做系統調用 mmap() 方法
來實現,可是 mmap()
一般是用在有物理介質
的文件系統上的。
而Binder
並不存在物理介質,所以mmap() 方法
並非爲了在物理介質和用戶空間之間創建映射,
mmap() 方法
會返回一個指針ptr
,該指針指向邏輯地址中的空間,這時候尚未數據拷貝。
要實現詩句拷貝須要將邏輯地址
轉換爲物理地址
,這個過程須要經過MMU(MemoryManagementUnit 內存管理單元)
實現。
因爲第一次數據通訊雙方還沒創建映射,MMU
在地址映射表中是沒法找到與指針ptr
相對應的物理地址的,也就是MMU
失敗,將產生一個缺頁中斷,
缺頁中斷的中斷響應函數會在swap 分區
中尋找相對應的頁面,
若是找不到(也就是該文件歷來沒有被讀入內存的狀況),則會經過mmap()
創建的映射關係,從硬盤上將文件讀取到物理內存中。
TIP:
swap 分區
一般被稱爲交換分區,這是一塊特殊的硬盤空間,即當實際內存不夠用的時候,操做系統會從內存中取出一部分暫時不用的數據,放在交換分區中,從而爲當前運行的程序騰出足夠的內存空間。
因此數據拷貝是經過缺頁中斷機制
將用戶數據寫入內存中。
一次完整的Binder IPC 通訊過程
一般是這樣:
- 首先
Binder 驅動
在內核空間建立一個數據接收緩存區; - 接着在內核空間開闢一塊內核緩存區,創建發送方進程和接收方進程對內科緩存區的映射關係;
- 發送方進程經過
系統調用 copy_from_user()
將數據copy
到內核中的內核緩存區,因爲內核緩存區和接收進程的用戶空間存在內存映射,所以也就至關於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通訊。
總結
Binder
是Android系統
提供的進程間通訊的一種方式。
之因此提供Binder
是由於傳統的IPC
機制存在一些問題
- 性能:傳統的
IPC
機制,如:Socket,管道,消息隊列
在通訊時數據會經歷兩次拷貝。而Binder
僅一次。 - 穩定:
Binder
基於C/S
架構模式,代碼結構清晰不易出錯。 - 安全:
Android
是開源系統,衆多軟件魚龍混雜。傳統的IPC
機制,通訊雙方不能鑑別身份。而Binder
提供了UID
用於標識進程,進而能鑑別通訊雙方。
傳統的IPC
通訊機制原理是用到了系統調用
的兩個方法
copy_from_user()
: 將數據從用戶空間拷貝到內核空間copy_to_user()
: 將數據從內核空間拷貝到用戶空間
實現過程是:
- 發送方將數據存入
用戶的內存緩存區
- 接收方在在本身進程開闢
用戶的內存緩存區
- 內核空間開闢
內核緩存區
- 發送方從用戶態進入內核態,並經過系統調用
copy_from_user()
將發送方的內存緩存區
的數據拷貝放入內核緩存區
,而後經過copy_to_user()
將數據拷貝到接收方的內存緩存區
。
這就是傳統IPC
機制通訊須要兩次數據拷貝的問題。
而Binder
僅須要一次數據拷貝。
它的原理是用到了內存映射和系統調用 mmap() 方法
- 經過內存映射的方式實現
發送方-內核-接收方
之間的對應關係。 - 再經過
系統調用 mmap() 方法
返回Binder驅動
中的邏輯地址,而邏輯地址要和物理地址轉換須要經過MMU
MMU
在鏈接邏輯地址和物理地址的時候會調用缺頁中斷方法
在swap 分區
中尋找相對應的數據。若是沒找到,說明數據不存在,則須要進行數據拷貝數據拷貝使用的仍是系統調用 copy_from_user()
方法。- 因爲存在映射關係,因此數據拷貝一次便可實現發送方和接收方的進程通訊。
參考
- Binder學習指南
- 爲何 Android 要採用 Binder 做爲 IPC 機制?
- Android跨進程通訊:圖文詳解 Binder機制 原理
- Android Bander設計與實現 - 設計篇
- 寫給 Android 應用工程師的 Binder 原理剖析!
- 內存映射原理
- 系統調用mmap詳解整理
- Linux swap分區及做用詳解
本文同步分享在 博客「_龍衣」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。