Ahab's Studio.

细读《深入理解 Android 内核设计思想》(五)Binder 机制 [下]

字数统计: 5.9k阅读时长: 24 min
2020/05/03 Share

对冗余挑拣重点,对重点深入补充,输出结构清晰的精简版

  1. 深入 binder 驱动内部
    1. binder_ioctl
    2. binder_get_thread
    3. binder_ioctl_write_read
    4. binder_thread_write
    5. binder_transaction
    6. binder_thread_read
    7. 小结
  2. binder Q&A
    1. 如何找到目标进程 Binder 实体
    2. 如何实现 Binder 线程的睡眠与唤醒
  3. 最后

深入 binder 驱动内部

前两篇文章都有提到 binder_ioctl 方法,在 Binder 机制 [上] 中介绍了 binder_ioctl 支持的命令;Binder 机制 [中] 中提到 IPCThreadState 会调用到 binder_ioctl 方法。

书中对 binder 驱动内部调用的讲解没有分为较清晰的步骤,一口气就是 20 页篇幅的源码详解,理解起来有些难度,容易迷失。在细读了三四遍后,终于感觉对整体有些掌握了,结合前面的学习与自己的理解,将一次 IPC 调用中 binder 驱动的工作分为以下 5 步:

1.准备数据,根据命令分发给具体的方法去处理
2.找到目标进程的相关信息
3.将数据一次拷贝到目标进程所映射的物理内存块
4.记录待处理的任务,唤醒目标线程
5.调用线程进入休眠
6.目标进程直接拿到数据进行处理,处理完后唤醒调用线程
7.调用线程返回处理结果

与上篇文章一样仍以 getService() 为例,按照上面的工作步骤为脉络,深入分析驱动层中的执行逻辑,彻底搞定 binder 驱动!

binder_ioctl

在 IPCThreadState 中这样调用了 binder_ioctl() 方法:

1
ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)

binder_ioctl() 方法中会根据 BINDER_WRITE_READ、BINDER_SET_MAX_THREADS 等不同 cmd 转调到不同的方法去执行,这里我们只关注 BINDER_WRITE_READ,简化后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
int ret;
//拿到调用进程在 binder_open() 中记录的 binder_proc
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
binder_lock(__func__);
//获取调用线程 binder_thread
thread = binder_get_thread(proc);
switch (cmd) {
case BINDER_WRITE_READ:
//处理 binder 数据读写,binder IPC 通信的核心逻辑
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
case BINDER_SET_MAX_THREADS:{...} //设置 binder 最大线程数
case BINDER_SET_CONTEXT_MGR:{...} //设置 service 大管家,即 ServiceManager
case BINDER_THREAD_EXIT:{...} //binder 线程退出命令,释放相关资源
case BINDER_VERSION: {...} //获取 binder 驱动版本号
...
}

Binder 机制 [上] 中详细介绍过 binder_open() 方法,它主要做了两个工作:1.创建及初始化每个进程独有一份的、用来存放 binder 相关数据的 binder_proc 结构体,2.将 binder_proc 记录起来,方便后续使用。正是通过 file 来记录的:

1
2
3
4
5
static int binder_open(struct inode *nodp, struct file *filp){
...
filp->private_data = proc;
...
}

拿到调用进程后,进一步通过 binder_get_thread() 方法拿到调用线程,然后就交给 binder_ioctl_write_read() 方法去执行具体的 binder 数据读写了,可见 binder_ioctl() 方法本身的逻辑非常简单,将数据 arg 透传了出去。下面分别来看 binder_get_thread()、binder_ioctl_write_read() 这两个方法。

binder_get_thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static struct binder_thread *binder_get_thread(struct binder_proc *proc){
struct binder_thread *thread = NULL;
struct rb_node *parent = NULL;
struct rb_node **p = &proc->threads.rb_node; //从 proc 中获取红黑树根节点
//查找 pid 等于当前线程 id 的thread,该红黑树以 pid 大小为序存放
while (*p) {
parent = *p;
thread = rb_entry(parent, struct binder_thread, rb_node);
if (current->pid < thread->pid) //current->pid 是当前调用线程的 id
p = &(*p)->rb_left;
else if (current->pid > thread->pid)
p = &(*p)->rb_right;
else
break;
}
if (*p == NULL) {//如果没有找到,则新创建一个
thread = kzalloc(sizeof(*thread), GFP_KERNEL);
if (thread == NULL)
return NULL;
binder_stats_created(BINDER_STAT_THREAD);
thread->proc = proc;
thread->pid = current->pid;
init_waitqueue_head(&thread->wait); //初始化等待队列
INIT_LIST_HEAD(&thread->todo); //初始化待处理队列
rb_link_node(&thread->rb_node, parent, p); //加入到 proc 的 threads 红黑树中
rb_insert_color(&thread->rb_node, &proc->threads);
thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
thread->return_error = BR_OK;
thread->return_error2 = BR_OK;
}
return thread;
}

binder_thread 是用来描述线程的结构体,binder_get_thread() 方法中逻辑也很简单,首先从调用进程 proc 中查找当前线程是否已被记录,如果找到就直接返回,否则新建一个返回,并记录到 proc 中。也就是说所有调用 binder_ioctl() 的线程,都会被记录起来。

binder_ioctl_write_read

此方法分为两部分来看,首先是整体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread){
int ret = 0;
struct binder_proc *proc = filp->private_data;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg; //用户传下来的数据赋值给 ubuf
struct binder_write_read bwr;
//把用户空间数据 ubuf 拷贝到 bwr
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
处理数据...
//将读写后的数据写回给用户空间
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
out:
return ret;
}

起初看到 copy_from_user() 方法时难以理解,因为它看起来是将我们要传输的数据拷贝到内核空间了,但目前还没有看到 server 端的任何线索,bwr 跟 server 端没有映射关系,那后续再将 bwr 传输给 server 端的时候又要拷贝,这样岂不是多次拷贝了?

其实这里的 copy_from_user() 方法并没有拷贝要传输的数据,而仅是拷贝了持有传输数据内存地址的 bwr。后续处理数据时会根据 bwr 信息真正的去拷贝要传输的数据。

处理完数据后,会将处理结果体现在 bwr 中,然后返回给用户空间处理。那是如何处理数据的呢?所谓的处理数据,就是对数据的读写而已:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if (bwr.write_size > 0) {//写数据
ret = binder_thread_write(proc,
thread,
bwr.write_buffer, bwr.write_size,
&bwr.write_consumed);
trace_binder_write_done(ret);
if (ret < 0) { //写失败
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
if (bwr.read_size > 0) {//读数据
ret = binder_thread_read(proc, thread, bwr.read_buffer,
bwr.read_size,
&bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
trace_binder_read_done(ret);
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait);//唤醒等待状态的线程
if (ret < 0) { //读失败
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}

可见 binder 驱动内部依赖用户空间的 binder_write_read 决定是要读取还是写入数据:其内部变量 read_size>0 则代表要读取数据,write_size>0 代表要写入数据,若都大于 0 则先写入,后读取。

至此焦点应该集中在 binder_thread_write() 和 binder_thread_read(),下面分析这两个方法。

binder_thread_write

在上面的 binder_ioctl_write_read() 方法中调用 binder_thread_write() 时传入了 bwr.write_buffer、bwr.write_size 等,先搞清楚这些参数是什么。

最开始是在用户空间 IPCThreadState 的 transact() 中通过 writeTransactionData() 方法创建数据并写入 mOut 的,writeTransactionData 方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
tr.target.handle = handle; //目标 server 的 binder 的句柄
tr.code = code; //请求码,getService() 服务对应的是 GET_SERVICE_TRANSACTION
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 {...}
mOut.writeInt32(cmd); // transact 传入的 cmd 是 BC_TRANSACTION
mOut.write(&tr, sizeof(tr)); //打包成 binder_transaction_data
return NO_ERROR;
}

然后在 IPCThreadState 的 talkWithDriver() 方法中对 write_buffer 赋值:

1
bwr.write_buffer = (uintptr_t)mOut.data();

搞清楚了数据的来源,再来看 binder_thread_write() 方法,binder_thread_write() 方法中处理了大量的 BC_XXX 命令,代码很长,这里我们只关注当前正在处理的 BC_TRANSACTION 命令,简化后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed){
uint32_t cmd;
void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //就是 bwr.write_buffer
void __user *ptr = buffer + *consumed; //数据起始地址
void __user *end = buffer + size; //数据结束地址
while (ptr < end && thread->return_error == BR_OK) { //可能有多个命令及对应数据要处理,所以要循环
if (get_user(cmd, (uint32_t __user *)ptr)) // 读取一个 cmd
return -EFAULT;
ptr += sizeof(uint32_t); //跳过 cmd 所占的空间,指向要处理的数据
switch (cmd) {
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr; //与 writeTransactionData 中准备的数据结构体对应
if (copy_from_user(&tr, ptr, sizeof(tr))) //拷贝到内核空间 tr 中
return -EFAULT;
ptr += sizeof(tr); //跳过数据所占空间,指向下一个 cmd
binder_transaction(proc, thread, &tr, cmd == BC_REPLY); //处理数据
break;
}
处理其他 BC_XX 命令...
}
*consumed = ptr - buffer; //被写入处理消耗的数据量,对应于用户空间的 bwr.write_consumed

binder_thread_write() 中从 bwr.write_buffer 中取出了 cmd 和 cmd 对应的数据,进一步交给 binder_transaction() 处理,需要注意的是,BC_TRANSACTION、BC_REPLY 这两个命令都是由 binder_transaction() 处理的。

简单梳理一下,由 binder_ioctl -> binder_ioctl_write_read -> binder_thread_write ,到目前为止还只是在准备数据,没有看到跟目标进程相关的任何处理,都属于 “准备数据,根据命令分发给具体的方法去处理” 第 1 个工作。而到此为止,第 1 个工作便结束,下一步的 binder_transaction() 方法终于要开始后面的工作了。

binder_transaction

binder_transaction() 方法中代码较长,先总结它干了哪些事:对应开头列出的工作,此方法中做了非常关键的 2-4 步:

  • 找到目标进程的相关信息
  • 将数据一次拷贝到目标进程所映射的物理内存块
  • 记录待处理的任务,唤醒目标线程

以这些工作为线索,将代码分为对应的部分来看,首先是找到目标进程的相关信息,简化后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
struct binder_transaction *t; //用于描述本次 server 端要进行的 transaction
struct binder_work *tcomplete; //用于描述当前调用线程未完成的 transaction
binder_size_t *offp, *off_end;
struct binder_proc *target_proc; //目标进程
struct binder_thread *target_thread = NULL; //目标线程
struct binder_node *target_node = NULL; //目标 binder 节点
struct list_head *target_list; //目标 TODO 队列
wait_queue_head_t *target_wait; //目标等待队列
if(reply){
in_reply_to = thread->transaction_stack;
...处理 BC_REPLY,暂不关注
}else{
//处理 BC_TRANSACTION
if (tr->target.handle) { //handle 不为 0
struct binder_ref *ref;
//根据 handle 找到目标 binder 实体节点的引用
ref = binder_get_ref(proc, tr->target.handle);
target_node = ref->node; //拿到目标 binder 节点
} else {
// handle 为 0 则代表目标 binder 是 service manager
// 对于本次调用来说目标就是 service manager
target_node = binder_context_mgr_node;
}
}
target_proc = target_node->proc; //拿到目标进程
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
while (tmp) {
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from; //拿到目标线程
tmp = tmp->from_parent;
}
}
target_list = &target_thread->todo; //拿到目标 TODO 队列
target_wait = &target_thread->wait; //拿到目标等待队列

binder_transaction、binder_work 等结构体在上一篇中有介绍,上面代码中也详细注释了它们的含义。比较关键的是 binder_get_ref() 方法,它是如何找到目标 binder 的呢?这里暂不延伸,下文再做分析。

继续看 binder_transaction() 方法的第 2 个工作,将数据一次拷贝到目标进程所映射的物理内存块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
t = kzalloc(sizeof(*t), GFP_KERNEL); //创建用于描述本次 server 端要进行的 transaction
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); //创建用于描述当前调用线程未完成的 transaction
if (!reply && !(tr->flags & TF_ONE_WAY)) //将信息记录到 t 中:
t->from = thread; //记录调用线程
else
t->from = NULL;
t->sender_euid = task_euid(proc->tsk);
t->to_proc = target_proc; //记录目标进程
t->to_thread = target_thread; //记录目标线程
t->code = tr->code; //记录请求码,getService() 对应的是 GET_SERVICE_TRANSACTION
t->flags = tr->flags;
//实际申请目标进程所映射的物理内存,准备接收要传输的数据
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
//申请到 t->buffer 后,从用户空间将数据拷贝进来,这里就是一次拷贝数据的地方!!
if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
tr->data.ptr.buffer, tr->data_size)) {
return_error = BR_FAILED_REPLY;
goto err_copy_data_failed;
}

为什么在拷贝之前要先申请物理内存呢?在 Binder 机制 [上] 中介绍 binder_mmap() 时详细分析过,虽然 binder_mmap() 直接映射了 (1M-8K) 的虚拟内存,但却只申请了 1 页的物理页面,等到实际使用时再动态申请。也就是说,在 binder_ioctl() 实际传输数据的时候,再通过 binder_alloc_buf() 方法去申请物理内存。

至此已经将要传输的数据拷贝到目标进程,目标进程可以直接读取到了,接下来要做的就是将目标进程要处理的任务记录起来,然后唤醒目标进程,这样在目标进程被唤醒后,才能知道要处理什么任务。

最后来看 binder_transaction() 方法的第 3 个工作,记录待处理的任务,唤醒目标线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (reply) { //如果是处理 BC_REPLY,pop 出来栈顶记录的 transaction(实际上是删除链表头元素)
binder_pop_transaction(target_thread, in_reply_to);
} else if (!(t->flags & TF_ONE_WAY)) {
//如果不是 oneway,将 server 端要处理的 transaction 记录到当前调用线程
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
} else {
...暂不关注 oneway 的情况
}
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list); //加入目标的处理队列中
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; //设置调用线程待处理的任务类型
list_add_tail(&tcomplete->entry, &thread->todo); //记录调用线程待处理的任务
if (target_wait)
wake_up_interruptible(target_wait); //唤醒目标线程

再次梳理一下,至此已经完成了前四个工作:

1.准备数据,根据命令分发给具体的方法去处理
2.找到目标进程的相关信息
3.将数据一次拷贝到目标进程所映射的物理内存块
4.记录待处理的任务,唤醒目标线程

其中第 1 个工作涉及到的方法为 binder_ioctl() -> binder_get_thread() -> binder_ioctl_write_read() -> binder_thread_write() ,主要是一些数据的准备和方法转跳,没做什么实质的事情。而 binder_transaction() 方法中做了非常重要的 2-4 工作。

剩下的工作还有:

5.调用线程进入休眠
6.目标进程直接拿到数据进行处理,处理完后唤醒调用线程
7.调用线程返回处理结果

可以想到,5 和 6 其实没有时序上的限制,而是并行处理的。下面先来看第 5 个工作:调用线程是如何进入休眠等待服务端执行结果的。

binder_thread_read

在唤醒目标线程后,调用线程就执行完 binder_thread_write() 写完了数据,返回到 binder_ioctl_write_read() 方法中,接着执行 binder_thread_read() 方法。

而调用线程的休眠就是在此方法中触发的,下面将 binder_thread_read() 分为两部分来看,首先是是否阻塞当前线程的判断逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed, int non_block){
void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //bwr.read_buffer
void __user *ptr = buffer + *consumed; //数据起始地址
void __user *end = buffer + size; //数据结束地址
if (*consumed == 0) {
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
//是否要准备睡眠当前线程
wait_for_proc_work = thread->transaction_stack == NULL &&
list_empty(&thread->todo);
if (wait_for_proc_work) {
if (non_block) { //non_block 为 false
if (!binder_has_proc_work(proc, thread))
ret = -EAGAIN;
} else
ret = wait_event_freezable_exclusive(proc->wait,
binder_has_proc_work(proc, thread));
} else {
if (non_block) { //non_block 为 false
if (!binder_has_thread_work(thread))
ret = -EAGAIN;
} else
ret = wait_event_freezable(thread->wait,
binder_has_thread_work(thread));
}

consumed 即用户空间的 bwr.read_consumed,这里是 0 ,所以将一个 BR_NOOP 加到了 ptr 中。

怎么理解 wait_for_proc_work 条件呢?在 binder_transaction() 方法中将 server 端要处理的 transaction 记录到了当前调用线程 thread->transaction_stack 中;将当前调用线程待处理的任务记录到了 thread->todo 中。所以这里的 thread->transaction_stack 和 thread->todo 都不为空,wait_for_proc_work 为 false,代表不准备阻塞当前线程。

但 wait_for_proc_work 并不是决定是否睡眠的最终条件,接着往下看,其中 non_block 恒为 false,那是否要睡眠当前线程就取决于 binder_has_thread_work() 的返回值,binder_has_thread_work() 方法如下:

1
2
3
4
static int binder_has_thread_work(struct binder_thread *thread){
return !list_empty(&thread->todo) || thread->return_error != BR_OK ||
(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);
}

thread->todo 不为空,所以 binder_has_thread_work() 返回 true,当前调用线程不进入休眠,继续往下执行。你可能会有疑问,不是说调用线程的休眠就是在 binder_thread_read() 方法中触发的吗?确实是,只不过不是本次,先接着分析 binder_thread_read() 继续往下要执行的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct binder_work *w;
w = list_first_entry(&thread->todo, struct binder_work,entry);
switch (w->type) {
case BINDER_WORK_TRANSACTION_COMPLETE: {
cmd = BR_TRANSACTION_COMPLETE;
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
binder_stat_br(proc, thread, cmd);
list_del(&w->entry); //删除 binder_work 在 thread->todo 中的引用
kfree(w);
}
case BINDER_WORK_NODE{...}
case BINDER_WORK_DEAD_BINDER{...}
...

在上面 binder_transaction() 方法最后,将 BINDER_WORK_TRANSACTION_COMPLETE 类型的 binder_work 加入到 thread->todo 中。而这里就是对这个 binder_work 进行处理,将一个 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。

梳理一下目前的逻辑,至此已经顺序执行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用户空间传输了两个命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。

本次 binder_ioctl() 调用就执行完了,然后会回到 IPCThreadState 中,上一篇文章中详细分析了 IPCThreadState 中的代码,这里就不再展开,简单概括一下后续执行的逻辑:

mIn 中有 BR_NOOP 和 BR_TRANSACTION_COMPLETE 两个命令,首先处理 BR_NOOP 命令,此命令什么也没做,由于 talkWithDriver() 处于 while 循环中,会再一次进入 talkWithDriver(),但因为此时 mIn 中还有数据没读完,不会调用 binder_ioctl()。

然后处理 BR_TRANSACTION_COMPLETE 命令,如果是 oneway 就直接结束本次 IPC 调用,否则再一次进入 talkWithDriver(),第二次进入 talkWithDriver 时,bwr.write_size = 0,bwr.read_size > 0,所以会第二次调用 binder_ioctl() 方法。第二次执行 binder_ioctl() 时,bwr.write_size = 0,bwr.read_size > 0,所以不会再执行 binder_thread_write() 方法,而只执行 binder_thread_read() 方法。

第二次执行 binder_thread_read() 时,thread->todo 已经被处理为空,但是 thread->transaction_stack 还不为空,wait_for_proc_work 仍然为 false,但最终决定是否要休眠的条件成立了: binder_has_thread_work(thread) 返回 false,由此当前调用线程通过 wait_event_freezable() 进入休眠。

小结

至此还剩下两个工作:

6.目标进程直接拿到数据进行处理,处理完后唤醒调用线程
7.调用线程返回处理结果

但是已经不用再看代码了,因为上述方法已经覆盖了剩下的工作。对于 getService() 来说,目标进程就是 Service Manager,相关的代码在 Binder 机制 [上] 也已经做过详细的分析。用图来概括整体的工作。调用进程逻辑:


BpBinder->IPCThreadState:transact()
IPCThreadState->IPCThreadState:writeTransactionData()
IPCThreadState->IPCThreadState:waitForResponse()
IPCThreadState->BinderDriver:talkWithDriver()
BinderDriver->BinderDriver:binder_ioctl()
BinderDriver->BinderDriver:binder_get_thread()
BinderDriver->BinderDriver:binder_ioctl_write_read()
BinderDriver->BinderDriver:binder_thread_write()
BinderDriver->BinderDriver:binder_transaction()
BinderDriver–>BinderDriver:唤醒目标进程
BinderDriver->BinderDriver:binder_thread_read()
BinderDriver–>IPCThreadState:
IPCThreadState->IPCThreadState:处理 BR_NOOP
IPCThreadState->IPCThreadState:处理 BR_TRANSACTION_COMPLETE
IPCThreadState->BinderDriver:talkWithDriver()
BinderDriver->BinderDriver:binder_ioctl()
BinderDriver->BinderDriver:binder_get_thread()
BinderDriver->BinderDriver:binder_ioctl_write_read()
BinderDriver->BinderDriver:binder_thread_read()
BinderDriver–>BinderDriver:进入休眠
BinderDriver–>BinderDriver:目标进程处理完
BinderDriver–>BinderDriver:被唤醒
BinderDriver–>IPCThreadState:
IPCThreadState->IPCThreadState:处理 BR_REPLY

Service Manager 端逻辑:


servicemanager->servicemanager:启动
servicemanager->servicemanager:binder_open()
servicemanager->servicemanager:binder_become_context_manager()
servicemanager->BinderDriver:binder_loop()
BinderDriver->BinderDriver:binder_ioctl()
BinderDriver->BinderDriver:binder_get_thread()
BinderDriver->BinderDriver:binder_ioctl_write_read()
BinderDriver->BinderDriver:binder_thread_read()
BinderDriver–>BinderDriver:进入休眠
BinderDriver–>BinderDriver:其他进程 getService()
BinderDriver–>BinderDriver:被唤醒
BinderDriver–>servicemanager:
servicemanager->servicemanager:binder_parse()
servicemanager->servicemanager:处理 BR_TRANSACTION
servicemanager->servicemanager:do_find_service()
servicemanager->BinderDriver:binder_write(BC_REPLY)
BinderDriver->BinderDriver:binder_ioctl()
BinderDriver->BinderDriver:binder_get_thread()
BinderDriver->BinderDriver:binder_ioctl_write_read()
BinderDriver->BinderDriver:binder_thread_write()
BinderDriver->BinderDriver:binder_transaction()
BinderDriver–>BinderDriver:唤醒调用进程
BinderDriver->BinderDriver:binder_thread_read()
BinderDriver–>servicemanager:
servicemanager->servicemanager:处理 BR_TRANSACTION_COMPLETE
servicemanager->BinderDriver:binder_loop()
servicemanager->BinderDriver:binder_loop() 再次进入休眠

本节完整的分析了一次 IPC 调用中 binder 驱动内部具体的执行逻辑,此部分也是 binder 机制中最难的,而将最难的部分掌握后,可以极大的提高信心。

想要完全掌握本节内容,前提是必须对前两篇 binder 文章已经熟悉,因为 binder 驱动和用户空间的 IPCThreadState 以及 servicemanager 关联是十分紧密的,如果没有掌握,是无法真正理清本节内容的。

binder Q&A

在上面分析 binder 驱动代码过程中,主要是根据文章开头的七个工作为线索,为了不偏离主线,在遇到比较重要的知识点时都没做延伸,这里以 Q&A 的方式补充分析。

如何找到目标进程 Binder 实体

servicemanager 的 binder 实体固定为 binder_context_mgr_node,直接返回即可。如何获取其他 binder 实体呢?在上面的 binder_transaction() 方法中是这样获取目标 binder 的:

1
2
//根据 handle 找到目标 binder 实体节点的引用
ref = binder_get_ref(proc, tr->target.handle);

binder_get_ref() 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static struct binder_ref *binder_get_ref(struct binder_proc *proc,
uint32_t desc){
struct rb_node *n = proc->refs_by_desc.rb_node; //取红黑树根结点
struct binder_ref *ref;
while (n) { //遍历查询指定 desc(handle) 值的 binder 实体
ref = rb_entry(n, struct binder_ref, rb_node_desc);
if (desc < ref->desc)
n = n->rb_left;
else if (desc > ref->desc)
n = n->rb_right;
else
return ref;
}
return NULL;
}

可见是在调用进程的 binder_proc 结构体中获取到的,那目标 binder 实体又是什么时候存储到调用进程中的呢?

对于实名的 Server,当它利用 addService() 把自身加到 ServiceManager 中时,会”路过” binder 驱动,binder 驱动就会把这一 binder 实体记录到 ServiceManager 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

当一个 Binder Client 通过 getService() 向 ServiceManager 发起查询时,ServiceManager 就可以准确的告诉 Client 目标 Binder 实体,将其也记录到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

上面说的是通过 addService() 将自身注册到 Service Manager 中的 Server,任何 Binder Client 都能通过 Service Manager 获取到,这种叫做 “实名” Server。 而 Android 中还存在另一种 Binder Server,并不在 Service Manager 中注册,比如我们自定义的 Service,这种可以叫做 “匿名” Server。

如何找到一个匿名 Server 的 binder 实体呢?匿名 Server 一般要通过其他实名 Server 为中介来传递,比如 IWindowSession 是靠 WindowManagerService 来传递的,当 Binder Client 调用 openSession 真正生成一个 Session 对象,这个对象作为 reply 第一次 “路过” binder 驱动时,就会被记录到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

如何实现 Binder 线程的睡眠与唤醒

答案在上文中已经有很详细的分析了,这里再概括一下:

唤醒:在 binder_transaction() 方法中写完数据后,通过 wake_up_interruptible(target_wait) 唤醒目标线程
睡眠:在 binder_thread_read() 中,通过 wait_event_freezable_exclusive() 或 wait_event_freezable() 调用进入睡眠

最后

最后再对 binder 的整体架构做一个简要的概述。对于一个比较典型的、两个应用之间的 IPC 通信流程而言:

Client 通过 ServiceManager 或 AMS 获取到的远程 binder 实体,一般会用 Proxy 做一层封装,比如 ServiceManagerProxy、 AIDL 生成的 Proxy 类。而被封装的远程 binder 实体是一个 BinderProxy

BpBinder 和 BinderProxy 其实是一个东西:远程 binder 实体,只不过一个 Native 层、一个 Java 层,BpBinder 内部持有了一个 binder 句柄值 handle。

ProcessState 是进程单例,负责打开 Binder 驱动设备及 mmap;IPCThreadState 为线程单例,负责与 binder 驱动进行具体的命令通信。

由 Proxy 发起 transact() 调用,会将数据打包到 Parcel 中,层层向下调用到 BpBinder ,在 BpBinder 中调用 IPCThreadState 的 transact() 方法并传入 handle 句柄值,IPCThreadState 再去执行具体的 binder 命令。

由 binder 驱动到 Server 的大概流程就是:Server 通过 IPCThreadState 接收到 Client 的请求后,层层向上,最后回调到 Stub 的 onTransact() 方法。

当然这不代表所有的 IPC 流程,比如 Service Manager 作为一个 Server 时,便没有上层的封装,也没有借助 IPCThreadState,而是初始化后通过 binder_loop() 方法直接与 binder 驱动通信的。

链接:细读《深入理解 Android 内核设计思想》系列

关注公众号,Get 更多知识点

原文作者:Ahab

原文链接:http://yhaowa.gitee.io/c74cb1a3/

发表日期:May 3rd 2020, 10:02:35 am

更新日期:May 16th 2020, 11:20:22 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 深入 binder 驱动内部
    1. 1.1. binder_ioctl
    2. 1.2. binder_get_thread
    3. 1.3. binder_ioctl_write_read
    4. 1.4. binder_thread_write
    5. 1.5. binder_transaction
    6. 1.6. binder_thread_read
    7. 1.7. 小结
  2. 2. binder Q&A
    1. 2.1. 如何找到目标进程 Binder 实体
    2. 2.2. 如何实现 Binder 线程的睡眠与唤醒
  3. 3. 最后