Binder系列2—Binder Driver再探

一、Binder通信簡述



上一篇文章Binder Driver初探介紹了Binder驅動的init、open、mmap、ioctl這4個核心方法,并說明與Binder相關的常見結構體。

Client進程通過RPC(Remote Procedure Call Protocol)與Server通信,可以簡單地劃分為三層,驅動層、IPC層、業務層。demo()便是Client端和Server共同協商好的統一方法;handle、RPC數據、代碼、協議這4項組成了IPC層的數據,通過IPC層進行數據傳輸;而真正在Client和Server兩端建立通信的基礎設施便是Binder Driver



二、Binder通信協議

2.1 通信模型


Binder協議包含在IPC數據中,分為兩類:

BINDER_COMMAND_PROTOCOL:binder請求碼,以”BC_“開頭,簡稱BC碼,用于從IPC層傳遞到Binder Driver層;

BINDER_RETURN_PROTOCOL?:binder響應碼,以”BR_“開頭,簡稱BR碼,用于從Binder Driver層傳遞到IPC層;

Binder IPC通信至少是兩個進程的交互:

client進程執行binder_thread_write,根據BC_XXX命令,生成相應的binder_work;

server進程執行binder_thread_read,根據binder_work.type類型,生成BR_XXX,發送到用戶空間處理。



2.1.1 通信過程


其中binder_work.type共有6種類型:



2.2 binder_thread_write

請求處理過程是通過binder_thread_write()方法,該方法用于處理Binder協議中的請求碼。當binder_buffer存在數據,binder線程的寫操作循環執行。


對于請求碼為BC_TRANSACTION或BC_REPLY時,會執行binder_transaction()方法,這是最為頻繁的操作。 對于其他命令則不同。

2.2.1 binder_transaction


路由過程:handle -> ref -> target_node -> target_proc

reply的過程會找到target_thread;

非reply則一般找到target_proc;

對特殊的嵌套binder call會根據transaction_stack來決定是插入事務到目標線程還是目標進程。

2.2.2 BC_PROTOCOL

binder請求碼,是用enum binder_driver_command_protocol來定義的,是用于應用程序向binder驅動設備發送請求消息,應用程序包含Client端和Server端,以BC_開頭,總17條;(-代表目前不支持的請求碼)


1、BC_FREE_BUFFER:

通過mmap()映射內存,其中ServiceManager映射的空間大小為128K,其他Binder應用進程映射的內存大小為1M-8K。

Binder驅動基于這塊映射的內存采用最佳匹配算法來動態分配和釋放,通過binder_buffer結構體中的free字段來表示相應的buffer是空閑還是已分配狀態。對于已分配的buffers加入到binder_proc中的allocated_buffers紅黑樹;對于空閑的buffers加入到binder_proc中的free_buffers紅黑樹。

當應用程序需要內存時,根據所需內存大小從free_buffers中找到最合適的內存,并放入allocated_buffers樹;當應用程序處理完后必須盡快使用BC_FREE_BUFFER命令來釋放該buffer,從而添加回到free_buffers樹。

2,BC_INCREFS、BC_ACQUIRE、BC_RELEASE、BC_DECREFS等請求碼的作用是對binder的強/弱引用的計數操作,用于實現強/弱指針的功能。

3,參數類型主要有以下幾類:

binder_transaction_data(結構體)

binder_ptr_cookie(由binder指針和cookie組成)

binder_uintptr_t(指針)

__u32(無符號整型)

4,Binder線程創建與退出:

BC_ENTER_LOOPER:binder主線程(由應用層發起)的創建會向驅動發送該消息;joinThreadPool()過程創建binder主線程;

BC_REGISTER_LOOPER:Binder用于驅動層決策而創建新的binder線程;joinThreadPool()過程,創建非binder主線程;

BC_EXIT_LOOPER:退出Binder線程,對于binder主線程是不能退出;joinThreadPool()的過程出現timeout,并且非binder主線程,則會退出該binder線程;


2.3 binder_thread_read

響應處理過程是通過binder_thread_read()方法,該方法根據不同的binder_work->type以及不同狀態,生成相應的響應碼。



說明:

當transaction堆棧為空,且線程todo鏈表為空,且non_block=false時,意味著沒有任何事務需要處理的,會進入等待客戶端請求的狀態。當有事務需要處理時便會進入循環處理過程,并生成相應的響應碼。在Binder驅動層,只有在進入binder_thread_read()方法時,同時滿足以下條件, 才會生成BR_SPAWN_LOOPER命令,當用戶態進程收到該命令則會創建新線程:

binder_proc的requested_threads線程數為0;

binder_proc的ready_threads線程數為0;

binder_proc的requested_threads_started個數小于15(即最大線程個數);

binder_thread的looper狀態為BINDER_LOOPER_STATE_REGISTERED或BINDER_LOOPER_STATE_ENTERED。

那么在哪里處理響應碼呢? 通過前面的Binder通信協議圖,可以知道處理響應碼的過程是在用戶態處理,即后續文章會講到的用戶空間IPCThreadState類中的IPCThreadState::waitForResponse()IPCThreadState::executeCommand()兩個方法共同處理Binder協議中的18個響應碼。

2.3.1 BR_PROTOCOL

binder響應碼,是用enum binder_driver_return_protocol來定義的,是binder設備向應用程序回復的消息,,應用程序包含Client端和Server端,以BR_開頭,總18條;

.........

BR_SPAWN_LOOPER:binder驅動已經檢測到進程中沒有線程等待即將到來的事務。那么當一個進程接收到這條命令時,該進程必須創建一條新的服務線程并注冊該線程,在接下來的響應過程會看到何時生成該響應碼。

BR_TRANSACTION_COMPLETE:當Client端向Binder驅動發送BC_TRANSACTION命令后,Client會收到BR_TRANSACTION_COMPLETE命令,告知Client端請求命令發送成功;對于Server向Binder驅動發送BC_REPLY命令后,Server端會收到BR_TRANSACTION_COMPLETE命令,告知Server端請求回應命令發送成功。

BR_DEAD_REPLY: 當應用層向Binder驅動發送Binder調用時,若Binder應用層的另一個端已經死亡,則驅動回應BR_DEAD_BINDER命令。

BR_FAILED_REPLY: 當應用層向Binder驅動發送Binder調用時,若transaction出錯,比如調用的函數號不存在,則驅動回應BR_FAILED_REPLY。



三. 場景總結

3.1 BC協議使用場景


binder_thread_write()根據不同的BC協議而執行不同的流程。 其中BC_TRANSACTION和BC_REPLY協議,會進入binder_transaction()過程。

2.4.2 binder_work類型

BINDER_WORK_TRANSACTION

binder_transaction()

binder_release_work()

BINDER_WORK_TRANSACTION_COMPLETE

binder_transaction()

binder_release_work()

BINDER_WORK_NODE

binder_new_node()

BINDER_WORK_DEAD_BINDER

binder_thread_write(),收到BC_REQUEST_DEATH_NOTIFICATION

BINDER_WORK_DEAD_BINDER_AND_CLEAR

binder_thread_write(),收到BC_CLEAR_DEATH_NOTIFICATION

BINDER_WORK_CLEAR_DEATH_NOTIFICATION

binder_thread_write(),收到BC_CLEAR_DEATH_NOTIFICATION

binder_thread_write(),收到BC_DEAD_BINDER_DONE\

3.2 BR協議使用場景

BR_DEAD_REPLY,BR_FAILED_REPLY,BR_ERROR這些都是失敗或錯誤相關的應答協議

3.3 協議轉換圖



圖解:(以BC_TRANSACTION為例)

發起端進程:binder_transaction()過程將BC_TRANSACTION轉換為BW_TRANSACTION;

接收端進程:binder_thread_read()過程,將BW_TRANSACTION轉換為BR_TRANSACTION;

接收端進程:IPC.execute()過程,處理BR_TRANSACTION命令。

注:BINDER_WORK_xxx –> BW_xxx

3.4 數據轉換圖


圖(左)說明:

AMP.startService: 將數據封裝到Parcel類型;

IPC.writeTransactionData:將數據封裝到binder_transaction_data結構體;

IPC.talkWithDriver:將數據進一步封裝到binder_write_read結構體;

再通過ioctl()寫入命令BINDER_WRITE_READ和binder_write_read結構體到驅動層

binder_transaction: 將發起端數據拷貝到接收端進程的buffer結構體;

圖(右)說明:

binder_thread_read:根據binder_transaction結構體和binder_buffer結構體數據生成新的binder_transaction_data結構體,寫入bwr的write_buffer,傳遞到用戶空間。

IPC.executeCommand: 解析binder_transaction_data數據,找到目標BBinder并調用其transact()方法;

AMN.onTransact: 解析Parcel數據,然后調用目標服務的目標方法;

AMS.startService: 層層封裝和拆分后,執行真正的業務邏輯。


四、Binder內存機制

在上一篇文章從代碼角度闡釋了binder_mmap(),這也是Binder進程間通信效率高的核心機制所在,如下圖:


虛擬進程地址空間(vm_area_struct)和虛擬內核地址空間(vm_struct)都映射到同一塊物理內存空間。當Client端與Server端發送數據時,Client(作為數據發送端)先從自己的進程空間把IPC通信數據copy_from_user拷貝到內核空間,而Server端(作為數據接收端)與內核共享數據,不再需要拷貝數據,而是通過內存地址空間的偏移量,即可獲悉內存地址,整個過程只發生一次內存拷貝。一般地做法,需要Client端進程空間拷貝到內核空間,再由內核空間拷貝到Server進程空間,會發生兩次拷貝。

對于進程和內核虛擬地址映射到同一個物理內存的操作是發生在數據接收端,而數據發送端還是需要將用戶態的數據復制到內核態。到此,可能有讀者會好奇,為何不直接讓發送端和接收端直接映射到同一個物理空間,那樣就連一次復制的操作都不需要了,0次復制操作那就與Linux標準內核的共享內存的IPC機制沒有區別了,對于共享內存雖然效率高,但是對于多進程的同步問題比較復雜,而管道/消息隊列等IPC需要復制2兩次,效率較低。這里就不先展開討論Linux現有的各種IPC機制跟Binder的詳細對比,總之Android選擇Binder的基于速度和安全性的考慮。

下面這圖是從Binder在進程間數據通信的流程圖,從圖中更能明了Binder的內存轉移關系。


?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容