日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android Binder设计与实现 - 实现篇(1)

發布時間:2025/3/15 Android 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Binder设计与实现 - 实现篇(1) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文屬于原創作品,轉載請注明出處并放于明顯位置,原文地址:http://www.cnblogs.com/albert1017/p/3849585.html?

前言

? ? ? 在學習Android的Binder機制時,看了http://blog.csdn.net/universus/article/details/6211589這篇文章(讀本文前最好讀一下),覺得寫得非常棒,可惜只有設計篇,都幾年了還沒有實現篇,就想嘗試完成這個工作,雖然可能沒有universus寫得那么好,但也希望能對同在學習Android的Binder機制的人有所幫助。同時如果文中如果有什么理解錯誤請及時指出,歡迎大家交流。

? ? ? 可以參考的資料:楊豐盛老師的《Android技術內幕》,鄧平凡老師的《深入理解Android》以及羅升陽老師的相關博客或書籍(http://blog.csdn.net/luoshengyang/article)中關于Binder的部分。

1、概述

? ? ? Android中基于binder的IPC,其本質都是通過文件操作與binder驅動交互實現的。在通信過程中,每個參與通信的進程都會在內核存在對應的數據,這些數據是進程在和驅動打交道,如執行fopen或者ioctl時,由binder驅動創建與維護。當然,與binder驅動直接交互實現IPC比較麻煩,故Android幫我們封裝成了Binder Adapter,主要包括IPCThreadState和ProcessState相關的部分。我們平時的Service都是通過Binder Adapter層間接操作驅動的。我們首先拋開Binder Adapter層,看直接與binder驅動交互需要怎么做,然后在這個基礎上分析Android是怎么通過Binder Adapter層幫我們封裝的。

? ? ? 同時,我們說binder通信是一種支持CS架構的IPC,是因為binder從驅動級別就保存有一個進程是否處于循環監聽狀態,因為CS架構的基本邏輯就是Server處于循環監聽狀態,等待Client的請求并響應其請求,另外在Binder中還有類似于“會話”的概念,實現同步通信,在驅動層對一次會話進行了支持,這也是一種對CS架構的支持。

? ? ? 當然,我們還得意識到,Android的binder機制在應用層看到到的是RPC機制,即應用層的業務邏輯已經與IPC綁定了,要在IPC的基礎上實現RPC,那么在客戶端調用遠程方法后,Binder機制會將該方法轉換為對應的約定編號,再加上參數,作為IPC傳遞的內容,服務器端收到IPC消息后,將獲取函數參數,同時按約定將編號轉化為對應的服務端方法的調用,然后再將調用方法的結果轉化為IPC的通信內容,然后傳遞給客戶端,從而實現RPC。

? ? ?到了RPC層其實就和每個Service的業務邏輯掛鉤了,我們先拋開業務邏輯,只關注IPC通信的過程,看看底層數據是怎么通過binder驅動傳遞的,然后在我們搞清楚這寫機制以后,再看Android是如何在IPC通信的基礎上實現RPC的。

? ? ?我們可以將網絡七層協議模型或者TCP/IP參考模型部分與這里的Binder機制進行對比理解,我們可以認為共享內存實現了物理層,從物理上解決傳輸問題;而通過binder_node實現了網絡層,handle就好比IP地址,通過handle來從客戶端進程找到對應的服務端進程,就好比從一個IP發數據包然后找到對應的目的IP,當然,由于客戶端直接記住IP地址有困難,就發明了DNS,查詢URL轉換為IP地址來訪問,而binder中ServiceManager就是DNS服務器,實現服務名稱到對應handle的轉換,再往上就是RPC調用邏輯了,這就與網絡模型中的應用層對應了,實現了一個具體服務的遠程調用。

2、binder相關的內存模型

圖1

? ? ? 如圖1,每個使用Binder的進程,在內核空間中都存在binder_proc、binder_buffer資源,每個線程還存在個binder_thread與之對應,進程中會存在binder通信使用的用戶內存空間塊,它將與該進程對應的binder_buffer映射同一塊內存(對binder設備的fd執行mmap函數就能完成這個共享內存的映射),這就是Binder通信只拷貝一次的根本原因。binder_proc和binder_thread分別保存對應的進程、線程相關的信息。

? ? ?另外解釋一下幾個概念:

? ? ? 用戶空間的binder本地對象:就是繼承了IBinder類的那些子類的對象,如AudioFlinger類型的對象、MediaPlayerService類型的對象;

? ? ? 我們可以認為上圖的內核空間中,驅動代碼屬于代碼段,其余部分屬于數據段,而用戶空間進程通過系統調用調用驅動代碼來操作數據段中的內容。

3、直接與Binder驅動交互實現IPC

3.1、ServiceManager需要進行的操作

//打開binder設備 1、 bs->fd = open("/dev/binder", O_RDWR);//bs定義struct binder_state *bs;//進行內存映射 2、 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);//設置本進程為ServiceManager進程 3、 ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);unsigned readbuf[N];//在本用戶進程空間開辟一段內存//將BC_ENTER_LOOPER填充至bwrreadbuf[0] = BC_ENTER_LOOPER;bwr.write_buffer = readbuf;//通知binder驅動本線程進入循環等待的loop狀態,線程對應的binder_thread中的looper值也對應改變 4、 ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//bwr為binder_write_read類型,裝載的命令為BC_ENTER_LOOPER//進入循環,等待讀binder消息,當有消息讀到時,解析readbuf中返回的結果for (;;) { bwr.read_size = sizeof(readbuf); //需要讀的大小 bwr.read_consumed = 0; bwr.read_buffer = (unsigned) readbuf; //將進程內存空間傳過去 5、 ioctl(bs->fd, BINDER_WRITE_READ, &bwr); //操作驅動讀取消息//解析readbuf中返回的結果uint32_t cmd = *readbuf++;switch(cmd) {//判斷結果中的命令類型 ...case BR_TRANSACTION: {//處理其中的結果//關鍵的關鍵:readbuf后面的數據中包含一個進程內存空間的指針,//它是與本進程通信的那個遠端進程傳過來的數據在本進程對應的內核內存空間的地址,進行轉換得到,//轉換是在kernel的drivers/staging/android/binder.c中://tr.data.ptr.buffer = (void *)t->buffer->data + proc->user_buffer_offset;//其中user_buffer_offset是在建立進程的binder_proc時計算出的內核空間地址與本用戶進程空間地址的偏移量 ...}...}}

? ? ?沒錯,本質上我們就只需要執行上面的5個步驟就可以使ServiceManager跑起來,下面說一下每個步驟執行后的結果:

? ? ?1語句執行后,內核中將創建本進程對應的創建對應binder_proc;

? ? ?2語句執行后,將創建本進程在內核中使用的buffer,即上面內核模型中的內核空間塊,同時將其映射到用戶內存空間塊;

? ? ?3語句執行后,由于是第一次調用ioctl,在執行binder_get_thread(proc)語句時,將為本線程創建對應的binder_thread結構體保存在內核,同時binder驅動將為ServiceManager建立?binder_node對象;并將binder_node賦值給binder_context_mgr_node,同時記住進程的PID,從而設置其為binder的ServiceManager進程;

? ? ?4語句執行后,binder驅動將獲知本進程已經進入循環監聽等待狀態,binder驅動中對應的線程信息結構體的表線程狀態的looper變量也對應修改;

? ? ?5語句就是ServiceManager進入循環,不斷監聽客戶端的請求,沒有請求時ioctl將阻塞,有請求到達是就分析請求類型,做出相應的處理。

3.2、普通Service需要進行的操作

1、 fd = open("/dev/binder", O_RDWR);//打開Binder驅動//binder驅動將返回本驅動的版本號,由于是第一次調用ioctl,在執行binder_get_thread(proc)語句時,將為本線程創建對應的binder_thread結構體保存在內核 2、 ioctl(fd, BINDER_VERSION, &vers); //通知binder驅動本服務最多可同時啟動接收binder消息的線程數 3、 ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);//進行內存映射 4、 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);//接下來就可以通過往bwr中填入一定的內容實現與其他進程的通信了//這個地方是作為客戶端向ServiceManager注冊服務,下面將詳細分析這里傳輸的bwr 構造注冊到ServiceManager的binder_write_read的數據結構變量bwr ... 5、 ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr);//讀取結果while(true){//剛才的一次寫操作會出發多次讀操作 6、 ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr);}

? ? ? bwr就是用戶進程向binder驅動傳遞的數據,此處的bwr內容如下圖2所示:(bwr的內容是通過讀取源碼時總結出來的,就沒貼出分析源碼的過程了,有興趣的可以結合圖2分析bwr的構建過程,這樣會更容易閱讀)

圖2

? ? ? bwr中的write_buffer指向mOut包裝的結構,而mOut的buffer將指向真正傳遞的數據,而本次ioctl的目的是注冊服務,所以本次傳遞的有效數據中包含了一個flat_binder_object類型對象(圖中畫了兩個flat_binder_object是為了便于理解offsets的概念),它是用戶進程中的待注冊的Service的類型(它是繼承自BBinder),即BBinder類型的對象,而如果IPC傳遞的實際數據內容中包含binder對象時,binder驅動需要知道數據中包含binder對象,并在驅動中將這個binder對象做一些處理,而接收進程接收到的binder對象就是被驅動修改后,在接收進程中也可以使用的binder。

? ? ? 5語句執行后:

? ? ? (1)首先,由于傳入內核的bwr的有效負載數據中,存在bind本地對象的封裝flat_binder_object對象,內核驅動會拿出這個flat_binder_object對象,然后以flat_binder_object對象的binder成員值(為binder本地對象類型的指針類型,如AudioFlinger對象指針)查找本進程對應的內核數據結構binder_proc中的nodes紅黑樹,查找不到就為binder本地對象創建對應的內核數據結構binder_node(見:/drivers/staging/android/binder.c的1623行及其后面的代碼),我們這里記它為snode,并找到ServiceManager的binder_node節點binder_context_mgr_node,在其中建立snode的引用。然后驅動會在內核中創建一個binder_transaction類型的事務,填充數據目標進程相關信息等,將binder_transaction的binder_work類型成員變量work(work.type=BINDER_WORK_TRANSACTION)加入到ServiceManager的todo隊列,讓ServiceManager去處理,這樣會喚醒正在讀等待的ServiceManager,而本進程會執行6語句,進入休眠狀態等待ServiceManager的回復。

? ? ? (2)ServiceManager就會從其binder_proc結構的todo隊列中讀取剛才放進去的binder_work,并由這個成員變量找到binder_transaction事務類型變量(記為t),然后創建binder_transaction_data類型變量(記為tr),填充t的內容到tr中,這個過程中也會將t中的指向內核數據的指針轉換為tr中指向ServiceManager進程的用戶空間的指針(tr.data.ptr.buffer = (void *)t->buffer->data + proc->user_buffer_offset),然后在內核創建的bwr的read_buffer中先填入BR_TRANSACTION這個cmd,再填入tr部分,另外這個地方還會把t壓入ServiceManager本線程binder_thread的transaction_stack的棧頂,以等待用戶空間的代碼處理完畢后向返回驅動結果。然后返回ioctl處,將內核的bwr拷貝回用戶空間,將ServiceManager的線程的looper的BINDER_LOOPER_STATE_NEED_RETURN標準去掉,然后就返回到用戶空間的binder_loop的調用ioctrl處。后面就是在ServiceManager的用戶空間中處理業務邏輯相關的問題了。ServiceManager會將服務端傳過來的Binder本地對象的封裝flat_binder_object的相關信息寫到一個struct svcinfo結構體中,包括它的名稱和句柄值等,然后插入到ServiceManager維護的svclist鏈表中去。

? ? ?第5句調用ioctl后內核發生的代碼調用有些繁瑣,有興趣的可以看一下,另外也可以看http://blog.csdn.net/luoshengyang/article/details/6629298的分析,分析代碼如下:

?View Code

? ? ? 到第5步時,我們做的操作應該和普通客戶端調用服務器端的操作是相同的,我們構造了BBinder的子類對象,即本服務對應的實現(如MediaService的實現就是MediaPlayerService類)類的對象加入到bwr中,同時bwr中的加入類型為BINDER_TYPE_BINDER的命令,當然,這個對象的載體是flat_binder_object類型對象,同時handle值也會被包裝傳入至binder驅動中去,binder驅動將根據這個handle值找到內核空間對應的binder_node對象,而這個對象保存了對應服務端的進程信息,這樣,就找到了對應服務端的進程信息,找到服務端在內存中擁有的內核空間塊,然后將客戶端傳來的數據拷貝到這個內核空間塊,而由于服務端處于循環監聽的阻塞狀態,這時它就將停止阻塞,按照前面說的將內核內存塊對應的地址轉換為對應用戶空間服務端進程內存地址(tr.data.ptr.buffer = (void *)t->buffer->data + proc->user_buffer_offset,即內核空間地址+內核空間地址與用戶空間地址的偏移量),并將其作為參數返回用戶空間的服務進程,服務進程根據返回參數讀取剛才從另一個進程拷貝進來內核空間的那部分數據,這就實現了IPC。

? ? ? 這里是服務端作為ServiceManager的客戶端,通過IPC向ServiceManager發送注冊服務請求,讓ServiceManager幫這個服務端記住它在binder驅動擁有的binder_node的handle值,而其他客戶端想訪問本服務端時,就先通過IPC向ServiceManager查詢需要服務的handle值,然后拿著這個handle值傳入binder驅動,binder驅動再根據handle值查詢對應服務器進程信息,拷貝對應信息到服務進程的內核內存塊,完成傳遞。

? ? ? 整個傳遞過程可以描述為圖3所示,可以結合圖2理解:

圖3?客戶端使用BC_TRANSACTION命令向服務端傳數據的過程

? ? ? 需要注意的是,圖中的兩個binder_transaction結構是一個對象,其余的同顏色的都只是一個類型,是不同的兩個對象。

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的Android Binder设计与实现 - 实现篇(1)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。