一 Binder是如何做到精确打击的?
我们先问一个问题,Binder 机制到底是如何从代理对象 BpBinder 找到其对应的 Binder 实体呢?难道它有某种制导装置吗?要回答这个问题,我们只能静下心来研究 Binder 驱动的代码。在 Binder系列2 Binder概念及相关接口和类 中,我们曾经介绍过 ProcessState,这个结构是属于应用层次的东西,仅靠它当然无法完成精确打击。其实在 Binder 驱动层,还有个与之相对应的结构,叫做 binder_proc。为了说明问题,我修改了系列2中的示意图,得到下图:
1.1 创建binder_proc
在系列2中我们知道当构造 ProcessState 并打开 Binder 驱动之时,会调用到驱动层的 binder_open() 函数,而 binder_proc 就是在 binder_open() 函数中创建的。新创建的 binder_proc 会作为一个节点,插入一个总链表 binder_procs 中。binder_open() 函数在系列3介绍SMgr 中已经介绍过了,咱们再次回顾下,具体代码可参考 kernel/drivers/staging/android/Binder.c。
驱动层的 binder_open() 的代码如下:
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc; //对应的进程结构体
struct binder_device *binder_dev;//对应的binder_device
........
proc = kzalloc(sizeof(*proc), GFP_KERNEL);//在驱动层创建binder_proc
........
INIT_LIST_HEAD(&proc->todo); //初始化进程的todo等待队列
........
//获取binder_device,这个binder_device在Binder驱动初始化的时候生成
binder_dev = container_of(filp->private_data, struct binder_device,
miscdev);
//每个进程的context都对应着同一个binder_dev的context
proc->context = &binder_dev->context;
binder_alloc_init(&proc->alloc);
binder_stats_created(BINDER_STAT_PROC);
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
INIT_LIST_HEAD(&proc->waiting_threads);
//把代表当前进程的binder_proc赋值给filp的private_data
//这个非常重要,以后Binder驱动就是根据这个域找到对应的是哪个进程的.
filp->private_data = proc;
mutex_lock(&binder_procs_lock);
//把这个binder_proc链入到全局的 binder_procs中
hlist_add_head(&proc->proc_node, &binder_procs);
mutex_unlock(&binder_procs_lock);
........
return 0;
}
注意,新创建的 binder_proc 会被记录在参数 filp 的 private_data 域中,以后每次执行 binder_ioctl(),都会从 filp->private_data 域重新读取 binder_proc 的。
binder_procs 总表的定义如下:
static HLIST_HEAD(binder_procs);
我们可以在List.h中看到 HLIST_HEAD 的定义:
tools/include/linux/list.h
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
于是 binder_procs 的定义相当于:
struct hlist_head binder_procs = { .first = NULL };
随着后续不断向 binder_procs 表中添加节点,这个表会不断加长,示意图如下:
1.2 binder_proc中的4棵红黑树
binder_proc 里含有很多重要内容,不过目前我们只需关心其中的几个域:
struct binder_proc {
struct hlist_node proc_node;//用于链接binder_proc到binder_procs中
struct rb_root threads;//Binder线程树,一系列binder_thread的集合
struct rb_root nodes;//Binder节点树,一系列binder_node的集合
struct rb_root refs_by_desc;//引用树,用句柄做索引,一系列binder_ref的集合
struct rb_root refs_by_node;//引用树,用binder_node做索引,一系列binder_ref的集合
struct list_head waiting_threads;
int pid;
struct task_struct *tsk;
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;
};
其它域我们暂时先不关注,重点关注其中的那4个 rb_root 域,“rb” 的意思是 “red black”,可见 binder_proc 里搞出了4棵红黑树,这四颗红黑树是 Binder 机制实现精确打击的关键。
其中,nodes 树是由一系列的 binder_node 节点组成的,所以叫 nodes 树,其中的每一个 binder_node 节点代表了一个 Binder 实体;refs_by_desc 树和 refs_by_node 树则用于记录 Binder 引用,它是由一系列的 binder_ref 引用组成的。之所以会有两棵引用树,是为了便于分别以句柄和 binder_node 为索引实现快速查找,他们的节点是复用的,不会有空间的浪费。threads 树用于记录执行传输动作的线程信息,是由一系列的 binder_thread 组成的。
在一个进程中,有多少"被其他进程进行跨进程调用的"Binder 实体,就会在该进程对应的 nodes 树中生成多少个 binder_node 红黑树节点。另一方面,一个进程要访问多少其他进程的 Binder 实体,则必须在其 refs_by_desc 树中拥有对应的引用节点 binder_ref.
上面已经说明了,这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;
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
struct rb_root {
struct rb_node *rb_node;
};
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 {
u8 has_strong_ref:1;
u8 pending_strong_ref:1;
u8 has_weak_ref:1;
u8 pending_weak_ref:1;
};
struct {
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_node 我们在 Binder系列1 Binder总体设计思想 中已经做过详细介绍,同学们可以回忆下,我们前文已经说过,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 {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* desc + proc => ref (transaction, inc/dec ref) */
/* node => refs + procs (proc exit) */
struct binder_ref_data data;//binder_ref_data中的desc域记录了这个引用的句柄值
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;//指向与此联系的一个binder_node
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 节点其实是复用的。
大家应该还记得,在系列2中我们是这样表述 BpBinder 和 BBinder 关系的:
现在,我们有了 binder_ref 和 binder_node 的知识,可以再丰富下这张图,来解释 BpBinder 到底是如何和 BBinder 联系上的:
上图只表示了从进程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 机制完成精确打击的大体过程。
二 BpBinder和IPCThreadState
接下来我们来谈谈 Binder 传输机制的具体实现。
在系列2中,我们已经提到了 BpBinder 和 ProcessState。当时只是说 BpBinder 是代理端的核心,主要负责跨进程传输,并且不关心所传输的内容。而 ProcessState 则是进程状态的记录器,它里面记录着打开 Binder 驱动后得到的句柄值。因为我们并没有进一步展开来讨论BpBinder 和 ProcessState,所以也就没有进一步打通 BpBinder 和 ProcessState 之间的关系。现在,我们试着补充一些内容。
作为代理端的核心,BpBinder 总要通过某种方式和 Binder 驱动打交道,才可能完成跨进程传递语义的工作。既然 Binder 驱动对应的句柄在ProcessState 中记着,那么现在就要看 BpBinder 如何和 ProcessState 联系了。此时,我们需要提到 IPCThreadState。
从名字上看,IPCThreadState 是"跨进程通信(IPC)相关的线程状态"。那么很显然,一个具有多个线程的进程里应该会有多个IPCThreadState 对象了,只不过每个线程只需一个 IPCThreadState 对象而已。这有点儿"局部单例"的意思。所以,在实际的代码中,IPCThreadState 对象是存放在线程的局部存储区(TLS)里的。
2.1 BpBinder的transact()动作
每当我们利用 BpBinder 的 transact() 函数发起一次跨进程事务时,其内部其实是调用 IPCThreadState 对象的 transact().BpBinder 的 transact() 代码如下:
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
if (mAlive) {
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
当然,进程中的一个 BpBinder 有可能被多个线程使用,所以发起传输的 IPCThreadState 对象可能并不是同一个对象,但这没有关系,因为这些 IPCThreadState 对象最终使用的是同一个 ProcessState 对象。
2.1.1 调用IPCThreadState的transact()
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
........
if (err == NO_ERROR) {
LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
(flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
........
if ((flags & TF_ONE_WAY) == 0) {
........
if (reply) {
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
........
}
} else {
err = waitForResponse(NULL, NULL);
}
return err;
}
我们可以看到 IPCThreadState::transact() 会先调用 writeTransactionData() 函数将 data 数据整理进内部的 mOut 包中,这个函数的代码如下:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
} else if (statusBuffer) {
........
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
接着 IPCThreadState::transact() 会考虑本次发起的事务是否需要回复。不需要等待回复的事务,在其 flag 标志中会含有 TF_ONE_WAY,表示一去不回头。而需要等待回复的事务,则需要在传递时提供记录回复信息的 Parcel 对象 reply,一般发起 transact() 的用户会提供这个 Parcel 对象,如果不提供,transact() 函数内部会临时构造一个 fakeReply 的 Parcel 对象。
上面代码中,实际完成跨进程事务的是 waitForResponse() 函数,waitForResponse() 的代码截选如下:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
while (1) {
// talkWithDriver()内部会完成跨进程事务
if ((err=talkWithDriver()) < NO_ERROR) break;
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;
cmd = (uint32_t)mIn.readInt32();
........
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
case BR_DEAD_REPLY:
err = DEAD_OBJECT;
goto finish;
case BR_FAILED_REPLY:
err = FAILED_TRANSACTION;
goto finish;
........
default:
// 注意这个executeCommand(),它会处理BR_TRANSACTION
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
........
}
2.1.2 talkWithDriver()
waitForResponse() 中是通过调用 talkWithDriver() 来和 Binder 驱动打交道的,说到底会调用 ioctl() 函数。因为 ioctl() 函数在传递 BINDER_WRITE_READ 语义时,既会使用"输入buffer",也会使用"输出buffer",所以 IPCThreadState 专门搞了两个 Parcel 类型的成员变量:mIn 和 mOut。总之就是,mOut 中的内容发出去,发送后的回复写进 mIn, talkWithDriver 的代码截选如下:
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
........
binder_write_read bwr;
// Is the read buffer empty?
//输入缓冲区mIn中的数据全部处理完毕时, 才需要读取, 即needRead为true
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
//doReceive默认为true,若needRead为true, 即mIn数据已全部处理完时, 此时才可以写数据, 设置outAvail为mOut.dataSize()
//若needRead为false, 即mIn中数据还没处理完, 则优先处理读数据, 暂时不写数据, 设置outAvail为0
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();//设置输出缓冲区数据
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();//设置输入缓冲区数据
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
........
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
........
//通过ioctl函数和Binder驱动交互
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
........
} while (err == -EINTR);
........
if (err >= NO_ERROR) {
if (bwr.write_consumed > 0) {//清空mOut中已经写入到Binder驱动的数据
if (bwr.write_consumed < mOut.dataSize())
mOut.remove(0, bwr.write_consumed);
else
mOut.setDataSize(0);
}
if (bwr.read_consumed > 0) {//从Binder驱动已经读取的数据设置到mIn中
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return err;
}
看到了吗?mIn 和 mOut 的 data 会先整理进一个 binder_write_read 结构,然后再调用 ioctl() 函数。而最关键的一句,当然就是那句 ioctl() 了。此时使用的文件描述符就是前文我们说的 ProcessState 中记录的 mDriverFD,说明是向 Binder 驱动传递语义。在系列1中我们已经知道 BINDER_WRITE_READ 表示我们希望读写一些数据。
至此,应用程序通过 BpBinder 向远端发起传输的发起过程就交代完了,数据传到了 Binder 驱动,一切就看 Binder 驱动怎么做了。至于驱动层又做了哪些动作,我们将在下一篇文章 Binder系列5 Binder传输原理之驱动路由 中介绍。