android binder机制---Binder驱动
4 Binder驱动
binder机制到底是如何从代理对象找到其对应的binder实体呢?其实,在binder驱动层,还有个与之相对的结构,
叫做binder_proc。示意图如下,
Binder驱动是Android专用的,但底层的驱动架构与Linux驱动一样。binder驱动在以misc设备进行注册,作为虚拟
字符设备,没有直接操作硬件,只是对设备内存的处理。主要是驱动设备的打开 (binder_open),数据操作(binder_ioctl)。
用户态的程序调用Kernel层驱动是需要陷入内核态,进行系统调用(syscall),比如打开Binder驱动方法的调用链为:
open-> __open() -> binder_open()。 open()为用户空间的方法,__open()便是系统调用中相应的处理方法,通过查找,
对应调用到内核binder驱动的binder_open()方法,至于其他的从用户态陷入内核态的流程也基本一致。
简单说,当用户空间调用open()方法,最终会调用binder驱动的binder_open()方法; ioctl()方法也是同理,在BInder
系列的后续文章从用户态进入内核态,都依赖于系统调用过程。
Kernel/drivers/staging/android/ 路径下的binder.c的binder_open方法如下,
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
proc = kzalloc(sizeof(*proc), GFP_KERNEL); // 为binder_proc结构体在分配kernel内存空间
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
proc->tsk = current; //将当前线程的task保存到binder进程的tsk
INIT_LIST_HEAD(&proc->todo); //初始化todo列表
init_waitqueue_head(&proc->wait); //初始化wait队列
proc->default_priority = task_nice(current); //将当前进程的nice值转换为进程优先级
binder_lock(__func__); //同步锁,因为binder支持多线程访问
binder_stats_created(BINDER_STAT_PROC); //BINDER_PROC对象创建数加1
hlist_add_head(&proc->proc_node, &binder_procs); //将proc_node节点添加到binder_procs为表头的队列
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
filp->private_data = proc; //file文件指针的private_data变量指向binder_proc数据
binder_unlock(__func__); //释放同步锁
return 0;
}
创建binder_proc对象,并把当前进程等信息保存到binder_proc对象,该对象管理IPC所需的各种信息并拥有其他
结构体的根结构体;再把binder_proc对象保存到文件指针filp,以及把binder_proc加入到全局链表binder_procs。
Binder驱动中通过static HLIST_HEAD(binder_procs);,创建了全局的哈希链表binder_procs,用于保存所有的
binder_proc队列,每个进程新创建的binder_proc对象都会加入binder_procs链表中。
binder_proc结构体重要的部分如下,
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;
. . . .
};
其中的那4个rb_root域,“rb”的意思是“red black”,可见binder_proc里搞出了4个红黑树。
其中,nodes树用于记录binder实体,refs_by_desc树和refs_by_node树则用于记录binder代理。之所以会有两个
代理树,是为了便于快速查找,我们暂时只关心其中之一就可以了。threads树用于记录执行传输动作的线程信息。
在一个进程中,有多少“被其他进程进行跨进程调用的”binder实体,就会在该进程对应的nodes树中生成多少个红
黑树节点。另一方面,一个进程要访问多少其他进程的binder实体,则必须在其refs_by_desc树中拥有对应的引用
节点。
这4棵树的节点类型是不同的,threads树的节点类型为binder_thread,nodes树的节点类型为binder_node,
refs_by_desc树和refs_by_node树的节点类型相同,为binder_ref。这些节点内部都会包含rb_node子结构,该结构
专门负责连接节点的工作,和前文的hlist_node有点儿异曲同工,这也是linux上一个常用的小技巧。我们以nodes树
为例,其示意图如下:
rb_node和rb_root的定义如下:
struct rb_node
{
unsigned long rb_parent_color;
#define RB_RED 0
#define RB_BLACK 1
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
/* The alignment might seem pointless, but allegedly CRIS needs it */
struct rb_root
{
struct rb_node *rb_node;
};
binder_node的定义如下:
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;
};
nodes树是用于记录binder实体的,所以nodes树中的每个binder_node节点,必须能够记录下相应binder实体的
信息。因此请大家注意binder_node的ptr域和cookie域。
另一方面,refs_by_desc树和refs_by_node树的每个binder_ref节点则和上层的一个BpBinder对应,而且更重要
的是,它必须具有和“目标binder实体的binder_node”进行关联的信息。binder_ref的定义如下:
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; // 注意这个node域
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
请注意那个node域,它负责和binder_node关联。另外,binder_ref中有两个类型为rb_node的域:rb_node_desc
域和rb_node_node域,它们分别用于连接refs_by_desc树和refs_by_node。也就是说虽然binder_proc中有两棵
引用树,但这两棵树用到的具体binder_ref节点其实是复用的。
上图只表示了从进程1向进程2发起跨进程传输的意思,其实反过来也是可以的,即进程2也可以通过自己的“引用
树”节点找到进程1的“实体树”节点,并进行跨进程传输。大家可以自己补充上图。
OK,现在我们可以更深入地说明binder句柄的作用了,比如进程1的BpBinder在发起跨进程调用时,向binder驱动
传入了自己记录的句柄值,binder驱动就会在“进程1对应的binder_proc结构”的引用树中查找和句柄值相符的
binder_ref节点,一旦找到binder_ref节点,就可以通过该节点的node域找到对应的binder_node节点,这个目标
binder_node当然是从属于进程2的binder_proc啦,不过不要紧,因为binder_ref和binder_node都处于binder驱
动的地址空间中,所以是可以用指针直接指向的。目标binder_node节点的cookie域,记录的其实是进程2中BBinder
的地址,binder驱动只需把这个值反映给应用层,应用层就可以直接拿到BBinder了。这就是Binder完成精确打击的
大体过程。
5,小结
1,Java层的系统服务所在进程和native的系统服务所在进程都会在进程启动时打开binder驱动。
2,一个进程中仅有一个ProcessState对象,相当于全局变量,但是有多个IPCThreadState对象。
3,打开binder驱动之后,每个进程都会将进程信息写入binder_proc结构中。
4, servicemanager进程首先通知Binder驱动程序它是守护进程,然后进入循环等待请求的到来。