前言
Binder可以說是整個Android框架最重要的一個基礎。如果不能吃透Binder,就談不上對Android有多么深刻的理解。這個道理相信大部分Android開發者都清楚。但是Binder整個框架又看起來好像深不可測,上至Java,下至kernel。又橫跨眾多進程和服務。想看看源代碼來學習。看著看著就看不明白了,不由得發出“我是誰?我在哪里?”這樣的疑問。這篇文章就想從作者個人的經驗來聊聊如何學習Binder。
Binder的學習
Binder的學習和學習其他源代碼一樣并沒有什么特殊的訣竅。那就是硬著頭皮看源碼,看文章。反復的看,不停的想,至少把妨礙自己理解的堵塞點都搞懂了,整個鏈路從上到下,從下到上都無障礙的跑通了那才算真正掌握了Binder。當然這也是學習所有其他框架源碼的要求。關于學習源碼的更多方法,可以參考我的另一篇文章我是如何學習Flutter源碼的。
源碼浩如煙海,看著看著就不知道跑哪里去了,沒有關系??梢詤⒖记叭说慕涷瀬頌樽约簬?。然而互聯網上說Binder的文章可謂多如牛毛。如何選擇呢?這里我推薦一位大牛的Binder系列文章。一遍看不懂那就多看幾遍,而且要結合Android源碼去看。文章里的每一個代碼片段最好都自己去源碼里對照著研究。這個一是為了加深記憶,二是為了促進消化。
如果全搞明白了,那么恭喜你,這篇文章剩下的內容不用再看了。如果是那種剛看完覺得自己都會了,過一段時間別人問你你又說不出來個子丑寅卯。這種情況是因為你只是死記硬背記住了關于Binder的一些東西而沒有完全理解Binder,有一些關于Binder通信的關鍵點沒有搞清楚,知其然而不知其所以然。處于“半懂不懂”的狀態。如果此時放棄學習Binder那就太可惜了。
寫這篇文章呢,就是希望能用盡量少的搬運源碼的方式來捋一下Binder。希望能幫你疏通堵點,徹底理解Binder。
正如我在前言里說的,Binder深不可測,上至Java,下至kernel。而程序員似乎會有個習慣,不太喜歡去挖自己領域之外的東西,喜歡Java的就不想去看C/C++的東西,搞APP的,搞Framework的不想去瞅一眼kernel里面長啥樣?!拔抑灰肋@東西API是啥,會用就行了。。?!?。某些情況下這可能是沒什么問題的。但不適用于Binder。
要徹底理解Binder,必須要理解Binder驅動(Driver)。
很多對Binder一知半解的同學通常都是卡在這個地方了,不是說硬記住那幾個“BC_TRANSACTION”,“BR_REPLY”命令,然后交互流程是啥樣就可以了。但如果此時你把這些東西也忘了,這篇文章也就不用往下看了,這邊建議你回頭把上面說的Binder系列文章擼幾遍再來。
如果你現在處于“半懂不懂”的狀態,那么接著看下面的章節能不能幫到你來理解Binder。
Binder的理解
本章節假設讀者對Binder架構已經有了初步的了解。所以并不會很詳細的搬源碼。大部分東西都會直接拿來用。涉及驅動的部分也不會做詳盡說明,只會把一些關鍵點串起來。文中也不會涉及Java Binder。Java Binder在這里完全是干擾。
概述
這里首先列一下Binder中的一些內容:
- 除了
service_manager
,其他應用級進程/線程,和Binder驅動打交道的都是IPCThreadState
。 - 我們只關注進程間通信的核心流程:
transaction
。 - 傳輸發起者需要告訴Binder三個東西:
handle
(你找誰?),cmd
(你要干啥?)和data
(東西呢?)。 - 在客戶端
handle
由BpBinder
持有。不管什么樣的服務在Binder眼里就是一串數字。記住這一點。 - 在server端真正的服務是
BBinder
來完成的。
error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
&reply, tr.flags);
- 顯然Binder驅動幫我們完成了從本地的一串數字
handle
到遠端實體服務BBinder
的轉換。
這里就引出了幾個問題:
- 本地
handle
是如何轉換為遠端BBinder
的? - 這個
handle
是從哪里來的呢?
第二個問題可能有些同學會說是從service_manager
那里查來的,然而并沒有這么簡單。這個問題我們稍后再解釋。這里先假設我們已經有了這個handle
來說明第一個問題。
Binder驅動傳輸流程
這個流程其實就只有三步:第一,客戶端把數據寫進來。第二,把數據從客戶端傳到服務端。第三,通知服務端把數據讀走。
這個流程我希望大家能緊盯著handle
因為它是我們理解問題1的唯一線索。
客戶端把數據寫進來
在用戶空間,函數IPCThreadState::writeTransactionData()
負責寫入驅動。
這個函數會構造一個binder_transaction_data
的結構體tr
。我們的handle
會被寫進去。
status_t IPCThreadState::writeTransactionData(...
int32_t handle, ...)
{
binder_transaction_data tr;
...
// handle在這里
tr.target.handle = handle;
tr.code = code;
...
}
然后就到了驅動那里
static int binder_thread_write(...) {
...
//我們只關注BC_TRANSACTION
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
//tr到了這里
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
//這個函數做傳輸工作
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}
...
}
寫進來的可能是各種命令,我們只關注transaction
。
把數據從客戶端傳到服務端
這事是由函數binder_transaction()
來干的。注意看一下那幾個入參,proc
代表客戶端進程。thread
代表客戶端線程,tr
呢就是最開始的那個,記住里面放著handle
。
傳輸的前提是要找到目標的進程\線程。而我們唯一的線索就是handle
。怎么通過handle
來獲得目標進程\線程呢?
static void binder_transaction(...) {
...
if (tr->target.handle) {
//通過handle查找目標引用
target_ref = binder_get_ref(proc, tr->target.handle,
true);
//通過目標引用得到目標節點。
target_node = target_ref->node;
}
...
}
//從node里拿到目標進程的信息
target_proc = target_node->proc;
...
//然后搞了個binder_transaction的結構體
t = kzalloc(sizeof(*t), GFP_KERNEL);
...
//設置目標進程/線程
t->to_proc = target_proc;
t->to_thread = target_thread;
...
//設置目標節點
t->buffer->target_node = target_node;
...
//要干的活是傳輸
t->work.type = BINDER_WORK_TRANSACTION;
...
//把傳輸任務插入到目標進程/線程的todo隊列。
binder_proc_transaction(t, target_proc, target_thread);
...
把數據從客戶端傳到服務端階段就完成了。從上述關鍵代碼我們可以看到,要完成傳輸就需要拿到目標進程的信息。而我們只有handle
。驅動會先拿這個handle
在自身進程信息proc
內查找節點的應用,然后由這個引用就能得到節點,得到節點就能拿到目標進程的信息target_proc
了。那么ref
是啥?node
又是啥呢?為什么他們能通過handle
找的到呢?
node
可以說是Binder驅動的核心數據結構。node
會以紅黑樹的形式保存在服務端進程結構體內,客戶端則保存的是它的引用ref
。而handle
來自ref->desc
。
node
和ref
是什么時候保存到Binder驅動中的呢?這個我們在后面解決handle
從哪里來的問題再解釋。
所以在傳輸階段我們看到handle
換成了node
。在插入到目標進程todo
隊列的數據里只有node
而沒有handle
。handle
已經完成了它的使命,接下來讓我們關注node
。
通知服務端把數據讀走
static int binder_thread_read(...) {
...
//有活干了
w = list_first_entry(...);
...
//看看是什么活,我們只關心transaction
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
t = container_of(w, struct binder_transaction, work);
} break;
}
...
if (t->buffer->target_node) {
tr.target.ptr = target_node->ptr;
tr.cookie = target_node->cookie;
...
cmd = BR_TRANSACTION;
}
...
tr.code = t->code;
}
cmd
設置成了BR_TRANSACTION
;binder_thread_read()
會拼一個binder_transaction_data
。也就是tr
出來。它是要被送往用戶空間的,就像之前從用戶空間往binder驅動里寫的也是一個tr
一樣。這里注意驅動把node
里的兩個東西設置給了tr
。ptr
和cookie
。記住它們。
接下來就回到用戶空間了:
status_t IPCThreadState::executeCommand(int32_t cmd) {
//我們只關心傳輸
case BR_TRANSACTION:
{
if (tr.target.ptr) {
if (reinterpret_cast<RefBase::weakref_type*>(
tr.target.ptr)->attemptIncStrong(this)) {
error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
&reply, tr.flags);
reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
}
}
break;
}
啊哈,這里我們就能看出來原來cookie
的真身就是BBinder
。也就是我們服務的真身了啊。
所以捋一下Binder驅動的傳輸過程就是從客戶端的handle
在自身進程信息proc
里查找ref
。再通過ref
就能得到node
。而node
則是存儲在服務端進程信息里的。node
就很關鍵了,從它身上能找到目標進程的信息和目標服務的真身BBinder
實例。然后回到用戶區直接調用就好了。
傳輸過程就介紹完了,接下來我們還有解決另外一個系列問題,handle
從從哪里來?node
又是什么時候給保存到內核里去的呢?
對了,這就涉及到services_manager
了。
服務注冊及獲取
我們知道services_manager
是個特殊的服務。有點像個DNS服務器,其他什么AMS,PMS等等都需要先到它那里注冊以后,客戶們才能在services_manager
找到他們。既然services_manager
這么特殊,那它在驅動中肯定會有個獨一無二的node
吧。
所以services_manager
啟動的時候會給Binder驅動發這么個命令BINDER_SET_CONTEXT_MGR
。
Binder驅動照辦:
static int binder_ioctl_set_ctx_mgr(struct file *filp)
{
...
temp = binder_new_node(proc, 0, 0);
...
context->binder_context_mgr_node = temp;
binder_put_node(temp);
...
}
函數binder_new_node()
是用來新建node
的
static struct binder_node *binder_new_node(struct binder_proc *proc,
binder_uintptr_t ptr,
binder_uintptr_t cookie)
三個入參,proc
,ptr
和cookie
?;叵胍幌律厦娼榻B傳輸過程的時候這三個參數是不是都用到過?當然,對于services_manager
來說只需要進程信息,其他兩個入參都是0。這個特殊的node
被設置給context->binder_context_mgr_node
。
接下來讓我們看一下服務注冊過程:
注冊服務
注冊服務的要點是盯住服務實體Binder是如何轉化為node
的。
我們都知道service_manager
特殊,特殊就特殊在它的handle
就是0。所以無論是注冊服務還是獲取服務,我們不需要去別處問,自己就可以搞出來個代表service_manager
的BpBinder
。注冊的時候是這樣的
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated, int dumpsysPriority) {
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
data.writeInt32(dumpsysPriority);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
注意這句data.writeStrongBinder(service);
也就是說把服務自身給寫到要傳輸的數據里面去了。
寫成啥樣了呢?
status_t flatten_binder(){
...
flat_binder_object obj;
...
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
const int32_t handle = proxy ? proxy->handle() : 0;
obj.hdr.type = BINDER_TYPE_HANDLE;
obj.binder = 0;
obj.handle = handle;
obj.cookie = 0;
} else {
obj.hdr.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local);
}
...
}
因為這里是注冊服務,所以給的Binder是實體Binder,走的是else
分支。類型為BINDER_TYPE_BINDER
,cookie
設置為service
實體。
注冊服務是一次handle
為0的Binder傳輸?;叵胛覀冎坝懻摰膫鬏斶^程,handle
如果是0的話會在傳輸過程走不一樣的分支:
static void binder_transaction(...) {
...
if (tr->target.handle) {
...
} else {
target_node = context->binder_context_mgr_node;
}
//從node里拿到目標進程的信息
target_proc = target_node->proc;
...
switch (hdr->type) {
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_binder(fp, t, thread);
} break;
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_handle(fp, t, thread);
} break;
...
//把傳輸任務插入到目標進程/線程的todo隊列。
binder_proc_transaction(t, target_proc, target_thread);
...
對于注冊服務的流程來說在傳輸的時候有兩個地方需要注意,
- 由于
handle
是0,所以直接會找到binder_context_mgr_node
。這個node
也就是我們之前看到的service_manager
初始化的時候創建的,也就是說我們直接就扎到了service_manager
。 - 還記得我們之前把服務實體放在傳輸數據里面了嗎?這里驅動會檢查傳輸的數據,如果發現有
BINDER_TYPE_XXX
類型的都要做轉換,為啥要轉呢?因為我的實體到你那里就得是個handle
啊, 否則都不在同一個進程,不轉換的話你拿我的實體也沒用啊。所以,這里我們的服務實體要做一次轉換了:
static int binder_translate_binder(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
struct binder_node *node;
struct binder_ref *ref;
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
node = binder_get_node(proc, fp->binder);
if (!node) {
node = binder_new_node(proc, fp->binder, fp->cookie);
}
ref = binder_get_ref_for_node(target_proc, node, &thread->todo);
if (fp->hdr.type == BINDER_TYPE_BINDER)
fp->hdr.type = BINDER_TYPE_HANDLE;
else
fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
fp->binder = 0;
fp->handle = ref->desc;
fp->cookie = 0;
...
}
注意看這里的轉換,首先是在自己的node
樹里面找有沒有實體服務Binder對應的node
。這里因為我們是要注冊服務,所以node
不存在,需要新建一個。你看至此我們的新服務已經有了自己的node
了。然后呢會在目標進程信息,也就是service_manager
中找這個node
的引用,沒有的話會新建一個引用的,這個過程會產生一個數放在ref->desc
。對了,這個數就是我們的新服務在service_manager
那邊的handle
了。最后就是對原始數據做變身了。類型從BINDER
變為HANDLE
。清空binder
和cookie
。給handle
字段賦值,我們的實體Binder就這樣華麗的變成自己的分身了。
千萬不要搞混了,這里做轉換的是我們要注冊的服務,完全是處在事務數據中。而傳輸這些事務數據的是handle為0,對應node是binder_context_mgr_node的事務。
然后就是service_manager
那邊讀取傳輸的數據。拿到那個轉換以后的fp->handle
和服務的名字一起保存起來。
總體而言注冊服務:
- 是一次對
service_manager
的傳輸,只不過需要在開始的時候把服務實體Binder放在要傳輸的數據里。 - Binder驅動會在傳輸過程中做一次從實體服務Binder到對應
handle
的變換。 - 在這個變換的過程中會創建實體服務Binder對應的
node
。
接下來我們看一下獲取服務的過程。貌似應該已經一目了然了??蛻舳四弥彰秩?code>service_manager那里去請求handle
。拿到以后就可以實例化BpBinder
去調用服務了。
獲取服務
獲取服務的過程分兩段,一個是把服務名字傳過去,另一個是把handle
傳回來。去程沒啥好說的,和上面的注冊服務過程差不多,還少了中間做轉換的步驟。回程就是不一樣的操作了。service_manager
把查到的handle
通過BC_REPLY
給送回來的。別看handle
只是個數,在傳輸的時候必須把它包裝一下,變成這樣的:
obj->hdr.type = BINDER_TYPE_HANDLE;
obj->handle = handle;
obj->cookie = 0;
類型是BINDER_TYPE_HANDLE
。
這個數據結構會寫入Binder驅動,命令BC_REPLY
會走到和BC_TRANSACTION
一樣的函數,也就是binder_transaction()
,但是某些分支有有所不同。
binder_transaction(...){
if (reply) {
in_reply_to = thread->transaction_stack;
target_thread = in_reply_to->from;
target_proc = target_thread->proc;
}
...
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_handle(fp, t, thread);
} break;
...
}
可見返回過程不需要再查找node
因為調用端是誰是清楚的。直接把from
變成target
就行了。
因為返回數據塊里面放著獲取到的服務handle
。同樣的這里會給它做個變換。
看一下binder_translate_handle()
如何做變換:
static int binder_translate_handle(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
struct binder_ref *ref;
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
ref = binder_get_ref(proc, fp->handle,
fp->hdr.type == BINDER_TYPE_HANDLE);
if (ref->node->proc == target_proc) {
if (fp->hdr.type == BINDER_TYPE_HANDLE)
fp->hdr.type = BINDER_TYPE_BINDER;
else
fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
fp->binder = ref->node->ptr;
fp->cookie = ref->node->cookie;
} else {
struct binder_ref *new_ref;
new_ref = binder_get_ref_for_node(target_proc, ref->node, NULL);
fp->binder = 0;
fp->handle = new_ref->desc;
fp->cookie = 0;
}
}
這里的轉換有兩個分支,如果此服務進程和目標進程相同,則將handle
轉換成實體Binder。也就是說你去service_manager
那里獲取到了和你同一個進程的服務,自然可以轉換為實體Binder直接拿來用了。
如果不是同一個進程呢?則會到目標進程信息那里去獲取一個新的handle
。替換掉原來的handle
返回給調用者。
為什么會這樣呢?這就需要深入了解node
和ref
的關系。
-
node
實體只保存在創建者進程所對應的proc
中。 -
ref
是保存在當前進程中對外部node
實體的引用,不同的進程對同一個node
的ref
是不同的。 - 所以可以認為
handle
是進程私有的,不要想當然的認為從service_manager
拿到的handle
就是和自己進程拿到的一樣。
總結
Binder雖然結構復雜,難以全面掌握。但有時又是Android開發者在實際工作中不得不面對的問題。掌握Binder需要投入大量精力和努力。通過一些文章的指引,是可以在學習Binder的路途上減輕一些壓力,避免一些誤區的。本文就希望能幫助開發者打通一些在學習Binder時可能會遇到的阻塞點,特別是這些阻塞點基本上會存在于Binder驅動層。畢竟樓再高,蓋起來還是要先從地基打起,Binder驅動就是Binder這座大廈的基礎。打好這個基礎,往上的就都是包裹和通道了,理解起來也就會比較輕松。希望能對大家有所幫助。