Android Binder学习(一)之Binder中的数据结构

 备注:双向箭头表示双向链表,各成员是串联起来的。 

    感兴趣可以加QQ群85486140,大家一起交流相互学习下!

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

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值