binder驅動-------之資料結構篇2
1:binder實體在驅動中的表示(struct binder_node )
binder_node是應用空間的binder實體在核心的表示,它直屬於某個特定的程序(binder_proc),其他程序對該binder實體的使用,都會掛在該binder實體的refs雜湊列表中。
struct binder_node { int debug_id; struct binder_work work; union { struct rb_node rb_node; struct hlist_node dead_node; }; struct binder_proc *proc; struct hlist_head refs; int internal_strong_refs; int local_weak_refs; int local_strong_refs; void __user *ptr; void __user *cookie; unsigned has_strong_ref:1; unsigned pending_strong_ref:1; unsigned has_weak_ref:1; unsigned pending_weak_ref:1; unsigned has_async_transaction:1; unsigned accept_fds:1; unsigned min_priority:8; struct list_head async_todo; };
struct binder_work work;//通過這個欄位來將該binder新增到todo列表中,然後會在binder_thread_read中得到處理。此時這個node->work.type型別為BINDER_WORK_NODE。
struct rb_node rb_node;//通過該欄位,以該binder例項在應用空間對應的引用物件(RefBase::weakref_type)的地址為index,將該binder實體連線到該實體所屬的程序(bidner_proc)的proc->nodes的紅黑樹下
struct binder_proc *proc;//指向該binder所屬的程序
struct hlist_head refs;//所有其他程序對該binder的引用,以雜湊表的形式組織在以refs為表頭的雜湊表下
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;//以上各欄位用於binder的引用管理的
void __user *ptr;//binder實體對應在應用空間的weakref_type物件(即引用物件)在應用空間的地址
void __user *cookie;//儲存使用者空間的Binder實體地址的指標,正是通過該欄位來呼叫伺服器端在應用空間的處理後臺,見下面:
status_t IPCThreadState::executeCommand(int32_t cmd) { ... ... case BR_TRANSACTION: { ... ... if (tr.target.ptr) { sp<BBinder> b((BBinder*)tr.cookie); const status_t error = b->transact(tr.code, buffer, &reply, tr.flags); if (error < NO_ERROR) reply.setError(error); } else { const status_t error = the_context_object->transact(tr.code, buffer, &reply, tr.flags); if (error < NO_ERROR) reply.setError(error); }
上面的cookie值就是我們binder中的cookie值,由於他本身就是伺服器端bbinder物件的地址,所以取出來直接可以引用該物件的操作函式(b->transact())了。
2:binder引用在驅動中的表示(struct binder_ref)
為了實現跨程序的通訊,核心空間必須要能夠依賴和維護某些資訊,準確的找到發起通訊的源程序要去到的目的程序,即binder驅動要能夠快速的準確的實現通訊在程序間的路由。而binder驅動實現路由所依賴的主要結構就是binder_node與binder_ref結構體。binder_node對應binder通訊的伺服器,而binder_ref對應binder的客服端,而從客服端發起的通訊,要找到這個客服端對應的伺服器端,就必須找到這個這次通訊的伺服器端對應的binder_node在當前程序中的引用:binder_ref,如果沒有這個引用就需要binder驅動負責建立這個引用。待這個引用於binder_node關係建立起來後,就會有如下關係:binder_node->refs的雜湊表中包含了這個引用物件,而binder_ref->node則指向目標程序所包含的binder_node結構體。即通過binder_node->refs雜湊表,可以找出系統當前所有對該binder實體有引用的客服端程序所對應的引用物件;通過binder_ref->node則可以根據當前客服端程序的引用物件我們可以找到該引用物件所對應的binder實體(binder_node)。
binder驅動中的資料路由的大致過程:
向Binder 驅動傳送資料包時,應用程式通過將引用號填入binder_transaction_data結構的target.handle域中表明該資料包的目的 Binder在傳送執行緒中的引用號。驅動根據該引用號在傳送方程序的紅黑樹中(binder_proc->refs_by_desc)找到引用的binder_ref結構,進而通過其node域知道對應的核心binder節點,然後再從binder節點的proc域知道目標Binder實體所在的程序及其它相關資訊,實現資料包的路由
需要注意的是:binder的引用是跟程序相關的,同一個binder實體在不同的程序中的引用號可能相同也可能不相同。但不同binder實體在同一個程序中的引用必須保證是唯一的。
struct binder_ref {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* desc + proc => ref (transaction, inc/dec ref) */
/* node => refs + procs (proc exit) */
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;
};
struct rb_node rb_node_desc;
struct rb_node rb_node_node;//同一個程序中的所有對binder的引用,組織為兩顆紅黑樹:一顆是以引用號為index,以rb_node_desc為節點,以binder_proc->refs_by_desc為根的紅黑樹下;一顆是以binder_node在核心中的地址為index,以rb_node_node為節點,以proc_node->refs_by_node為根的紅黑樹下,前者是為了在已知引號號的情況下,快速的找到該引用號對應的binder_ref結構體,後者是根據binder_node的地址快速找到該binder_node在當前程序中的binder_ref結構體。
struct hlist_node node_entry;//通過該節點,將系統所有對特定binder_node結構體例項的引用都以雜湊表的形式組織到該binde_node->refs下面,這樣便於當該binder實體對應的服務異常退出時,可以通過binder_node->refs的該欄位來通知系統中所有其它對該binder有引用的程序,讓他們執行相應的退出操作。
struct binder_proc *proc;//該binder引用所在的程序
struct binder_node *node;//該引用所對應的binder_node實體
uint32_t desc;//該引用對應的引用號
int strong;
int weak;//
struct binder_ref_death *death;//該結構用於當該引用對應的bind_node實體已經死亡時,來通知該引用需要做的操作。
3:binder實體或引用在傳輸過程中的表示(struct flat_binder_object)
binder實體在驅動傳輸中的表示是通過struct flat_binder_object結構來實現的,然而這些跨程序傳遞的Binder混雜在應用程式傳送的資料包裡,資料格式由使用者定義,如果不把它們一一標記出來告知驅動,驅動將無法從資料中將它們提取出來,因為驅動是不關心應用所傳遞的資料的格式和內容的,於是就使用陣列data.offsets存放使用者資料中每個Binder相對data.buffer的偏移量,用offsets_size表示這個陣列的大小。驅動在傳送資料包時會根據data.offsets和offset_size將散落於data.buffer中的Binder找出來並一一為它們建立相關的資料結構(在binder_transaction函式中,根據flat_binder_object結構體的內容建立binder_node或binder_ref等結構體,並把它插入到程序的相關樹和列表中)。在資料包中傳輸的Binder是型別為struct flat_binder_object的結構體
上圖就是如下幾個結構體的關係圖:
struct binder_write_read: 是binder_ioctl函式中的BINDER_WRITE_READ分支命令對應的引數
struct binder_transaction_data:是BC_TRANSACTION命令對應的引數
struct flat_binder_object:表示散落在data中的binder物件的表示
而binder_transaction_data結構的offsets成員的值表示物件偏移陣列的首地址,該偏移陣列一般就是緊接著放在binder_transaction_data結構對應的資料段的末尾,而offeset_size成員說明這個物件偏移陣列的大小。偏移陣列的內容(上圖中的offset1,offset2...)即為flat_binder_object物件相對於binder_transaction_data->data域的差值。
上面的這個圖形象具體的說明了這些結構體之間的關聯
struct flat_binder_object {
/* 8 bytes for large_flat_header. */
unsigned long type;
unsigned long flags;
/* 8 bytes of data. */
union {
void *binder; /* local object */
signed long handle; /* remote object */
};
/* extra data associated with local object */
void *cookie;
};
unsigned long type;//表示binder的型別,主要分為兩大類:一個是BINDER_TYPE_BINDER,另一個是BINDER_TYPE_HANDLE;
void *binder; /* local object */表示binder實體對應的服務在應用空間中的weakref_type物件的地址
signed long handle;//表示target binder實體在當前程序的引用號,最終通過當前程序的紅黑樹(proc->refs)來轉換成對應的binder_ref結構體
void *cookie;//表示binder實體在應用空間BBinder物件的地址,該物件只存在伺服器端
以上值在應用空間的如下函式被賦值的:(frameworks/native/libs/binder/parcel.cpp)
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);
}
而binder->localBinder()的定義在如下地方那個:frameworks/native/libs/binder/biner.cpp
BBinder* BBinder::localBinder()
{
return this;
}
可見this此處指的就是BBinder物件。
4: binder_transaction
4.1結構體內容
struct binder_transaction {
int debug_id;
struct binder_work work;
struct binder_thread *from;
struct binder_transaction *from_parent;
struct binder_proc *to_proc;
struct binder_thread *to_thread;
struct binder_transaction *to_parent;
unsigned need_reply:1;
/* unsigned is_dead:1; */ /* not used at the moment */
struct binder_buffer *buffer;
unsigned int code;
unsigned int flags;
long priority;
long saved_priority;
uid_t sender_euid;
};
struct binder_work work;//通過該節點,將binder_transaction事物新增到目標執行緒的todo列表中。
struct binder_thread *from;//指示該事物所對應的源執行緒
struct binder_transaction *from_parent;//用來連線源執行緒的事物佇列
struct binder_proc *to_proc;//該事物要去到的目標程序
struct binder_thread *to_thread;//該事物要去到的目標執行緒
struct binder_transaction *to_parent;//用來連線目的執行緒的事物佇列
unsigned need_reply:1;//表示該事物是一個需要回復的請求
struct binder_buffer *buffer;//該事物對應的binder記憶體,該段記憶體是在target proc的記憶體池中分配的,並且被同時對映到目標程序的使用者和核心空間
unsigned int code;//請求對應的標號,一般在應用空間的Ixxxx.cpp檔案(如IMediaPlayerService.cpp檔案)中定義好了
unsigned int flags;
long priority;//用於執行緒的遷移
需要特別注意struct binder_transaction *from_parent與struct binder_transaction *to_parent成員的區別額:由於binder_transaction事物可能同時存在於源執行緒和目的執行緒的事物棧中(thread->transaction_stack),而在源執行緒中的事物棧通過binder_transaction->from_parent連結;而目的執行緒中的事物棧通過binder_transaction->to_parent成員連結。
4.2 事物通訊過程
binder的client通過binder驅動同binder伺服器端進行通訊的模型如下:
如上圖,binder1是client段在應用空間的binder實體的表示(BpBinder),而binder2是service端在應用空間binder實體的表示(BBinder),driver指的就是binder驅動,
BC_TRANSACTION/BR_REPLY和BR_TRANSCATION/BC_REPLY命令對應的引數都是:binder_transaction_data結構。並且BR開頭的命令都是核心返回給應用空間的,而BC開頭的命令都是應用空間寫到核心的。BC_TRANSACTION表示client端發起的一個事物請求,BR_REPLY表示client端收到該事物請求的響應,BR_TRANSCATION表示service端收到的請求,BC_REPLY表示service處理請求後發出的響應
client端大致過程:首先發送cmd:BC_TRANSACTION + arg:binder_transaction_data到核心,核心首先處理binder_thread_write然後再處理binder_thread_read,最後返回使用者空間,在返回核心空間時,核心返回命令為:BR_REPLY命令。
client端在處理寫過程時:在binder_thread_write--->binder_transaction函式中,包含客服端請求資訊的binder_transaction_data結構會被轉化成struct binder_transaction結構體,並將該結構體通過t->work.entry成員掛在目標執行緒(伺服器對應的執行緒)的todo列表中,並喚醒目標執行緒。然後就阻塞在binder_thread_read函式。
而目標執行緒被喚醒起來後,在binder_thread_read函式中,從thread->todo列表中取出struct binder_transaction結構體*t,然後將該t結構轉換成binder_transaction_data結構,並返回BR_TRANSACTION命令到伺服器端的使用者空間,而伺服器端的後臺執行緒在收到該命令後,就開始就行處理。
伺服器端的所在的程序在處理完BR_TRANSACTION命令請求後,會發送BC_REPLY命令+處理請求後的響應送到核心,此時核心在binder_thread_write-->binder_transaction函式中,會將包含請求響應資訊的binder_transaction_data結構轉換成struct binder_transaction結構體*t,然後通過t->work.entry成員掛在client執行緒所在的todo列表,然後喚醒client執行緒。
client在binder_thread_read函式中被喚醒後,從thread->todo列表中取出struct binder_transaction結構體*t,然後將該包含請求響應資訊的t結構轉換成binder_transaction_data結構,並返回BR_REPLY命令到使用者空間。
從而完成一次client到伺服器的請求/響應全過程。
5 參考文獻
在binder領域有一片文章,那是天王級別的,可以秒殺市面上所有的有關android的參考書籍,該文通篇沒有程式碼和流程圖,但卻說的透切,通俗易懂。