深入理解Binder通訊原理及面試問題
Binder承擔了絕大部分Android程序通訊的職責,可以看做是Android的血管系統,負責不同服務模組程序間的通訊。在對Binder的理解上,可大可小,日常APP開發並不怎麼涉及Binder通訊知識,最多就是Service及AIDL的使用會涉及部分Binder知識。Binder往小了說可總結成一句話:一種IPC程序間通訊方式,負責程序A的資料,傳送到程序B。往大了說,其實涉及的知識還是很多的,如Binder驅動、Android中對於Binder驅動的擴充套件、Zygote程序孵化中對於Binder通訊的支援、Java層Binder封裝,Native層Binder通訊的封裝等等,Binder訃告機制等等。網上很多分析Binder框架的文章,一般都是從ServiceManager、Binder驅動、addService、getService來分析等來分析,其實這些主要是針對系統提供的服務,跟我們常見的bindService還是有一定區別的,這些文章一般分析的都很詳細,而且篇幅都很大,缺點就是看的太多,整體下來抓不住一些關鍵的點,看完了,當時能理解個大概,過段時間,基本忘乾淨了。本篇文章主要簡述一些Binder難以理解的點,但不會太細的跟蹤分析,只拋磚,自己去發掘玉:
- Binder的定向制導,如何找到目標Binder,喚起程序或者執行緒
- Binder中的紅黑樹,為什麼會有兩棵binder_ref紅黑樹
- Binder一次拷貝原理(直接拷貝到目標執行緒的核心空間,核心空間與使用者空間對應)
- Binder傳輸資料的大小限制(核心4M 上層限制1m-8k),傳輸Bitmap過大,就會崩潰的原因,Activity之間傳輸BitMap
- 系統服務與bindService等啟動的服務的區別
- Binder執行緒、Binder主執行緒、Client請求執行緒的概念與區別
- Client是同步而Server是非同步到底說的什麼
- Android APP程序天生支援Binder通訊的原理是什麼
- Android APP有多少Binder執行緒,是固定的麼
- Binder執行緒的睡眠與喚醒(請求執行緒睡在哪個等待佇列上,喚醒目標端哪個佇列上的執行緒)
- Binder協議中BC與BR的區別
- Binder在傳輸資料的時候是如何層層封裝的–不同層次使用的資料結構(命令的封裝)
- Binder驅動傳遞資料的釋放(釋放時機)
- 一個簡單的Binder通訊C/S模型
- ServiceManager addService的限制(並非服務都能使用ServiceManager的addService)
- bindService啟動Service與Binder服務實體的流程
- Java層Binder實體與與BinderProxy是如何例項化及使用的,與Native層的關係是怎樣的
- Parcel readStrongBinder與writeStrongBinder的原理
Binder如何精確制導,找到目標Binder實體,並喚醒程序或者執行緒
Binder實體服務其實有兩種,一是通過addService註冊到ServiceManager中的服務,比如ActvityManagerService、PackageManagerService、PowerManagerService等,一般都是系統服務;還有一種是通過bindServcice拉起的一些服務,一般是開發者自己實現的服務。這裡先看通過addService新增的被ServiceManager所管理的服務。有很多分析ServiceManager的文章,本文不分析ServiceManager,只是簡單提一下,ServiceManager是比較特殊的服務,所有應用都能直接使用,因為ServiceManager對於Client端來說Handle控制代碼是固定的,都是0,所以ServiceManager服務並不需要查詢,可以直接使用。
理解Binder定向制導的關鍵是理解Binder的四棵紅黑樹,先看一下binder_proc結構體,在它內部有四棵紅黑樹,threads,nodes,refs_by_desc,refs_by_node,nodes就是Binder實體在核心中對應的資料結構,binder_node裡記錄程序相關的binder_proc,還有Binder實體自身的地址等資訊,nodes紅黑樹位於binder_proc,可以知道Binder實體其實是程序內可見,而不是執行緒內。
struct binder_proc {
struct hlist_node proc_node;
struct rb_root threads;
struct rb_root nodes;
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
。。。
struct list_head todo;
wait_queue_head_t wait;
。。。
};
現在假設存在一堆Client與Service,Client如何才能訪問Service呢?首先Service會通過addService將binder實體註冊到ServiceManager中去,Client如果想要使用Servcie,就需要通過getService向ServiceManager請求該服務。在Service通過addService向ServiceManager註冊的時候,ServiceManager會將服務相關的資訊儲存到自己程序的Service列表中去,同時在ServiceManager程序的binder_ref紅黑樹中為Service新增binder_ref節點,這樣ServiceManager就能獲取Service的Binder實體資訊。而當Client通過getService向ServiceManager請求該Service服務的時候,ServiceManager會在註冊的Service列表中查詢該服務,如果找到就將該服務返回給Client,在這個過程中,ServiceManager會在Client程序的binder_ref紅黑樹中新增binder_ref節點,可見本程序中的binder_ref紅黑樹節點都不是本程序自己建立的,要麼是Service程序將binder_ref插入到ServiceManager中去,要麼是ServiceManager程序將binder_ref插入到Client中去。之後,Client就能通過Handle控制代碼獲取binder_ref,進而訪問Service服務。
getService之後,便可以獲取binder_ref引用,進而獲取到binder_proc與binder_node資訊,之後Client便可有目的的將binder_transaction事務插入到binder_proc的待處理列表,並且,如果程序正在睡眠,就喚起程序,其實這裡到底是喚起程序還是執行緒也有講究,對於Client向Service傳送請求的狀況,一般都是喚醒binder_proc上睡眠的執行緒:
struct binder_ref {
int debug_id;
struct rb_node rb_node_desc;
struct rb_node rb_node_node;
struct hlist_node node_entry;
struct binder_proc *proc;
struct binder_node *node;
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
binder_proc為何會有兩棵binder_ref紅黑樹
binder_proc中存在兩棵binder_ref紅黑樹,其實兩棵紅黑樹中的節點是複用的,只是查詢方式不同,一個通過handle控制代碼,一個通過node節點查詢。個人理解:refs_by_node紅黑樹主要是為了
binder驅動往使用者空間寫資料所使用的,而refs_by_desc是使用者空間向Binder驅動寫資料使用的,只是方向問題。比如在服務addService的時候,binder驅動會在在ServiceManager程序的binder_proc中查詢binder_ref結構體,如果沒有就會新建binder_ref結構體,再比如在Client端getService的時候,binder驅動會在Client程序中通過 binder_get_ref_for_node為Client建立binder_ref結構體,並分配控制代碼,同時插入到refs_by_desc紅黑樹中,可見refs_by_node紅黑樹,主要是給binder驅動往使用者空間寫資料使用的。相對的refs_by_desc主要是為了使用者空間往binder驅動寫資料使用的,當用戶空間已經獲得Binder驅動為其建立的binder_ref引用控制代碼後,就可以通過binder_get_ref從refs_by_desc找到響應binder_ref,進而找到目標binder_node。可見有兩棵紅黑樹主要是區分使用物件及資料流動方向,看下面的程式碼就能理解:
// 根據32位的uint32_t desc來查詢,可以看到,binder_get_ref不會新建binder_ref節點
static struct binder_ref *binder_get_ref(struct binder_proc *proc,
uint32_t desc)
{
struct rb_node *n = proc->refs_by_desc.rb_node;
struct binder_ref *ref;
while (n) {
ref = rb_entry(n, struct binder_ref, rb_node_desc);
if (desc < ref->desc)
n = n->rb_left;
else if (desc > ref->desc)
n = n->rb_right;
else
return ref;
}
return NULL;
}
可以看到binder_get_ref並具備binder_ref的建立功能,相對應的看一下binder_get_ref_for_node,binder_get_ref_for_node紅黑樹主要通過binder_node進行查詢,如果找不到,就新建binder_ref,同時插入到兩棵紅黑樹中去
static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc,
struct binder_node *node)
{
struct rb_node *n;
struct rb_node **p = &proc->refs_by_node.rb_node;
struct rb_node *parent = NULL;
struct binder_ref *ref, *new_ref;
while (*p) {
parent = *p;
ref = rb_entry(parent, struct binder_ref, rb_node_node);
if (node < ref->node)
p = &(*p)->rb_left;
else if (node > ref->node)
p = &(*p)->rb_right;
else
return ref;
}
// binder_ref 可以在兩棵樹裡面,但是,兩棵樹的查詢方式不同,並且通過desc查詢,不具備新建功能
new_ref = kzalloc(sizeof(*ref), GFP_KERNEL);
if (new_ref == NULL)
return NULL;
binder_stats_created(BINDER_STAT_REF);
new_ref->debug_id = ++binder_last_id;
new_ref->proc = proc;
new_ref->node = node;
rb_link_node(&new_ref->rb_node_node, parent, p);
// 插入到proc->refs_by_node紅黑樹中去
rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);
// 是不是ServiceManager的
new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1;
// 分配Handle控制代碼,為了插入到refs_by_desc
for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
ref = rb_entry(n, struct binder_ref, rb_node_desc);
if (ref->desc > new_ref->desc)
break;
new_ref->desc = ref->desc + 1;
}
// 找到目標位置
p = &proc->refs_by_desc.rb_node;
while (*p) {
parent = *p;
ref = rb_entry(parent, struct binder_ref, rb_node_desc);
if (new_ref->desc < ref->desc)
p = &(*p)->rb_left;
else if (new_ref->desc > ref->desc)
p = &(*p)->rb_right;
else
BUG();
}
rb_link_node(&new_ref->rb_node_desc, parent, p);
// 插入到refs_by_desc紅黑樹中區
rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc);
if (node) {
hlist_add_head(&new_ref->node_entry, &node->refs);
binder_debug(BINDER_DEBUG_INTERNAL_REFS,
"binder: %d new ref %d desc %d for "
"node %d\n", proc->pid, new_ref->debug_id,
new_ref->desc, node->debug_id);
} else {
binder_debug(BINDER_DEBUG_INTERNAL_REFS,
"binder: %d new ref %d desc %d for "
"dead node\n", proc->pid, new_ref->debug_id,
new_ref->desc);
}
return new_ref;
}
該函式呼叫在binder_transaction函式中,其實就是在binder驅動訪問target_proc的時候,這也也很容易理解,Handle控制代碼對於跨程序沒有任何意義,程序A中的Handle,放到程序B中是無效的。
Binder一次拷貝原理
Android選擇Binder作為主要程序通訊的方式同其效能高也有關係,Binder只需要一次拷貝就能將A程序使用者空間的資料為B程序所用。這裡主要涉及兩個點:
- Binder的map函式,會將核心空間直接與使用者空間對應,使用者空間可以直接訪問核心空間的資料
A程序的資料會被直接拷貝到B程序的核心空間(一次拷貝)
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) ProcessState::ProcessState() : mDriverFD(open_driver()) , mVMStart(MAP_FAILED) , mManagesContexts(false) , mBinderContextCheckFunc(NULL) , mBinderContextUserData(NULL) , mThreadPoolStarted(false) , mThreadPoolSeq(1){ if (mDriverFD >= 0) { .... // mmap the binder, providing a chunk of virtual address space to receive transactions. mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); ... } }
mmap函式屬於系統呼叫,mmap會從當前程序中獲取使用者態可用的虛擬地址空間(vm_area_struct *vma),並在mmap_region中真正獲取vma,然後呼叫file->f_op->mmap(file, vma),進入驅動處理,之後就會在記憶體中分配一塊連續的虛擬地址空間,並預先分配好頁表、已使用的與未使用的標識、初始地址、與使用者空間的偏移等等,通過這一步之後,就能把Binder在核心空間的資料直接通過指標地址對映到使用者空間,供程序在使用者空間使用,這是一次拷貝的基礎,一次拷貝在核心中的標識如下:
struct binder_proc { struct hlist_node proc_node; // 四棵比較重要的樹 struct rb_root threads; struct rb_root nodes; struct rb_root refs_by_desc; struct rb_root refs_by_node; int pid; struct vm_area_struct *vma; //虛擬地址空間,使用者控制元件傳過來 struct mm_struct *vma_vm_mm; struct task_struct *tsk; struct files_struct *files; struct hlist_node deferred_work_node; int deferred_work; void *buffer; //初始地址 ptrdiff_t user_buffer_offset; //這裡是偏移 struct list_head buffers;//這個列表連線所有的記憶體塊,以地址的大小為順序,各記憶體塊首尾相連 struct rb_root free_buffers;//連線所有的已建立對映的虛擬記憶體塊,以記憶體的大小為index組織在以該節點為根的紅黑樹下 struct rb_root allocated_buffers;//連線所有已經分配的虛擬記憶體塊,以記憶體塊的開始地址為index組織在以該節點為根的紅黑樹下 }
上面只是在APP啟動的時候開啟的地址對映,但並未涉及到資料的拷貝,下面看資料的拷貝操作。當資料從使用者空間拷貝到核心空間的時候,是直從當前程序的使用者空間接拷貝到目標程序的核心空間,這個過程是在請求端執行緒中處理的,操作物件是目標程序的核心空間。看如下程式碼:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
...
在通過進行binder事物的傳遞時,如果一個binder事物(用struct binder_transaction結構體表示)需要使用到記憶體,
就會呼叫binder_alloc_buf函式分配此次binder事物需要的記憶體空間。
需要注意的是:這裡是從目標程序的binder記憶體空間分配所需的記憶體
//從target程序的binder記憶體空間分配所需的記憶體大小,這也是一次拷貝,完成通訊的關鍵,直接拷貝到目標程序的核心空間
//由於使用者空間跟核心空間僅僅存在一個偏移地址,所以也算拷貝到使用者空間
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
t->buffer->allow_user_free = 0;
t->buffer->debug_id = t->debug_id;
//該binder_buffer對應的事務
t->buffer->transaction = t;
//該事物對應的目標binder實體 ,因為目標程序中可能不僅僅有一個Binder實體
t->buffer->target_node = target_node;
trace_binder_transaction_alloc_buf(t->buffer);
if (target_node)
binder_inc_node(target_node, 1, 0, NULL);
// 計算出存放flat_binder_object結構體偏移陣列的起始地址,4位元組對齊。
offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
// struct flat_binder_object是binder在程序之間傳輸的表示方式 //
// 這裡就是完成binder通訊單邊時候在使用者程序同核心buffer之間的一次拷貝動作 //
// 這裡的資料拷貝,其實是拷貝到目標程序中去,因為t本身就是在目標程序的核心空間中分配的,
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
binder_user_error("binder: %d:%d got transaction with invalid "
"data ptr\n", proc->pid, thread->pid);
return_error = BR_FAILED_REPLY;
goto err_copy_data_failed;
}
可以看到binder_alloc_buf(target_proc, tr->data_size,tr->offsets_size, !reply && (t->flags & TF_ONE_WAY))函式在申請記憶體的時候,是從target_proc程序空間中去申請的,這樣在做資料拷貝的時候copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)),就會直接拷貝target_proc的核心空間,而由於Binder核心空間的資料能直接對映到使用者空間,這裡就不在需要拷貝到使用者空間。這就是一次拷貝的原理。核心空間的資料對映到使用者空間其實就是新增一個偏移地址,並且將資料的首地址、資料的大小都複製到一個使用者空間的Parcel結構體,具體可以參考Parcel.cpp的Parcel::ipcSetDataReference函式。
Binder傳輸資料的大小限制
雖然APP開發時候,Binder對程式設計師幾乎不可見,但是作為Android的資料運輸系統,Binder的影響是全面性的,所以有時候如果不瞭解Binder的一些限制,在出現問題的時候往往是沒有任何頭緒,比如在Activity之間傳輸BitMap的時候,如果Bitmap過大,就會引起問題,比如崩潰等,這其實就跟Binder傳輸資料大小的限制有關係,在上面的一次拷貝中分析過,mmap函式會為Binder資料傳遞對映一塊連續的虛擬地址,這塊虛擬記憶體空間其實是有大小限制的,不同的程序可能還不一樣。
普通的由Zygote孵化而來的使用者程序,所對映的Binder記憶體大小是不到1M的,準確說是 1*1024*1024) - (4096 *2) :這個限制定義在ProcessState類中,如果傳輸說句超過這個大小,系統就會報錯,因為Binder本身就是為了程序間頻繁而靈活的通訊所設計的,並不是為了拷貝大資料而使用的:
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
而在核心中,其實也有個限制,是4M,不過由於APP中已經限制了不到1M,這裡的限制似乎也沒多大用途:
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
struct vm_struct *area;
struct binder_proc *proc = filp->private_data;
const char *failure_string;
struct binder_buffer *buffer;
//限制不能超過4M
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
。。。
}
有個特殊的程序ServiceManager程序,它為自己申請的Binder核心空間是128K,這個同ServiceManager的用途是分不開的,ServcieManager主要面向系統Service,只是簡單的提供一些addServcie,getService的功能,不涉及多大的資料傳輸,因此不需要申請多大的記憶體:
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
// 僅僅申請了128k
bs = binder_open(128*1024);
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
系統服務與bindService等啟動的服務的區別
服務可分為系統服務與普通服務,系統服務一般是在系統啟動的時候,由SystemServer程序建立並註冊到ServiceManager中的。而普通服務一般是通過ActivityManagerService啟動的服務,或者說通過四大元件中的Service元件啟動的服務。這兩種服務在實現跟使用上是有不同的,主要從以下幾個方面:
- 服務的啟動方式
- 服務的註冊與管理
- 服務的請求使用方式
首先看一下服務的啟動上,系統服務一般都是SystemServer程序負責啟動,比如AMS,WMS,PKMS,電源管理等,這些服務本身其實實現了Binder介面,作為Binder實體註冊到ServiceManager中,被ServiceManager管理,而SystemServer程序裡面會啟動一些Binder執行緒,主要用於監聽Client的請求,並分發給響應的服務實體類,可以看出,這些系統服務是位於SystemServer程序中(有例外,比如Media服務)。在來看一下bindService型別的服務,這類服務一般是通過Activity的startService或者其他context的startService啟動的,這裡的Service元件只是個封裝,主要的是裡面Binder服務實體類,這個啟動過程不是ServcieManager管理的,而是通過ActivityManagerService進行管理的,同Activity管理類似。
再來看一下服務的註冊與管理:系統服務一般都是通過ServiceManager的addService進行註冊的,這些服務一般都是需要擁有特定的許可權才能註冊到ServiceManager,而bindService啟動的服務可以算是註冊到ActivityManagerService,只不過ActivityManagerService管理服務的方式同ServiceManager不一樣,而是採用了Activity的管理模型,詳細的可以自行分析
最後看一下使用方式,使用系統服務一般都是通過ServiceManager的getService得到服務的控制代碼,這個過程其實就是去ServiceManager中查詢註冊系統服務。而bindService啟動的服務,主要是去ActivityManagerService中去查詢相應的Service元件,最終會將Service內部Binder的控制代碼傳給Client。
Binder執行緒、Binder主執行緒、Client請求執行緒的概念與區別
Binder執行緒是執行Binder服務的載體,只對於服務端才有意義,對請求端來說,是不需要考慮Binder執行緒的,但Android系統的處理機制其實大部分是互為C/S的。比如APP與AMS進行互動的時候,都互為對方的C與S,這裡先不討論這個問題,先看Binder執行緒的概念。
Binder執行緒就是執行Binder實體業務的執行緒,一個普通執行緒如何才能成為Binder執行緒呢?很簡單,只要開啟一個監聽Binder字元裝置的Loop執行緒即可,在Android中有很多種方法,不過歸根到底都是監聽Binder,換成程式碼就是通過ioctl來進行監聽。
拿ServerManager程序來說,其主線就是Binder執行緒,其做法是通過binder_loop實現不死執行緒:
void binder_loop(struct binder_state *bs, binder_handler func)
{
...
for (;;) {
<!--關鍵點1-->
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
<!--關鍵點2-->
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
。。
}
}
上面的關鍵程式碼1就是阻塞監聽客戶端請求,2 就是處理請求,並且這是一個死迴圈,不退出。再來看SystemServer程序中的執行緒,在Android4.3(6.0以後打程式碼就不一樣了)中SystemSever主執行緒便是Binder執行緒,同時一個Binder主執行緒,Binder執行緒與Binder主執行緒的區別是:執行緒是否可以終止Loop,不過目前啟動的Binder執行緒都是無法退出的,其實可以全部看做是Binder主執行緒,其實現原理是,在SystemServer主執行緒執行到最後的時候,Loop監聽Binder裝置,變身死迴圈執行緒,關鍵程式碼如下:
extern "C" status_t system_init()
{
...
ALOGI("System server: entering thread pool.\n");
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
ALOGI("System server: exiting thread pool.\n");
return NO_ERROR;
}
ProcessState::self()->startThreadPool()是新建一個Binder主執行緒,而PCThreadState::self()->joinThreadPool()是將當前執行緒變成Binder主執行緒。其實startThreadPool最終也會呼叫joinThreadPool,看下其關鍵函式:
void IPCThreadState::joinThreadPool(bool isMain)
{
...
status_t result;
do {
int32_t cmd;
...關鍵點1
result = talkWithDriver();
if (result >= NO_ERROR) {
...關鍵點2
result = executeCommand(cmd);
}
// 非主執行緒的可以退出
if(result == TIMED_OUT && !isMain) {
break;
}
// 死迴圈,不完結,呼叫了這個,就好比是開啟了Binder監聽迴圈,
} while (result != -ECONNREFUSED && result != -EBADF);
}
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
do {
...關鍵點3
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
}
先看關鍵點1 talkWithDriver,其實質還是去掉用ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)去不斷的監聽Binder字元裝置,獲取到Client傳輸的資料後,再通過executeCommand去執行相應的請求,joinThreadPool是普通執行緒化身Binder執行緒最常見的方式。不信,就再看一個MediaService,看一下main_mediaserver的main函式:
int main(int argc, char** argv)
{
。。。
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
ALOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();
registerExtensions();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
其實還是通過joinThreadPool變身Binder執行緒,至於是不是主執行緒,看一下下面的函式:
void IPCThreadState::joinThreadPool(bool isMain)
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
String8 name = makeBinderThreadName();
ALOGV("Spawning new pooled thread, name=%s\n", name.string());
sp<Thread> t = new PoolThread(isMain);
t->run(name.string());
}
}
其實關鍵就是就是傳遞給joinThreadPool函式的isMain是否是true,不過是否是Binder主執行緒並沒有什麼用,因為原始碼中並沒有為這兩者的不同處理留入口,感興趣可以去檢視一下binder中的TIMED_OUT。
最後來看一下普通Client的binder請求執行緒,比如我們APP的主執行緒,在startActivity請求AMS的時候,APP的主執行緒成其實就是Binder請求執行緒,在進行Binder通訊的過程中,Client的Binder請求執行緒會一直阻塞,知道Service處理完畢返回處理結果。
Binder請求的同步與非同步
很多人都會說,Binder是對Client端同步,而對Service端非同步,其實並不完全正確,在單次Binder資料傳遞的過程中,其實都是同步的。只不過,Client在請求Server端服務的過程中,是需要返回結果的,即使是你看不到返回資料,其實還是會有個成功與失敗的處理結果返回給Client,這就是所說的Client端是同步的。至於說服務端是非同步的,可以這麼理解:在服務端在被喚醒後,就去處理請求,處理結束後,服務端就將結果返回給正在等待的Client執行緒,將結果寫入到Client的核心空間後,服務端就會直接返回了,不會再等待Client端的確認,這就是所說的服務端是非同步的,可以從原始碼來看一下:
Client端同步阻塞請求
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{if (reply) { err = waitForResponse(reply); } ...
Client在請求服務的時候 Parcel* reply基本都是非空的(還沒見過空用在什麼位置),非空就會執行waitForResponse(reply),如果看過幾篇Binder分析文章的人應該都會知道,在A端向B寫完資料之後,A會返回給自己一個BR_TRANSACTION_COMPLETE命令,告知自己資料已經成功寫入到B的Binder核心空間中去了,如果是需要回復,在處理完BR_TRANSACTION_COMPLETE命令後會繼續阻塞等待結果的返回:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){
...
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break;
cmd = mIn.readInt32();
switch (cmd) {
<!--關鍵點1 -->
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
<!--關鍵點2 -->
case BR_REPLY:
{
binder_transaction_data tr;
// free buffer,先設定資料,直接
if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
// 牽扯到資料利用,與記憶體釋放
reply->ipcSetDataReference(...)
}
goto finish;
}
finish:
...
return err;
}
關鍵點1就是處理BR_TRANSACTION_COMPLETE,如果需要等待reply,還要通過talkWithDriver等待結果返回,最後執行關鍵點2,處理返回資料。對於服務端來說,區別就在於關鍵點1 ,來看一下服務端Binder執行緒的程式碼,拿常用的joinThreadPool來看,在talkWithDriver後,會執行executeCommand函式,
void IPCThreadState::joinThreadPool(bool isMain)
{
...
status_t result;
do {
int32_t cmd;
...關鍵點1
result = talkWithDriver();
if (result >= NO_ERROR) {
...關鍵點2
result = executeCommand(cmd);
}
// 非主執行緒的可以退出
if(result == TIMED_OUT && !isMain) {
break;
}
// 死迴圈,不完結,呼叫了這個,就好比是開啟了Binder監聽迴圈,
} while (result != -ECONNREFUSED && result != -EBADF);
}
executeCommand會進一步呼叫sendReply函式,看一下這裡的特點waitForResponse(NULL, NULL),這裡傳遞的都是null,在上面的關鍵點1的地方我們知道,這裡不需要等待Client返回,因此會直接 goto finish,這就是所說的Client同步,而服務端非同步的邏輯。
// BC_REPLY
status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags)
{
// flag 0
status_t err;
status_t statusBuffer;
err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer);
if (err < NO_ERROR) return err;
return waitForResponse(NULL, NULL);
}
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
請求同步最好的例子就是在Android6.0之前,國產ROM許可權的申請都是同步的,在申請許可權的時候,APP申請許可權的執行緒會阻塞,就算是UI執行緒也會阻塞,ROM為了防止ANR,都會為許可權申請設定一個倒計時,不操作,就給個預設操作,有興趣可以自己分析。
Android APP程序天生支援Binder通訊的原理是什麼
Android APP程序都是由Zygote程序孵化出來的。常見場景:點選桌面icon啟動APP,或者startActivity啟動一個新程序裡面的Activity,最終都會由AMS去呼叫Process.start()方法去向Zygote程序傳送請求,讓Zygote去fork一個新程序,Zygote收到請求後會呼叫Zygote.forkAndSpecialize()來fork出新程序,之後會通過RuntimeInit.nativeZygoteInit來初始化Andriod APP執行需要的一些環境,而binder執行緒就是在這個時候新建啟動的,看下面的原始碼(Android 4.3):
這裡不分析Zygote,只是給出其大概執行機制,Zygote在啟動後,就會通過runSelectLoop不斷的監聽socket,等待請求來fork程序,如下:
private static void runSelectLoop() throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
FileDescriptor[] fdArray = new FileDescriptor[4];
...
int loopCount = GC_LOOP_COUNT;
while (true) {
int index;
...
boolean done;
done = peers.get(index).runOnce();
...
}}}
每次fork請求到來都會呼叫ZygoteConnection的runOnce()來處理請求,
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
。。。
try {
...關鍵點1
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName);
}
try {
if (pid == 0) {
// in child
...關鍵點2
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
。。。
}
runOnce()有兩個關鍵點,關鍵點1 Zygote.forkAndSpecialize就是通過fork系統呼叫來新建程序,**關鍵點2 **handleChildProc就是對新建的APP程序進行一些初始化工作,為Android Java程序建立一些必須的場景。Zygote.forkAndSpecialize沒什麼可看的,就是Linux中的fork程序,這裡主要看一下handleChildProc
private void handleChildProc(Arguments parsedArgs,
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
//從Process.start啟動的parsedArgs.runtimeInit一般都是true if (parsedArgs.runtimeInit) {
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
pipeFd, parsedArgs.remainingArgs);
} else {
// Android應用啟動都走該分支
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
parsedArgs.remainingArgs);
}
}
接著看 RuntimeInit.zygoteInit函式
public static final void zygoteInit(int targetSdkVersion, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
redirectLogStreams();
commonInit();
<!--關鍵點1-->
nativeZygoteInit();
<!--關鍵點2-->
applicationInit(targetSdkVersion, argv);
}
先看關鍵點1,nativeZygoteInit屬於Native方法,該方法位於AndroidRuntime.cpp中,其實就是呼叫呼叫到app_main.cpp中的onZygoteInit
static void com_android_internal_os_RuntimeInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
gCurRuntime->onZygoteInit();
}
關鍵就是onZygoteInit
virtual void onZygoteInit()
{
sp proc = ProcessState::self();
//啟動新binder執行緒loop
proc->startThreadPool();
}
首先,ProcessState::self()函式會呼叫open()開啟/dev/binder裝置,這個時候Client就能通過Binder進行遠端通訊;其次,proc->startThreadPool()負責新建一個binder執行緒,監聽Binder裝置,這樣程序就具備了作為Binder服務端的資格。每個APP的程序都會通過onZygoteInit開啟Binder,既能作為Client,也能作為Server,這就是Android程序天然支援Binder通訊的原因。
Android APP有多少Binder執行緒,是固定的麼?
通過上一個問題我們知道了Android APP執行緒為什麼天然支援Binder通訊,並且可以作為Binder的Service端,同時也對Binder執行緒有了一個瞭解,那麼在一個Android APP的程序裡面究竟有多少個Binder執行緒呢?是固定的嗎。在分析上一個問題的時候,我們知道Android APP程序在Zygote fork之初就為它新建了一個Binder主執行緒,使得APP端也可以作為Binder的服務端,這個時候Binder執行緒的數量就只有一個,假設我們的APP自身實現了很多的Binder服務,一個執行緒夠用的嗎?這裡不妨想想一下SystemServer程序,SystemServer擁有很多系統服務,一個執行緒應該是不夠用的,如果看過SystemServer程式碼可能會發現,對於Android4.3的原始碼,其實一開始為該服務開啟了兩個Binder執行緒。還有個分析Binder常用的服務,media服務,也是在一開始的時候開啟了兩個執行緒。
先看下SystemServer的開始載入的執行緒:通過 ProcessState::self()->startThreadPool()新加了一個Binder執行緒,然後通過IPCThreadState::self()->joinThreadPool();將當前執行緒變成Binder執行緒,注意這裡是針對Android4.3的原始碼,android6.0的這裡略有不同。
extern "C" status_t system_init()
{
...
ALOGI("System server: entering thread pool.\n");
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
ALOGI("System server: exiting thread pool.\n");
return NO_ERROR;
}
再看下Media服務,同SystemServer類似,也是開啟了兩個Binder執行緒:
int main(int argc, char** argv)
{ ...
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
可以看出Android APP上層應用的程序一般是開啟一個Binder執行緒,而對於SystemServer或者media服務等使用頻率高,服務複雜的程序,一般都是開啟兩個或者更多。來看第二個問題,Binder執行緒的數目是固定的嗎?答案是否定的,驅動會根據目標程序中是否存在足夠多的Binder執行緒來告訴程序是不是要新建Binder執行緒,詳細邏輯,首先看一下新建Binder執行緒的入口:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;
switch (cmd) {
...
// 可以根據核心返回資料建立新的binder執行緒
case BR_SPAWN_LOOPER:
mProcess->spawnPooledThread(false);
break;
}
executeCommand一定是從Bindr驅動返回的BR命令,這裡是BR_SPAWN_LOOPER,什麼時候,Binder驅動會向程序傳送BR_SPAWN_LOOPER呢?全域性搜尋之後,發現只有一個地方binder_thread_read,如果直觀的想一下,什麼時候需要新建Binder執行緒呢?很簡單,不夠用的時候,注意上面使用的是spawnPooledThread(false),也就是說這裡啟動的都是普通Binder執行緒。為了瞭解啟動時機,先看一些binder_proc內部判定引數的意義:
struct binder_proc {
...
int max_threads; // 程序所能啟動的最大非主Binder執行緒數目
int requested_threads; // 請求啟動的非主執行緒數
int requested_threads_started;//已經啟動的非主執行緒數
int ready_threads; // 當前可用的Binder執行緒數
...
};
再來看binder_thread_read函式中是麼時候會去請求新建Binder執行緒,以Android APP程序為例子,通過前面的分析知道APP程序天然支援Binder通訊,因為它有一個Binder主執行緒,啟動之後就會阻塞等待Client請求,這裡會更新proc->ready_threads,第一次阻塞等待的時候proc->ready_threads=1,之後睡眠。
binder_thread_read(){
...
retry:
//當前執行緒todo佇列為空且transaction棧為空,則代表該執行緒是空閒的 ,看看是不是自己被複用了
wait_for_proc_work = thread->transaction_stack == NULL &&
list_empty(&thread->todo);
...//可用執行緒個數+1
if (wait_for_proc_work)
proc->ready_threads++;
binder_unlock(__func__);
if (wait_for_proc_work) {
...
//當程序todo佇列沒有資料,則進入休眠等待狀態
ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
} else {
if (non_block) {
...
} else
//當執行緒todo佇列沒有資料,則進入休眠等待狀態
ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));
}
binder_lock(__func__);
//被喚醒可用執行緒個數-1
if (wait_for_proc_work)
proc->ready_threads--;
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
...
while (1) {
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL;
//先考慮從執行緒todo佇列獲取事務資料
if (!list_empty(&thread->todo)) {
w = list_first_entry(&thread->todo, struct binder_work, entry);
//執行緒todo佇列沒有資料, 則從程序todo對獲取事務資料
} else if (!list_empty(&proc->todo) && wait_for_proc_work) {
w = list_first_entry(&proc->todo, struct binder_work, entry);
} else {
}
..
if (t->buffer->target_node) {
cmd = BR_TRANSACTION; //設定命令為BR_TRANSACTION
} else {
cmd = BR_REPLY; //設定命令為BR_REPLY
}
..
done:
*consumed = ptr - buffer;
//建立執行緒的條件
if (proc->requested_threads + proc->ready_threads == 0 &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))) {
//需要新建的數目執行緒數+1
proc->requested_threads++;
// 生成BR_SPAWN_LOOPER命令,用於建立新的執行緒
put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer);
}
return 0;
}
被Client喚醒後proc->ready_threads會-1,之後變成0,這樣在執行到done的時候,就會發現proc->requested_threads + proc->ready_threads == 0,這是新建Binder執行緒的一個必須條件,再看下其他幾個條件
if (proc->requested_threads + proc->ready_threads == 0 &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED)))
- proc->requested_threads + proc->ready_threads == 0 :如果目前還沒申請新建Binder執行緒,並且proc->ready_threads空閒Binder執行緒也是0,就需要新建一個Binder執行緒,其實就是為了保證有至少有一個空閒的執行緒。
- proc->requested_threads_started < proc->max_threads:目前啟動的普通Binder執行緒數requested_threads_started還沒達到上限(預設APP程序是15)
- thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED) 當先執行緒是Binder執行緒,這個是一定滿足的,不知道為什麼列出來
proc->max_threads是多少呢?不同的程序其實設定的是不一樣的,看普通的APP程序,在ProcessState::self()新建ProcessState單利物件的時候會呼叫ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);設定上限,可以看到預設設定的上限是15。
static int open_driver()
{
int fd = open("/dev/binder", O_RDWR);
...
size_t maxThreads = 15;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
...
}
如果滿足新建的條件,就會將proc->requested_threads加1,並在驅動執行完畢後,利用put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer);通知服務端在使用者空間發起新建Binder執行緒的操作,新建的是普通Binder執行緒,最終再進入binder_thread_write的BC_REGISTER_LOOPER:
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
void __user *buffer, int size, signed long *consumed)
{
...
case BC_REGISTER_LOOPER:
...
// requested_threads --
proc->requested_threads--;
proc->requested_threads_started++;
}
}
這裡會將proc->requested_threads復原,其實就是-1,並且啟動的Binder執行緒數+1。
個人理解,之所以採用動態新建Binder執行緒的意義有兩點,第一:如果沒有Client請求服務,就保持執行緒數不變,減少資源浪費,需要的時候再分配新執行緒。第二:有請求的情況下,保證至少有一個空閒執行緒是給Client端,以提高Server端響應速度。
Client端執行緒睡眠在哪個佇列上,喚醒Server端哪個等待佇列上的執行緒
先看第一部分:傳送端執行緒睡眠在哪個佇列上?
傳送端執行緒一定睡眠在自己binder_thread的等待佇列上,並且,該佇列上有且只有自己一個睡眠執行緒
再看第二部分:在Binder驅動去喚醒執行緒的時候,喚醒的是哪個等待佇列上的執行緒?
理解這個問題需要理解binder_thread中的 struct binder_transaction * transaction_stack棧,這個棧規定了transaction的執行順序:棧頂的一定先於棧內執行。
如果本地操作是BC_REPLY,一定是喚醒之前傳送等待的執行緒,這個是100%的,但是如果是BC_TRANSACTION,那就不一定了,尤其是當兩端互為服務相互請求的時候,場景如下:
- 程序A的普通執行緒AT1請求B程序的B1服務,喚醒B程序的Binder執行緒,AT1睡眠等待服務結束
- B程序的B1服務在執行的的時候,需要請求程序A的A1服務,則B程序的Binder執行緒BT1睡眠,喚醒A程序的Binder執行緒,等待服務結束
- A程序的A1服務在執行的時候,又需要B程序的B2服務,則A程序的binder執行緒AT2睡眠,喚醒B程序的Binder執行緒,等待服務結束
這個時候就會遇到一個問題:喚醒哪個執行緒比較合適?是睡眠在程序佇列上的執行緒,還是之前睡眠的執行緒BT1?答案是:之前睡眠的執行緒BT1,具體看下面的圖解分析
首先第一步A普通執行緒去請求B程序的B1服務,這個時候在A程序的AT1執行緒的binder_ref中會將binder_transaction1入棧,而同樣B的Binder執行緒在讀取binder_work之後,也會將binder_transaction1加入自己的堆疊,如下圖:
而當B的Binder執行緒被喚醒後,執行Binder實體中的服務時,發現服務函式需要反過來去請求A端的A1服務,那就需要通過Binder向A程序傳送請求,並新建binder_transaction2壓入自己的binder_transaction堆疊,而A程序的Binder執行緒被喚醒後也會將binder_transaction2加入自己的堆疊,會後效果如下:
這個時候,還是沒有任何問題,但是恰好在執行A1服務的時候,又需要請求B2服務,這個時候,A1執行緒重複上述壓棧過程,新建binder_transaction3壓入自己的棧,不過在寫入到目標端B的時候,會面臨一個抉擇,寫入那個佇列,是binder_proc上的佇列,還是正在等候A返回的BT1執行緒的佇列?
結果已經說過,是BT1的佇列,為什麼呢?因為BT1佇列上的之前的binder_transaction2在等待A程序執行完,但是A端的binder_transaction3同樣要等待binder_transaction3在B程序中執行完畢,也就是說,binder_transaction3在B端一定是先於binder_transaction2執行的,因此喚醒BT1執行緒,並將binder_transaction3壓入BT2的棧,等binder_transaction3執行完畢,出棧後,binder_transaction2才能執行,這樣,既不妨礙binder_transaction2的執行,同樣也能讓睡眠的BT1程序提高利用率,因為最終的堆疊效果就是:
而當binder_transaction3完成,出棧的過程其實就簡單了,
- BT1 執行binder_transaction3,喚醒A端AT2 Binder執行緒,並且BT1繼續睡眠(因為還有等待的transaction)
- AT2 執行binder_transaction2,喚醒BT1
- BT1 執行binder_transaction1,喚醒AT1
- 執行結束
從這裡可以看出,其實設計的還是很巧妙的,讓執行緒複用,提高了效率,還避免了新建不必要的Binder執行緒,在binder驅動中島實現程式碼,其實就是根據binder_transaction中堆疊記錄查詢,
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{..
while (tmp) {
// 找到對方正在等待自己程序的執行緒,如果執行緒沒有在等待自己程序的返回,就不要找了
// 判斷是不target_proc中,是不是有執行緒,等待當前執行緒
// thread->transaction_stack,這個時候,
// 是binder執行緒的,不是普通執行緒 B去請求A服務,
// 在A服務的時候,又請求了B,這個時候,A的服務一定要等B處理完,才能再返回B,可以放心用B
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from;
tmp = tmp->from_parent;
... }
} }
Binder協議中BC與BR的區別
BC與BR主要是標誌資料及Transaction流向,其中BC是從使用者空間流向核心,而BR是從核心流線使用者空間,比如Client向Server傳送請求的時候,用的是BC_TRANSACTION,當資料被寫入到目標程序後,target_proc所在的程序被喚醒,在核心空間中,會將BC轉換為BR,並將資料與操作傳遞該使用者空間。
Binder在傳輸資料的時候是如何層層封裝的–不同層次使用的資料結構(命令的封裝)
核心中,與使用者空間對應的結構體物件都需要新建,但傳輸資料的資料只拷貝一次,就是一次拷貝的時候。
從Client端請求開始分析,暫不考慮java層,只考慮Native,以ServiceManager的addService為例,具體看一下
MediaPlayerService::instantiate();
MediaPlayerService會新建Binder實體,並將其註冊到ServiceManager中:
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
}
這裡defaultServiceManager其實就是獲取ServiceManager的遠端代理:
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
if (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}
如果將程式碼簡化其實就是
return gDefaultServiceManager = BpServiceManager (new BpBinder(0));
addService就是呼叫BpServiceManager的addService,
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
這裡會開始第一步的封裝,資料封裝,其實就是講具體的傳輸資料寫入到Parcel物件中,與Parcel對應是ADD_SERVICE_TRANSACTION等具體操作。比較需要注意的就是data.writeStrongBinder,這裡其實就是把Binder實體壓扁:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
具體做法就是轉換成flat_binder_object,以傳遞Binder的型別、指標之類的資訊:
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}
return finish_flatten_binder(binder, obj, out);
}
接下來看 remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); 在上面的環境中,remote()函式返回的就是BpBinder(0),
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
if (mAlive) {
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
之後通過 IPCThreadState::self()->transact( mHandle, code, data, reply, flags)進行進一步封裝:
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags){
if ((flags & TF_ONE_WAY) == 0) {
if (err == NO_ERROR) {
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
if (reply) {
err = waitForResponse(reply);
}
..
return err;
}
writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);是進一步封裝的入口,在這個函式中Parcel& data、handle、code、被進一步封裝成binder_transaction_data物件,並拷貝到mOut的data中去,同時也會將BC_TRANSACTION命令也寫入mOut,這裡與binder_transaction_data對應的CMD是BC_TRANSACTION,binder_transaction_data也儲存了資料的指引新資訊:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
tr.data.ptr.offsets = data.ipcObjects();
} ..
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
mOut封裝結束後,會通過waitForResponse呼叫talkWithDriver繼續封裝:
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
binder_write_read bwr;
// Is the read buffer empty? 這裡會有同時返回兩個命令的情況 BR_NOOP、BR_COMPLETE
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (long unsigned int)mOut.data(); // This is what we'll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (long unsigned int)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
。。
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
if (mProcess->mDriverFD <= 0) {
err = -EBADF;
}
} while (err == -EINTR);
if (err >= NO_ERROR) {
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < (ssize_t)mOut.dataSize())
mOut.remove(0, bwr.write_consumed);
else
mOut.setDataSize(0);
}
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return err;
}
talkWithDriver會將mOut中的資料與命令繼續封裝成binder_write_read物件,其中bwr.write_buffer就是mOut中的data(binder_transaction_data+BC_TRRANSACTION),之後就會通過ioctl與binder驅動互動,進入核心,這裡與binder_write_read物件對應的CMD是BINDER_WRITE_READ,進入驅動後,是先寫後讀的順序,所以才叫BINDER_WRITE_READ命令,與BINDER_WRITE_READ層級對應的幾個命令碼一般都是跟執行緒、程序、資料整體傳輸相關的操作,不涉及具體的業務處理,比如BINDER_SET_CONTEXT_MGR是將執行緒程式設計ServiceManager執行緒,並建立0號Handle對應的binder_node、BINDER_SET_MAX_THREADS是設定最大的非主Binder執行緒數,而BINDER_WRITE_READ就是表示這是一次讀寫操作:
#define BINDER_CURRENT_PROTOCOL_VERSION 7
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)
#define BINDER_THREAD_EXIT _IOW('b', 8, int)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)
詳細看一下binder_ioctl對於BINDER_WRITE_READ的處理,
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
..
<!--拷貝binder_write_read物件到核心空間-->
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}
<!--根據是否需要寫資料處理是不是要寫到目標程序中去-->
if (bwr.write_size > 0) {
ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
}
<!--根據是否需要寫資料處理是不是要讀,往自己程序裡讀資料-->
if (bwr.read_size > 0) {
ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
<!--是不是要同時喚醒程序上的阻塞佇列-->
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait);
}
break;
}
case BINDER_SET_MAX_THREADS:
if (copy_from_user(&proc->ma