Android Binder學習(一)之Binder中的資料結構
備註:雙向箭頭表示雙向連結串列,各成員是串聯起來的。
在分析Android framework程式碼時,遇到最多的就是binder程序間通訊了。如果只知道怎麼用,也不影響我們日常的工作。但如果你想閱讀binder原始碼,就需要花點時間了。相對與linux核心來說,Android可以理解成一個“應用”了,所以分析起來大家一定要有信心。在寫部落格之前,我也看過老羅講的binder部分,老羅大神寫的太詳細了。本文以及後面的幾篇博文算是自己學習binder的學習筆記吧(自己敲出來的,理解時間會長些)。本文屬個人觀點,如有問題,歡迎指正。
注意:本文是基於android5.1分析的
一、Binder模型
和學習其它技術一樣,首先我們一起看看Binder粗略的線條。一看就知道是經典的CS模型,但是圖中表面上看,我們直接和對應的服務交流。其實中間還加了一個"總司令“servicemanager,需要註冊的服務都掛在它名下,具體的怎麼掛的,後面的博文會描述的。這裡簡稱servicemanager為sm。
粗略的線條理解:
1.service都是由server註冊到sm中的。在註冊時先得到一個sm的代理物件,將當前new出來的service物件的引用技術和物件引用打包傳給kernel。並以binder_node的形式保留在binder驅動中的binder_context_mgr_node全域性連結串列中。可以理解成在sm中有一個服務列表,需要什麼樣的服務,就點什麼服務。
2.client在請求相應的服務時,會在kernel中由建立一個binder_ref引用物件(關鍵是給了一個desc描述符),並寄掛在當前程序binder_proc的下面,而這個binder_ref物件會返回到使用者空間,根據這個desc描述符生成對應的代理物件,其中這個desc就儲存在Bpbinder類物件的私有成員mHandle中。
3.當client通過代理物件使用服務功能時,就會將上面的desc描述符傳給binder kernel驅動,進而就可以找到對應的binder_node,接著就可以找到對應的service 使用者空間物件了。最後我們就可以使用對應的服務了。
二、binder資料結構
1.binder_node
在drivers/staging/android/binder.c中
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; //binder實體對應的程序,可以理解成service是在哪個server程序中
struct hlist_head refs; //binder引用物件的連結串列,可以理解成使用該服務的“客戶端”構成的連結串列
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;
};
上面這個結構體是binder實體,它是每一個servie元件在核心中的存在形式。核心可以通過這個物件找到使用者空間service元件物件。
(01) rb_node和dead_node屬於一個union。如果該Binder實體還在使用,則通過rb_node將該節點連結到proc->nodes紅黑樹中;否則,則將該Binder實體通過dead_node連結到全域性雜湊表binder_dead_nodes中。
(02) proc,它是binder_proc(程序上下文資訊)結構體物件。目的是儲存該Binder實體的程序。
(03) refs,它是該Binder實體所有引用組成的連結串列(這裡就是程序使用的服務引用物件)。
2.binder_ref
binder_ref是一個代理物件在核心中的表現形式,每一個代理物件在核心中都有一個binder_ref中,並掛在客戶端所屬的binder_proc下面。它定義在driver/staging/android/binder.c中。
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;
};
<1>rb_node_desc:該指標是要連結到客戶端程序的binder_proc->refs_by_desc紅黑樹中,該紅黑樹按desc來排序。
<2>rb_node_node:該指標是要連結到客戶端程序的binder_proc->refs_by_node紅黑樹中,該紅黑樹按node來排序的。
<3>node_entry:該指標要連結到binder_node->refs中,這樣服務才知道誰來看過它
<4>proc:指向當前使用程序間通訊的客戶端程序的binder_proc物件
<5>node:當前引用物件使用的服務物件(binder_node)的地址
<6>desc:這個在前面講過了表示該引用物件的控制代碼。
3.binder_buffer
binder_buffer是用來程序間傳遞資料的核心緩衝區。
struct binder_buffer {
struct list_head entry; /* free and allocated entries by address */
struct rb_node rb_node; /* free entry by size or allocated entry */
/* by address */
unsigned free:1;
unsigned allow_user_free:1;
unsigned async_transaction:1;
unsigned debug_id:29;
struct binder_transaction *transaction;
struct binder_node *target_node;
size_t data_size;
size_t offsets_size;
uint8_t data[0];
};
<1>entry:下面我們瞭解binder_proc結構時,就能瞭解到通過該域可連結當前binder_buffer到對應程序的free或allocated binder_buffer連結串列中。<2>free:表示該binder_buffer是否是空閒的。如果為1,則說明該binder_buffer是空閒的,就會連結到binder_proc->free_buffers連結串列中。反之就連結到binder_proc->allocated_buffers。
<3>async_transaction:如果為1,說明和該binder_buffer關聯的是一個非同步事務。
<4>transaction:該核心緩衝區屬於哪個事務所用
<5>target_node:表示是哪一個binder實體在使用該binder_buffer.
<6>data:這裡才是真正的資料。
4.binder_proc
每一個使用binder程序間通訊的程序在核心中都對應有一個binder_proc物件,它描述了該程序的上下文資訊。當程序開啟“/dev/binder”檔案節點時,binder驅動就會建立一個binder_proc物件,並通過proc_node連線到全域性的binder_procs雜湊表中,這一過程我們會在後續的博文中介紹。
struct binder_proc {
struct hlist_node proc_node; //通過這個物件可將當前binder_proc物件連結進全域性binder_procs物件雜湊表中
struct rb_root threads; //當前程序處理程序通訊的執行緒池紅黑樹
struct rb_root nodes; //當前程序內部binder實體組成的紅黑樹,只有server才有binder實體物件。
struct rb_root refs_by_desc; //binder引用物件組成的紅黑樹,且已描述符來排序
struct rb_root refs_by_node; //binder引用物件組成的紅黑樹,切以binder實體的地址來排序
int pid; //當前程序的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; //當前程序的buffer首地址,
ptrdiff_t user_buffer_offset; //buffer user層虛擬地址和kernel 虛擬地址的差值。
struct list_head buffers;//可以理解成buffer連結串列,便於查詢適合的buffer
struct rb_root free_buffers; //空閒buffer連結串列
struct rb_root allocated_buffers; //已分配buffer的連結串列
size_t free_async_space;
struct page **pages; //申請buffer時,描述實體記憶體的page頁陣列
size_t buffer_size; //申請buffer大小,
uint32_t buffer_free;
struct list_head todo; //程序處理事件佇列
wait_queue_head_t wait;
struct binder_stats stats;
struct list_head delivered_death;
int max_threads;
int requested_threads;
int requested_threads_started;
int ready_threads;
long default_priority;
struct dentry *debugfs_entry;
};
我就拿一些我認為比較重要的,來介紹一下。
<1>proc_node:將該物件連結到全域性binder_procs物件中。
<2>threads:該程序內用於處理使用者請求的所有執行緒組成的紅黑樹。後面介紹binder_thread結構體時,你就知道binder_thread->rb_node就是連結到這個地方的。
<3>nodes:該程序內的所有Binder實體所組成的紅黑樹,binder實體對應的就是服務了,一般只有server程序才會用到。
<4>refs_by_desc:該程序內的所有按著desc描述符排列的Binder引用組成的紅黑樹。之前我們介紹binder引用物件時它的binder_ref->rb_node_desc就是連結到這裡的。
<5>refs_by_node:該程序內的所有按著binder_node地址排列的Binder引用組成的紅黑樹。之前我們介紹binder引用物件時它的binder_ref->rb_node_node就是連結到這裡的。
<6>vma:user空間傳下來的虛擬地址
<7>buffer:整個buffer的地址,後面會根據需要分割成相應的binder_buffer.
<8>buffers:binder_buffer構成的連結串列
<9>user_buffer_offset:是該核心虛擬地址和程序虛擬地址之間的差值。為了減少memcpy操作,binder驅動將程序的核心虛擬地址和程序虛擬地址對映到同一物理頁面,這樣的話使用者空間直接寫的話,核心去對應地址就可以看到,不需要傳值,傳址。只要其中一個地址,我們就能根據這個值計算處另外一個。其實這些我們在寫程式碼是不用關心的。
<10>free_buffers:沒有使用的binder_buffer構成的連結串列。
<11>allocated_buffers:已分配出去的binder_buffer構成的連結串列
<12>pages:描述實體記憶體的page頁陣列
<13>todo:該程序的待處理事務佇列。
<14>wait:程序事務等待佇列。它和todo佇列是相輔相成的,實現程序的等待和喚醒。(引用老羅的:比如說,當程序的wait佇列為空時,程序就沒事可做了,即進入等待狀態。當它們的作用是實現程序的等待/喚醒。例如,當Server程序的wait等待佇列為空時,Server就進入中斷等待狀態;當某Client向Server傳送請求時,就將該請求新增到Server的todo待處理事務佇列中,並嘗試喚醒Server等待佇列上的執行緒。如果,此時Server的待處理事務佇列不為空,則Server被喚醒後;喚醒後,則取出待處理事務進行處理,處理完畢,則將結果返回給Client)。
一個binder_proc對應一個程序,程序可能包含多個執行緒(binder_thread),也可能註冊(server程序)了多個服務(binder_node),也可能引用了(客戶端程序)多個服務(bider_ref),有時也使用多個buffer.反映出來就像下圖這樣。
5.binder_thread
struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node;
int pid;
int looper;
struct binder_transaction *transaction_stack;
struct list_head todo;
uint32_t return_error; /* Write failed, return error code in read buf */
uint32_t return_error2; /* Write failed, return error code in read */
/* buffer. Used when sending a reply to a dead process that */
/* we are also waiting on */
wait_queue_head_t wait;
struct binder_stats stats;
};
<1>proc:指向其宿主程序binder_proc
<2>rb_node:連結到binder_proc->threads中
<3>transaction_stack:事務堆疊,
<4>
todo:需要執行緒處理的請求連結串列。例如當client程序請求指定binder執行緒來處理,那麼就會將請求新增到相應binder執行緒todo佇列中
<5>wait:等待佇列
<6>stats:binder執行緒的一些統計資料
6.binder_transaction
如資料庫事務一樣,該結構描述了一個程序間通訊過程。
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; /*binder驅動在修改一個執行緒優先順序之前,會將原來執行緒優先順序儲存在這裡,以備後面還原*/
uid_t sender_euid;/*使用者id*/
};
<1>work:當binder驅動為目標進或目標執行緒建立一個事務時,就會將將work->type設定為BINDER_WORK_TRANSACTION,並將該事務物件連結到目標程序binder_proc->todo佇列,或者binder_thread->todo佇列中。
<2>from:發起事務的執行緒
<3>from_parent:當前事務依賴的另外一個事務,需要等前一個事務做完,才能處理當前事務。
<4>to_parent:目標執行緒需要處理的下一個事務。
<5>to_proc:處理該事務的程序結構體
<6>to_thread:處理該事務的執行緒結構體
<7>buffer:就是事務所需要的binder_buffer了。
3.binder_buffer
binder_buffer就是binder驅動通訊資料緩衝區了。
struct binder_buffer {
struct list_head entry; /* free and allocated entries by address */
struct rb_node rb_node; /* free entry by size or allocated entry */
/* by address */
unsigned free:1;
unsigned allow_user_free:1;
unsigned async_transaction:1;
unsigned debug_id:29;
struct binder_transaction *transaction;
struct binder_node *target_node;
size_t data_size;
size_t offsets_size;
uint8_t data[0];
};
(1)transaction:該binder_buffer是被哪一個事務所使用。
(2)target_node:表示該binder_buffer交給哪一個binder實體使用。
(3)data_size:該binder_buffer資料緩衝區的大小。
(4)offsets_size:這個是偏移陣列,用來表示binder物件的個數,當buffer中含有binder物件時,我們可以根據這個標誌找出其中的binder物件。
(5)data[0]:真正的資料緩衝區,使用時拿到data的地址,就是有效buffer的首地址。
7.binder_write_read
struct binder_write_read {
binder_size_t write_size; /* bytes to write */
binder_size_t write_consumed; /* bytes consumed by driver */
binder_uintptr_t write_buffer;
binder_size_t read_size; /* bytes to read */
binder_size_t read_consumed; /* bytes consumed by driver */
binder_uintptr_t read_buffer;
};
該結構體就是用來和kernel通訊的結構體
(1)write_size:寫給kernel binder驅動資料的長度。
(2)write_consumed:kernel binder驅動已經寫入的資料長度。
(3)write_buffer:write buffer資料緩衝區。
(4)read_size:將要讀取的資料長度。
(5)read_consumed:binder kernel驅動已經讀取的資料長度。
(6)read_buffer:讀取資料的buffer。
8.flat_binder_object
struct flat_binder_object {
/* 8 bytes for large_flat_header. */
__u32 type;
__u32 flags;
/* 8 bytes of data. */
union {
binder_uintptr_t binder; /* local object */
__u32 handle; /* remote object */
};
/* extra data associated with local object */
binder_uintptr_t cookie;
};
binder物件結構體:該結構體可以表示代理物件和本地物件。
(1)type:表示代理物件還是本地物件的型別。
(2)binder&handle:這個兩個成員變數構成一個聯合體,當該flat_binder_object物件表示一個代理物件時,這裡會使用handle,用來表示一個binder引用物件的控制代碼值(這個值就儲存在binder代理物件中,後續通訊時會先根據該控制代碼找到和哪個binder實體物件通訊)。當該flat_binder_object物件表示一個本地物件是,binder物件有效,表示一個本地物件內部弱引用計數物件的地址。同時cookie時真正的服務物件的使用者空間地址,返回到server程序時,會根據找個地址找到對應的服務物件,進而就可以進行相應的操作。
9.binder_transaction_data
struct binder_transaction_data {
/* The first two are only used for bcTRANSACTION and brTRANSACTION,
* identifying the target and contents of the transaction.
*/
union {
__u32 handle; /* target descriptor of command transaction */
binder_uintptr_t ptr; /* target descriptor of return transaction */
} target;
binder_uintptr_t cookie; /* target object cookie */
__u32 code; /* transaction command */
/* General information about the transaction. */
__u32 flags;
pid_t sender_pid;
uid_t sender_euid;
binder_size_t data_size; /* number of bytes of data */
binder_size_t offsets_size; /* number of bytes of offsets */
/* If this transaction is inline, the data immediately
* follows here; otherwise, it ends with a pointer to
* the data buffer.
*/
union {
struct {
/* transaction data */
binder_uintptr_t buffer;
/* offsets from buffer to flat_binder_object structs */
binder_uintptr_t offsets;
} ptr;
__u8 buf[8];
} data;
};
該結構體在binder通訊的核心資料結構:
(1)handle & ptr : 這兩個資料包含在共同體target中,當該target描述一個binder實體物件時,ptr有效,表示該服務物件一個弱引用計數物件的地址。當target描述一個binder引用物件時,handle資料有效,表示一個binder引用物件控制代碼值。
(2)cookie:當target描述一個本地物件時,cookie就是該服務物件在server中的地址。
(3)code:通訊碼
(4)sender_pid & sender_euid:表示事務發起者的pid和uid。
(5)data_size:資料緩衝區大小。
(6)offset_size:偏移陣列大小,用來表示有多少個binder物件,包含在資料緩衝區中。
(7)data:data是一個共用體,當通訊資料很小的時,可以直接使用buf[8]來儲存資料。當夠大時,只能用指標buffer來描述一個申請的資料緩衝區。
三、總結
通過檢視原始碼,我發現每一個使用了binder通訊的程序,其binder_proc物件都會連結到全域性的proc_node中,就如下圖所看到的那樣,手拉手。同時,每一個使用服務的程序都會在其binder_proc->refs_by_desc維護一個引用物件的紅黑樹,該紅黑樹描述了它使用的服務,用了多少服務,就有多少引用物件。如下圖所示,同一個服務被多個程序使用時的景象,每一個程序都用一個該binder物件的引用物件。如果拿到該引用物件後,就可以直接與對應的服務通訊了,不需要service_manage來參與了。
隨著學習的深入,我們會碰到一個叫匿名binder的傢伙,該傢伙一開始使用系統中已經註冊的服務得到一個代理物件,然後通過該代理物件,直接到對應的本地服務中申請其他服務物件,然後以binder物件的形式進行傳遞,這樣kernel中就會生成一個binder_node,進而會生成一個binder_ref返回給相應的代理物件。後面匿名binder拿著這個代理物件,就直接可以和對應的服務通訊了,不需要service_manage的參與(後面在簡單結合從surfaceflinger申請buffer時的案例分析一下)。