一篇文章瞭解相見恨晚的 Android Binder 進程間通訊機制【轉】

來源:互聯網
上載者:User

標籤:water   緩衝   使用   新大陸   工作原理   enter   假設   特權   瞭解   

本文轉載自:70082302

Android-Binder進程間通訊機制概述

最近在學習Binder機制,在網上查閱了大量的資料,也看了老羅的Binder系列的部落格和Innost的深入理解Binder系列的部落格,都是從底層開始講的,全是C代碼,雖然之前學過C和C++,然而各種函數之間花式跳轉,看的我都懷疑人生。毫不誇張的講每看一遍都是新的內容,跟沒看過一樣。後來又看到了Gityuan的部落格看到了一些圖解彷彿發現了新大陸。

下面就以圖解的方式介紹下Binder機制,相信你看這篇文章,一定有所收穫。

什麼是 Binder?

Binder是Android系統中進程間通訊(IPC)的一種方式,也是Android系統中最重要的特性之一。Android中的四大組件Activity,Service,Broadcast,ContentProvider,不同的App等都運行在不同的進程中,它是這些進程間通訊的橋樑。正如其名“粘合劑”一樣,它把系統中各個組件粘合到了一起,是各個組件的橋樑。

理解Binder對於理解整個Android系統有著非常重要的作用,如果對Binder不瞭解,就很難對Android系統機制有更深入的理解。

1. Binder 架構

  • Binder 通訊採用 C/S 架構,從組件視角來說,包含 Client、 Server、 ServiceManager 以及 Binder 驅動,其中 ServiceManager 用於管理系統中的各種服務。
  • Binder 在 framework 層進行了封裝,通過 JNI 技術調用 Native(C/C++)層的 Binder 架構。
  • Binder 在 Native 層以 ioctl 的方式與 Binder 驅動通訊。
2. Binder 機制

  • 首先需要註冊服務端,只有註冊了服務端,用戶端才有通訊的目標,服務端通過 ServiceManager 註冊服務,註冊的過程就是向 Binder 驅動的全域鏈表 binder_procs 中插入服務端的資訊(binder_proc 結構體,每個 binder_proc 結構體中都有 todo 任務隊列),然後向 ServiceManager 的 svcinfo 列表中緩衝一下註冊的服務。

  • 有了服務端,用戶端就可以跟服務端通訊了,通訊之前需要先擷取到服務,拿到服務的代理,也可以理解為引用。比如下面的代碼:

    //擷取WindowManager服務引用WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
    • 1
    • 2

    擷取服務端的方式就是通過 ServiceManager 向 svcinfo 列表中查詢一下返回服務端的代理,svcinfo 列表就是所有登入服務的通訊錄,儲存了所有註冊的服務資訊。

  • 有了服務端的引用我們就可以向服務端發送請求了,通過 BinderProxy 將我們的請求參數發送給 ServiceManager,通過共用記憶體的方式使用核心方法 copy_from_user() 將我們的參數先拷貝到核心空間,這時我們的用戶端進入等待狀態,然後 Binder 驅動向服務端的 todo 隊列裡面插入一條事務,執行完之後把執行結果通過 copy_to_user() 將核心的結果拷貝到使用者空間(這裡只是執行了拷貝命令,並沒有拷貝資料,binder只進行一次拷貝),喚醒等待的用戶端並把結果響應回來,這樣就完成了一次通訊。

怎麼樣是不是很簡單,以上就是 Binder 機制的主要通訊方式,下面我們來看看具體實現。

3. Binder 驅動

我們先來瞭解下使用者空間與核心空間是怎麼互動的。

先瞭解一些概念

使用者空間/核心空間

詳細解釋可以參考 Kernel Space Definition; 簡單理解如下:

Kernel space 是 Linux 核心的Runspace,User space 是使用者程式的Runspace。 為了安全,它們是隔離的,即使使用者的程式崩潰了,核心也不受影響。

Kernel space 可以執行任意命令,調用系統的一切資源; User space 只能執行簡單的運算,不能直接調用系統資源,必須通過系統介面(又稱 system call),才能向核心發出指令。

系統調用/核心態/使用者態

雖然從邏輯上抽離出使用者空間和核心空間;但是不可避免的的是,總有那麼一些使用者空間需要訪問核心的資源;比如應用程式訪問檔案,網路是很常見的事情,怎麼辦呢?

Kernel space can be accessed by user processes only through the use of system calls.

使用者空間訪問核心空間的唯一方式就是系統調用;通過這個統一入口介面,所有的資源訪問都是在核心的控制下執行,以免導致對使用者程式對系統資源的越權訪問,從而保障了系統的安全和穩定。使用者軟體良莠不齊,要是它們亂搞把系統玩壞了怎麼辦?因此對於某些特權操作必須交給安全可靠的核心來執行。

當一個任務(進程)執行系統調用而陷入核心代碼中執行時,我們就稱進程處於核心運行態(或簡稱為核心態)此時處理器處於特權級最高的(0級)核心代碼中執行。當進程在執行使用者自己的代碼時,則稱其處於使用者運行態(使用者態)。即此時處理器在特權級最低的(3級)使用者代碼中運行。處理器在特權等級高的時候才能執行那些特權CPU指令。

核心模組/驅動

通過系統調用,使用者空間可以訪問核心空間,那麼如果一個使用者空間想與另外一個使用者空間進行通訊怎麼辦呢?很自然想到的是讓作業系統核心添加支援;傳統的 Linux 通訊機制,比如 Socket,管道等都是核心支援的;但是 Binder 並不是 Linux 核心的一部分,它是怎麼做到訪問核心空間的呢? Linux 的動態可載入核心模組(Loadable Kernel Module,LKM)機制解決了這個問題;模組是具有獨立功能的程式,它可以被單獨編譯,但不能獨立運行。它在運行時被連結到核心作為核心的一部分在核心空間運行。這樣,Android系統可以通過添加一個核心模組運行在核心空間,使用者進程之間的通過這個模組作為橋樑,就可以完成通訊了。

在 Android 系統中,這個運行在核心空間的,負責各個使用者進程通過 Binder 通訊的核心模組叫做 Binder 驅動;

驅動程式一般指的是裝置驅動程式(Device Driver),是一種可以使電腦和裝置通訊的特殊程式。相當於硬體的介面,作業系統只有通過這個介面,才能控制硬體裝置的工作;

驅動就是操作硬體的介面,為了支援Binder通訊過程,Binder 使用了一種“硬體”,因此這個模組被稱之為驅動。

熟悉了上面這些概念,我們再來看下上面的圖,使用者空間中 binder_open(), binder_mmap(), binder_ioctl() 這些方法通過 system call 來調用核心空間 Binder 驅動中的方法。核心空間與使用者空間共用記憶體通過 copy_from_user(), copy_to_user() 核心方法來完成使用者空間與核心空間記憶體的資料轉送。 Binder驅動中有一個全域的 binder_procs 鏈表儲存了服務端的進程資訊。

4. Binder 進程與線程

對於底層Binder驅動,通過 binder_procs 鏈表記錄所有建立的 binder_proc 結構體,binder 驅動層的每一個 binder_proc 結構體都與使用者空間的一個用於 binder 通訊的進程一一對應,且每個進程有且只有一個 ProcessState 對象,這是通過單例模式來保證的。在每個進程中可以有很多個線程,每個線程對應一個 IPCThreadState 對象,IPCThreadState 對象也是單例模式,即一個線程對應一個 IPCThreadState 對象,在 Binder 驅動層也有與之相對應的結構,那就是 Binder_thread 結構體。在 binder_proc 結構體中通過成員變數 rb_root threads,來記錄當前進程內所有的 binder_thread。

Binder 線程池:每個 Server 進程在啟動時建立一個 binder 線程池,並向其中註冊一個 Binder 線程;之後 Server 進程也可以向 binder 線程池註冊新的線程,或者 Binder 驅動在探測到沒有空閑 binder 線程時主動向 Server 進程註冊新的的 binder 線程。對於一個 Server 進程有一個最大 Binder 線程數限制,預設為16個 binder 線程,例如 Android 的 system_server 進程就存在16個線程。對於所有 Client 端進程的 binder 請求都是交由 Server 端進程的 binder 線程來處理的。

5. ServiceManager 啟動

瞭解了 Binder 驅動,怎麼與 Binder 驅動進行通訊呢?那就是通過 ServiceManager,好多文章稱 ServiceManager 是 Binder 驅動的守護進程,大管家,其實 ServiceManager 的作用很簡單就是提供了查詢服務和註冊服務的功能。下面我們來看一下 ServiceManager 啟動的過程。


- ServiceManager 分為 framework 層和 native 層,framework 層只是對 native 層進行了封裝方便調用,圖上展示的是 native 層的 ServiceManager 啟動過程。

  • ServiceManager 的啟動是系統在開機時,init 進程解析 init.rc 檔案調用 service_manager.c 中的 main() 方法入口啟動的。 native 層有一個 binder.c 封裝了一些與 Binder 驅動互動的方法。

  • ServiceManager 的啟動分為三步,首先開啟驅動建立全域鏈表 binder_procs,然後將自己當前進程資訊儲存到 binder_procs 鏈表,最後開啟 loop 不斷的處理共用記憶體中的資料,並處理 BR_xxx 命令(ioctl 的命令,BR 可以理解為 binder reply 驅動處理完的響應)。

6. ServiceManager 註冊服務

  • 註冊 MediaPlayerService 服務端,我們通過 ServiceManager 的 addService() 方法來註冊服務。

  • 首先 ServiceManager 向 Binder 驅動發送 BC_TRANSACTION 命令(ioctl 的命令,BC 可以理解為 binder client 用戶端發過來的請求命令)攜帶 ADD_SERVICE_TRANSACTION 命令,同時註冊服務的線程進入等待狀態 waitForResponse()。 Binder 驅動收到請求命令向 ServiceManager 的 todo 隊列裡面添加一條註冊服務的事務。事務的任務就是建立服務端進程 binder_node 資訊並插入到 binder_procs 鏈表中。

  • 交易處理完之後發送 BR_TRANSACTION 命令,ServiceManager 收到命令後向 svcinfo 列表中添加已經註冊的服務。最後發送 BR_REPLY 命令喚醒等待的線程,通知註冊成功。

7. ServiceManager 擷取服務

  • 擷取服務的過程與註冊類似,相反的過程。通過 ServiceManager 的 getService() 方法來註冊服務。

  • 首先 ServiceManager 向 Binder 驅動發送 BC_TRANSACTION 命令攜帶 CHECK_SERVICE_TRANSACTION 命令,同時擷取服務的線程進入等待狀態 waitForResponse()。

  • Binder 驅動收到請求命令向 ServiceManager 的發送 BC_TRANSACTION 查詢登入的服務,查詢到直接響應 BR_REPLY 喚醒等待的線程。若查詢不到將與 binder_procs 鏈表中的服務進行一次通訊再響應。

8. 進行一次完整通訊

  • 我們在使用 Binder 時基本都是調用 framework 層封裝好的方法,AIDL 就是 framework 層提供的傻瓜式是使用方式。假設服務已經註冊完,我們來看看用戶端怎麼執行服務端的方法。

  • 首先我們通過 ServiceManager 擷取到服務端的 BinderProxy 代理對象,通過調用 BinderProxy 將參數,方法標識(例如:TRANSACTION_test,AIDL中自動產生)傳給 ServiceManager,同時用戶端線程進入等待狀態。

  • ServiceManager 將使用者空間的參數等請求資料複製到核心空間,並向服務端插入一條執行執行方法的事務。事務執行完通知 ServiceManager 將執行結果從核心空間複製到使用者空間,並喚醒等待的線程,響應結果,通訊結束。

總結

好了,這裡只是從實現邏輯上簡單介紹了下 Binder 機制的工作原理,想要深入理解 Binder 機制,還得自己下功夫,看源碼,儘管這個過程很痛苦。一遍看不懂就再來一遍,說實話本人理解能力比較差,跟著部落格思路看了不下十遍。努力總會有收穫,好好欣賞 native 層各方法之間花式跳轉的魅力吧。最後你將發現新世界的大門在向你敞開。

網上資料很多,個人覺得比較好的如下: 
1. Bander設計與實現 
2. 老羅的 Android處理序間通訊(IPC)機制Binder簡要介紹和學習計劃 系列 
3. Innost的 深入理解Binder 系列 
4. Gityuan的 Binder系列 (基於 Android 6.0) 
5. Binder學習指南

參考資料
  • Binder系列
  • Binder學習指南

更多文章: 
https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode



歡迎長按 -> 識別圖中二維碼

 

 

或者 掃一掃 關注我的公眾號

 

一篇文章瞭解相見恨晚的 Android Binder 進程間通訊機制【轉】

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.