Android 進程間通信(二) -- 理解 Binder 的機制

參考 寫給 Android 應用工程師的 Binder 原理剖析 一些文字和圖片均參考該文

系列文章
Android 進程間通信(一) – Android 多進程模式
Android 進程間通信(二) – 理解 Binder 的機制
Android 進程間通信(三) --通過 AIDL 理解Binder,並手寫Binder服務

上一章中,已經理解了進程之間通信的一些基本知識,這一章來好好學習 Binder。

一、爲啥使用Binder

我們知道,Android 底層使用了大量的 Binder 來進行進程之間的通信。那爲啥要新設計個 Binder ,而不是用傳統的IPC 通信方式呢?
主要是考慮到以下幾個方面:

  1. 性能方面:Socket 作爲通過接口,但傳輸效率低,開銷大,且阻塞 IO,一般用於跨網絡的進程間通信;而消息隊列和管道,則採用用存儲-轉阿發方法,至少經過兩次拷貝;共享內存雖然無需拷貝,但實現複雜,控制也麻煩。

圖片原來於 Android Binder 設計與實現在這裏插入圖片描述

  1. 穩定性:Binder 基於C/S架構,client 有什麼需求就丟給 server,架構清晰,又相互獨立。
  2. 安全性:Android 爲每個應用都分配了自己的UID,用來鑑別身份,而傳統的 IPC 則無法做到。

想要了解 Binder ,想了解 傳統的 Linux 的IPC 機制。

1.1、linux 的 IPC 機制

Linux 採用了虛擬地址空間地址,操作系統將虛擬內存分爲 用戶空間 (User space) 和內核空間 (Kernel) ,普通的應用程序運行在用戶空間,而系統內核運行在內核空間;這也是我們常說的兩進程之間的數據不共享,不通過特殊手段不共享的問題。

在這裏插入圖片描述
從上圖可以看出來傳統的Linux跨進程涉及的一些點。

  • 進程隔離
  • 進程空間劃分:用戶空間(User space) 和 內核空間(Kerner space)
  • 系統調用狀態:內核態和用戶態

1.1.1 進程隔離

在操作系統中,兩個進程之間的數據時不共享的,必須通過特殊的通信機制,進程間數據才能共享。

1.1.2 進程空間劃分

現在的操作系統都是採用虛擬存儲器,對於 32 位系統而言,它的尋址地址就是 2的32次方,即 4GB;
對操作系統而言,其中核心的部分稱爲內核,它的權限最高,可以訪問受限的內存空間,也可以訪問硬件設備,爲了保護用戶進程不能直接操作內核,保證內核的安全性,從邏輯上,將虛擬空間分戶爲用戶空間和內核空間。
其中將高地址的1G劃分爲內核空間,而低地址的3G劃分爲用戶空間;
在這裏插入圖片描述

1.1.3 系統調用: 內核態,用戶態

雖然有以上哪些劃分,但兩進程之間不可能永遠不通信;當兩進程需要進行數據交互時,就需要系統調用來實現。系統調用時用戶空間訪問內核空間的唯一方式,保證了所有的資源訪問都是在內核控制下進行了,避免了越權訪問,提供系統穩定性。

Linux 使用兩級保護機制:0 級供系統內核使用,3 級供用戶程序使用。
當一個任務(進程)執行系統調用而陷入內核代碼中執行時,稱進程處於內核運行態(內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每個進程都有自己的內核棧。

當進程在執行用戶自己的代碼的時候,我們稱其處於用戶運行態(用戶態)。此時處理器在特權級最低的(3級)用戶代碼中運行。

系統調用如下兩個函數來實現:

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

一般來說,A進程和B進程是無法和對方數據交互,但有些情況下,就需要兩個進程之間有交互,而這個交互過程就叫做 IPC (Inter Process Communication ,進程間通信),IPC 的實質是 數據的交互。IPC 的通信過程如下:

  • A進程發送方,把要發送的數據放到 用戶空間的內存緩存區
  • 內核程序在內核空間開闢一塊內核緩存區,並將A進程用戶空間的數據,通過 copy_from_user 從內存緩衝區,拷貝到內核空間的內核緩衝區。
  • B進程接收方,也在用戶空間開闢一塊 內存緩存區,準備接受數據
  • 內核程序將內核緩存區通過 copy_to_user 將數據拷貝到用戶空間的內存緩存區。

在這裏插入圖片描述
通過以上過程,IPC 一次就完成了,但這種方式有比較大的缺陷:

  • 由於不知道需要多大內存用於存放數據,因此都是儘可能開闢大的內存,會導致浪費
  • 性能較低,整個過程需要經過A進程 內存緩存區 - 內核緩存區 - 內存緩存區,需要兩次拷貝。

1.2 Binder IPC通信原理

爲了克服傳統 IPC 的不足;Android 引入了 Binder 機制。Binder 在數據交互這塊,可以充當是一個橋樑的作用,讓兩個進程之間能夠相互通信。

從上面知道,數據的通信少不了內核的幫助,而Binder不屬於內核,但通過 Linex 的 LKM 機制:

模塊是具有獨立功能的程序,它可以被單獨編譯,但不能獨立運行。它在運行時被鏈接到內核作爲內核的一部分在內核空間運行

因此,Binder 作爲模塊存在於內核中,即成爲 Binder 驅動。回顧上一節,傳統IPC進程之間的通信需要兩次的數據拷貝,Binder 卻可以藉助 Linux 的另一個特性,只用一次性拷貝,就能實現 IPC 過程,這個就是內存映射

Binder IPC 涉及到的內存映射通過 mmap() 來實現,mmap()是操作系統中一個內存映射的方法;簡單來說,內存映射,就是把 用戶空間的一塊內存區域映射到內核空間,映射建立之後,用戶空間的數據,就能反映到這塊內核空間的內存來,這樣,當用戶空間的數據修改,內核空間的數據也會跟着被改動

內存映射能減少拷貝次數,實現用戶空間和內核空間的效率互動。兩個空間各自修改的數據能直接反射在映射的內存區域,從而被對方空間及時感知。

一次完整的 Binder IPC 通信過程是這樣的:

  1. Binder 驅動在內核空間建立內核緩存區
  2. Binder 驅動在內核空間創建接收數據緩存區,並與內核緩存區建立映射,以及與接收進程的 用戶空間地址建立映射關係
  3. 發送方進程通過系統調用 copy_from_user() 將數據copy 到內核中的內核緩存區,由於內核緩存區與接收數據緩存區有映射,接收數據緩存區又與接收方的用戶空間地址有映射,所以,數據直接就到接收方的用戶空間上了。
    如下圖:

在這裏插入圖片描述

二. Binder 通信模型

上面介紹了 Binder IPC 的通信原理,這裏實現層是如何設計的。

上面說到,Binder 是基於 C/S 架構的,由一系列組件組成,包括 Client 、Server、ServerManager,Binder驅動等

其中 Client 、Server、ServerManager 運行在用戶空間,Binder 驅動運行在內核空間。ServerManager 和 Binder 驅動是由系統提供,而 Client 、Server則由用戶自己創建。

Client 、Server、ServerManager,均是通過系統調用 open、mmap 和ioctl 來訪問設備文件 /dev/binder 的,從而實現與 Binder 的交互來 間接實現進程之間的數據通信。
如下圖:
在這裏插入圖片描述

  • Binder 驅動,已經解釋過了,就是兩個兩個進程之間的橋樑
  • ServiceManager :它是binder的服務大管家,它的作用只有一個,註冊和查詢。一個Binder註冊的時候,會攜帶對應的字符串,而 client 在獲取這個binder 的時候,就可以通過這個字符串,通過 ServiceManager 查詢拿到 binder。它也是一個looper循環。

三. Binder 的代理模式

從上面已經清楚,Client、Server 藉助Binder驅動,完成跨進程的實現機制。
但有個問題,A 進程想要獲取B進程的 object是,驅動是不是真的就把 object 返回 A ?

當然不是,當數據在 Binder 驅動 流過時,會對數據進行一層轉換;當 A 想獲取 B 的object 對象時,驅動並不會把 object 對象給 A,而是返回了一個跟 object 一模一樣的代理對象 objectProxy,objectProxy 不具備 object 方法的能力;當A 使用 objectProxy 時,只需要把參數通過 objectProxy 給 Binder 驅動就可以了,看起來就像調用了 object 了。

當B接收到 A 進程的消息時,發現這個是 objectProxy,就去查詢自己的表達那,一旦發現這個 B 進程的object 代理對象,就會通知 B 調用 object方法,並把結果返回給自己 或者 A 進程。如下:
在這裏插入圖片描述
這個章節,在 AIDL 的時候再來詳細分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章