Binder承擔了絕大部分Android程式通訊的職責,可以看做是Android的血管系統,負責不同服務模組程式間的通訊。在對Binder的理解上,可大可小,日常APP開發並不怎麼涉及Binder通訊知識,最多就是Service及AIDL的使用會涉及部分Binder知識。Binder往小了說可總結成一句話:一種IPC程式間通訊方式,負責程式A的資料,傳送到程式B。往大了說,其實涉及的知識還是很多的,如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中的服務,比如ActivityManagerService、PackageManagerService、PowerManagerService等,一般都是系統服務;還有一種是通過bindService拉起的一些服務,一般是開發者自己實現的服務。這裡先看通過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的,準確說是 110241024) - (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端響應速度。
聽說你 Binder 機制學的不錯,來解決下這幾個問題(二)
聽說你 Binder 機制學的不錯,來解決下這幾個問題(三)
僅供參考,歡迎指正