Binder系列4 Binder传输原理之请求发起

一 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传输原理之驱动路由 中介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值