Android - 從淺到懂理解 Binder

背景

在插件化使用時,進程間通訊使用了AIDL進行跨進程通訊,而AIDL底層的實現是使用Binder機制。
在深刻了解了AIDL以後,咱們還須要再深刻學習Binderhtml

爲何須要跨進程通訊(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 的原理

  1. 消息發送方將要發送的數據存放在用戶的內存緩存區中,經過系統調用進入內核態。
  2. 內核程序在內核空間開闢一塊內核緩存區,操做系統調用copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。
  3. 接收方進程在本身的用戶空間開闢一塊內存緩存區,而後內核程序調用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 到內核中的內核緩存區,因爲內核緩存區和接收進程的用戶空間存在內存映射,所以也就至關於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通訊。

總結

BinderAndroid系統提供的進程間通訊的一種方式。

之因此提供Binder是由於傳統的IPC機制存在一些問題

  • 性能:傳統的IPC機制,如:Socket,管道,消息隊列在通訊時數據會經歷兩次拷貝。而Binder僅一次。
  • 穩定:Binder基於C/S架構模式,代碼結構清晰不易出錯。
  • 安全:Android是開源系統,衆多軟件魚龍混雜。傳統的IPC機制,通訊雙方不能鑑別身份。而Binder提供了UID用於標識進程,進而能鑑別通訊雙方。

傳統的IPC通訊機制原理是用到了系統調用的兩個方法

  • copy_from_user() : 將數據從用戶空間拷貝到內核空間
  • copy_to_user() : 將數據從內核空間拷貝到用戶空間

實現過程是:

  1. 發送方將數據存入用戶的內存緩存區
  2. 接收方在在本身進程開闢用戶的內存緩存區
  3. 內核空間開闢內核緩存區
  4. 發送方從用戶態進入內核態,並經過系統調用copy_from_user()將發送方的內存緩存區的數據拷貝放入內核緩存區,而後經過copy_to_user()將數據拷貝到接收方的內存緩存區

這就是傳統IPC機制通訊須要兩次數據拷貝的問題。

Binder僅須要一次數據拷貝。
它的原理是用到了內存映射和系統調用 mmap() 方法

  • 經過內存映射的方式實現發送方-內核-接收方之間的對應關係。
  • 再經過系統調用 mmap() 方法返回Binder驅動中的邏輯地址,而邏輯地址要和物理地址轉換須要經過MMU
  • MMU在鏈接邏輯地址和物理地址的時候會調用缺頁中斷方法swap 分區中尋找相對應的數據。若是沒找到,說明數據不存在,則須要進行數據拷貝數據拷貝使用的仍是系統調用 copy_from_user()方法。
  • 因爲存在映射關係,因此數據拷貝一次便可實現發送方和接收方的進程通訊。

參考

本文同步分享在 博客「_龍衣」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索