分析
该小节为额外了解小节,Binder驱动是amdroid底层支持之一,如果深入了解Binder当然能更好的理解使用Binder驱动的程序和库,但是不想了解也没有关系,就好像一个厨师会炒蛋就行了,并不需要他了解蛋的成分。
所谓的情景分析,就是沿着驱动程序对源码进行分析,我们后面会引入以下概念:
binder_ref
binder_node
binder_proc
binder_thread
binder_buffer
先贴出他们的关系框图:
上节提到IPC。他是通过binder实现进程间的通信,上小节中也做了实验,大家都知道通信的三要素,分别为源,目的,数据。通过前面的学习我们知道client与server之间,通过
int binder_call(struct binder_state *bs,struct binder_io *msg, struct binder_io *reply,uint32_t target, uint32_t code)
在client调用该函数前,我们先要构建struct binder_io *msg,确定想要获得的uint32_t target服务(上节中的handle,注意不是进程),然后调用该函数。实际上handle是服务的引用。
比如我们经常使用open打开设备文件,然后获得一个int型的fd,fd就是这个设备文件的引用,同样,我们获取一个服务的时候,得到一个int型的handle,这个handle就是这个服务的引用。在不同的进程中,handle的值可能是不一样的,在两个不同应用程序中,我们使用open打开同一个.txt文件,得到的fd也可能是不一样的。
handle是进程A对进程B提供服务X的引用,我们一个程序可以获得多个handle,每个handle的值都是不相同的。上节在client.c中我们通过
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
获取服务,然后在调用 binder_call实现通信,在驱动的内部,存在一个结构体用来描述服务的引用,为struct binder_ref(服务的引用):
struct binder_ref {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* desc + proc => ref (transaction, inc/dec ref) */
/* node => refs + procs (proc exit) */
struct binder_ref_data data;
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;
struct binder_ref_death *death;
};
服务的引用使用struct binder_ref描述,那么服务本身使用什么描述呢?
binder_node:
struct binder_node {
int debug_id;
spinlock_t lock;
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;
int tmp_refs;
binder_uintptr_t ptr;
binder_uintptr_t cookie;
struct {
/*
* bitfield elements protected by
* proc inner_lock
*/
u8 has_strong_ref:1;
u8 pending_strong_ref:1;
u8 has_weak_ref:1;
u8 pending_weak_ref:1;
};
struct {
/*
* invariant after initialization
*/
u8 sched_policy:2;
u8 inherit_rt:1;
u8 accept_fds:1;
u8 min_priority;
};
bool has_async_transaction;
struct list_head async_todo;
};
驱动程序根据binder_ref中的binder_node成员,就能找到对应的binder_node(服务)。然后通过binder_node中的struct binder_proc *proc成员,就能找到进程B,struct binder_proc *proc用来描述一个进程
struct binder_proc *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;
struct list_head waiting_threads;
int pid;
struct task_struct *tsk;
struct files_struct *files;
struct mutex files_lock;
struct hlist_node deferred_work_node;
int deferred_work;
bool is_dead;
struct list_head todo;
struct binder_stats stats;
struct list_head delivered_death;
int max_threads;
int requested_threads;
int requested_threads_started;
int tmp_ref;
struct binder_priority default_priority;
struct dentry *debugfs_entry;
struct binder_alloc alloc;
struct binder_context *context;
spinlock_t inner_lock;
spinlock_t outer_lock;
};
现在我们总结一下,如果进程A想传输数据给进程B,进程A调用ioctl传入一个handle,根据handle找到相应的binder_ref,在内核中,传入的handle会与所有的binder_ref中的binder_ref.binder_ref_data.desc进行比较,如果相同,代表匹配成功,找到相应的binder_re,然后根据binder_re找到binder_proc(B进程)。
在真实的应用场景中,有多个handle程序要求进程B提供服务,那么进程B就会创建多个线程来提供服务,所以需要一个结构体来管理这些线程,binder_proc.threads是一个红黑树,挂载多个线程,其中每个线程使用结构体binder_thread表示:
struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node;
struct list_head waiting_thread_node;
int pid;
int looper; /* only modified by this thread */
bool looper_need_return; /* can be written by other thread */
struct binder_transaction *transaction_stack;
struct list_head todo;
struct binder_error return_error;
struct binder_error reply_error;
wait_queue_head_t wait;
struct binder_stats stats;
atomic_t tmp_ref;
bool is_dead;
struct task_struct *task;
};
每个线程都存在一个binder_thread结构体,下面我们细化的总结一下,传输过程如下:
1server传入flat_binder_object结构体,根据该结构体为每个服务创建(驱动程序)binder_node,binder_node.proc = server进程。
2.service_manege(驱动程序)创建binder_ref,引用binder_node,binder_ref.binder_ref_data.desc会随着服务的注册逐渐增加,在用户态创建服务链表,每个节点包括name(服务名),与handle(等于binder_ref.binder_ref_data.desc).
3.client向service_manege查询 服务,向其传入一个name,然后service_manege返回一个handle给驱动程序。
4.驱动程序在service_manage的binder_ref红黑树,找到对应的binder_ref,再根据binder_ref.node找到binder_node,最后给这个cluent创建新的binder_ref.,他的binder_ref.binder_ref_data.desc也是从一开始。驱动返回desc给应用层,他就是handle。
6.当client发送数给handle,驱动根据handle找到binder_ref(每个进程都有一系列的binder_ref),然后根据binder_ref)找到binder_node,根据binder_node找到server进程
那么AB进程之间的数据是什么传输的呢?框图如下:
数据如何复制呢?为了提高传输效率,会使用mmp,让用户态可以直接操作内核态,从而提高速率。如果不是使用mmp如何传输数据呢?
一般方法如下(需要两次复制,效率低):
1. client构造数据,驱动copy_from_user,然后传递给server驱动。
2. server驱动使用copy_to_user,传送给server用户态,进行解析。
binder的方法(只需要一次复制):
1. server使用mmp,这样用户态能直接访问驱动中的某块buff。
2. client 构造数据,然后通过驱动copy_from_user,拷贝到buff,然后server可以直接访问该buff,并且直接获得数据
会涉及一个binder_buff结构体,在后续为大家讲解
小节结语
该小节主要讲解了binder_ref,binder_node,binder_proc,binder_thread,binder_buffer结构体之间的关系。
首先server注册服务,server_manage会根据注册的先后顺序分配handle,从0开始。
每个handle与注册服务的name是一一对应的,进程每注册一个server之后,会为其分
配一个binder_node来描述这个server。client通过name获得对应的handel,每个
handel都会分配一个binder_ref对其描述,通过该binder_ref找到对应的
binder_node,在根据binder_node找到对应的binder_proc,这样就实现了进程
之间的通信