1. 程式人生 > >Android Binder學習(一)之Binder中的資料結構

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時的案例分析一下)。

相關推薦

Android Binder學習()Binder資料結構

 備註:雙向箭頭表示雙向連結串列,各成員是串聯起來的。    在分析Android framework程式碼時,遇到最多的就是binder程序間通訊了。如果只知道怎麼用,也不影響我們日常的工作。但如果你想閱讀binder原始碼,就需要花點時間了。相對與linux核心來說,A

Android Framework學習(九)Binder概述

Android系統中,每個應用程式是由Android的Activity,Service,Broadcast,ContentProvider這四大元件的中一個或多個組合而成,這四大元件所涉及的多程序間的通訊底層都是依賴於Binder IPC機制。不僅於此,整個An

Android O 學習()HAL型別

備註:這裡已Camera模組為例,如問題,歡迎討論。   以往的hal和framwork的程式碼緊密聯絡起來的,為此google為了framework 升級的方便在Android 8.0 上對 Android 作業系統底層進行了重新架構。新的架構已經瞭解一部分,但仍需要不斷學習,加上

Android 動畫學習()View Animation

Android動畫初步         動畫(Animation)在我們日常的Android開發工作當中使用得較為頻繁,尤其對於Android遊戲這個動畫的集合體,掌握動畫開發的重要性毋庸置疑。同

Redis學習筆記Redis基本資料結構

Redis基礎資料結構 Redis有5種基本資料結構:String(字串)、list(列表)、set(集合)、hash(雜湊)、zset(有序集合) 字串string 字串型別是Redis的value最簡單的資料結構,類似與Java語言中的ArrayList(數字列表),不過在Redis裡String是一種動

Android開發藝術探索學習-IPCBinder()

1. Binder簡介 1.1 What is Binder?     Android Dev Doc:Base class for a remotable object, the core part of a lightweight remote procedure

Android NDK學習起航先買艘船

      感覺Android寫了這麼久了, 也該學點裝逼的技能了。所以我把目光放在了NDK和C++上,只要玩轉了這兩個, 我就能去玩OpenCV和TensorFlow了,想想就美滋滋。今天先來基礎配置一個最簡單的NDK環境。基於AndroidStudio 3.2,使用Cma

Android開發學習資源()

1.Android效能優化(包括記憶體優化)一系列文章 http://androidperformance.com/categories/Android/http://www.csdn.net/article/2015-04-29/2824583-android-perfo

Android程式設計學習筆記 File資料儲存

File檔案可用來存放大量資料,如文字、圖片、音訊、視訊等。 在Android的資料儲存操作和Java中的IO流差不多的用法。 進行File資料儲存的步驟如下: ①開啟一個File物件 ②開啟一個FileOutputStream檔案輸出流,寫入資料 ③開啟一個FileIn

Android程式設計學習筆記 Fragment與Activity的資料傳遞

傳遞方向的不同: ①Activity----->Fragment: 在Activity中建立Bundle資料包,並呼叫Fragment的setArguments(Bundle bundle)方法 ②Fragment----->Activity: 需要在Frag

Android多媒體學習AndroidImage的簡單例項。

在多媒體應用中,Image是最基礎的功能模組,接下來我們將看看在Android中是如何獲取和儲存Image的。Android內嵌的Image獲取和儲存功能,可以讓我們對於整個媒體框架有個比較全面的瞭解,同時為audio和video的學習打下基礎。 一、Image的獲取可以通過

JavaWeb學習----Tomcat配置數字證書以及網路傳輸資料的密碼學知識

今天是學習JavaWeb的第二天,我們來了解什麼呢?就瞭解一下Tomcat中配置數字證書的相關內容,但是在說這部分內容的時候,我們貌似得先說一下數字證書的相關概念,那說到數字證書的時候我們還得了解一些

Android程式設計學習筆記 SQLite資料儲存

SQLite是一個輕量級的嵌入式的資料庫,我們在Android開發中不需要安裝任何外掛即可使用, 如果是root過的手機,可以在data/data/包名/databases裡面找到db資料庫檔案,推薦

Android學習筆記MVVM----DataBinding(資料雙向繫結)

要把大象裝冰箱,總共三步! 第一步:在app.gradle中新增一下程式碼 第二步:建立一個普通bean類和XML檔案 注意:xml檔案的命名將影響生成的binding類名,如需要自定義b

機器學習入門-載入sklearn資料並用matplotlib進行視覺化

from sklearn import datasets import matplotlib.pyplot as plt def get_data(): """ 從sklearn中獲取鳶尾花的資料 :return: 鳶尾花資料的字典,字典中包括的key有:【'data'

Metasploit框架的學習目錄結構

首先是介紹整個框架的結構: 0x01:最重要的目錄是data,modules,scripts,tools,plugins 1:我們先進去data目錄,該目錄包含大量有用的模組,如meterpreter,exploits,wordlists,templates等等 我們重點注意met

Android JNI 學習():JNI 簡介

將在 編程語言 ase 環境 公開 javase 全局 face let JNI 即 Java Native Interface 是 native 編程接口,它允許在Java虛擬機(VM)內運行Java代碼與其他編程語言(主要是C和C++)編寫的應用程序和庫進行交互操作。

Maven學習Nexus各repository介紹

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

LinuxC基礎學習各類軟體環境介紹

LinuxC基礎學習一之各類軟體環境介紹 一、常用軟體 1、VM12+Ubuntu14.04 2、Everything: 本地搜尋工具 3、Mindmanager:思維導圖工具 4、Xshell:

Java程式設計思想學習()----物件導論多型的理解

1.1抽象過程 1)萬物皆物件。 2)程式是物件的集合,他們通過傳送訊息來告知彼此所要求做的。 3)每個物件都有自己的由其他物件所構成的儲存。 4)每個物件都擁有其型別。 5)某一特定型別的所有物件都可以接收同樣的訊息。 上面是書上總結的內容,具體程式碼如下: //每個物件都有一個介面,介