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難以理解的點,但不會太細的跟蹤分析,只拋磚,本身去發掘玉,因爲篇幅過大,分三篇:javascript
Binder實體服務其實有兩種,一是經過addService註冊到ServiceManager中的服務,好比ActivityManagerService、PackageManagerService、PowerManagerService等,通常都是系統服務;還有一種是經過bindService拉起的一些服務,通常是開發者本身實現的服務。這裏先看經過addService添加的被ServiceManager所管理的服務。有不少分析ServiceManager的文章,本文不分析ServiceManager,只是簡單提一下,ServiceManager是比較特殊的服務,全部應用都能直接使用,由於ServiceManager對於Client端來講Handle句柄是固定的,都是0,因此ServiceManager服務並不須要查詢,能夠直接使用。java
理解Binder定向制導的關鍵是理解Binder的四棵紅黑樹,先看一下binder_proc結構體,在它內部有四棵紅黑樹,threads,nodes,refs_by_desc,refs_by_node,nodes就是Binder實體在內核中對應的數據結構,binder_node裏記錄進程相關的binder_proc,還有Binder實體自身的地址等信息,nodes紅黑樹位於binder_proc,能夠知道Binder實體實際上是進程內可見,而不是線程內。node
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服務。android
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紅黑樹,其實兩棵紅黑樹中的節點是複用的,只是查詢方式不一樣,一個經過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中是無效的。app
Android選擇Binder做爲主要進程通訊的方式同其性能高也有關係,Binder只須要一次拷貝就能將A進程用戶空間的數據爲B進程所用。這裏主要涉及兩個點:框架
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函數。
雖然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;
} 複製代碼
服務可分爲系統服務與普通服務,系統服務通常是在系統啓動的時候,由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服務的載體,只對於服務端纔有意義,對請求端來講,是不須要考慮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是對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進程都是由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通訊,而且能夠做爲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->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 機制學的不錯,來解決下這幾個問題(三)
僅供參考,歡迎指正