Android源码分析 - Binder驱动(下)

开篇

本篇以aosp分支android-11.0.0_r25,kernel分支android-msm-wahoo-4.4-android11作为基础解析

上一篇文章Android源码分析 - Binder驱动(中),我们分析了binder_ioctl中的写操作binder_thread_write部分,了解了binder请求的发起与调度,接下来我们就进行binder驱动的最后一部分分析,binder_thread_read

binder_ioctl_write_read

我们还是先从binder_ioctl后的BINDER_WRITE_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
31
32
33
34
35
36
37
38
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;
struct binder_write_read bwr;
...
//将用户空间ubuf拷贝至内核空间bwr
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
...
//当读缓存中有数据,执行binder读操作
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);
//如果todo队列中有未处理的任务,唤醒等待状态下的线程
binder_inner_proc_lock(proc);
if (!binder_worklist_empty_ilocked(&proc->todo))
binder_wakeup_proc_ilocked(proc);
binder_inner_proc_unlock(proc);
if (ret < 0) {
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
...
out:
return ret;
}

binder_thread_read

这是进行binder读操作的函数,这个函数也是比较长,我们同样将它分成几个部分:

  1. 等待可用的binder_work
  2. 循环获取todo队列中的binder_work,并根据binder_worktype,执行一定的处理
  3. 处理binder_transaction以及binder_transaction_data,并将binder_transaction_data拷贝回用户空间

第一部分:等待工作

binder_thread.looper

在此之前我们需要先看一下之前提到的,在binder_thread中的域成员looper,前面我们只是注释了这个域表示线程状态,这里我们介绍一下它有哪些取值:

  • BINDER_LOOPER_STATE_REGISTERED:表示该binder线程是非主binder线程
  • BINDER_LOOPER_STATE_ENTERED:表示该binder线程是主binder线程
  • BINDER_LOOPER_STATE_EXITED:表示该binder线程马上就要退出了
  • BINDER_LOOPER_STATE_INVALID:表示该binder线程是无效的(比如原来是binder主线程,后续用户又发送了一个BC_REGISTER_LOOPER请求)
  • BINDER_LOOPER_STATE_WAITING:表示当前binder线程正在等待请求
  • BINDER_LOOPER_STATE_NEED_RETURN:表示该binder线程在处理完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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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)
{
//用户空间传进来的需要将数据读到的地址
//实际上只是传输一些命令码和一个binder_transaction_data_secctx结构体
//真正的数据已经映射到用户虚拟内存空间中了,根据binder_transaction_data中所给的地址直接读就可以了
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
//起始地址 = 读数据的首地址 + 已读数据大小
void __user *ptr = buffer + *consumed;
//结束地址 = 读数据的首地址 + 读数据的总大小
void __user *end = buffer + size;

int ret = 0;
int wait_for_proc_work;

if (*consumed == 0) {
//向用户空间写一个binder响应码,该响应码不做任何操作
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}

retry:
binder_inner_proc_lock(proc);
//检查是否有可用的工作需要处理
wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
binder_inner_proc_unlock(proc);

//将线程的状态置为等待中
thread->looper |= BINDER_LOOPER_STATE_WAITING;

//如果没有可用的工作,可以等待进程todo队列中的工作
if (wait_for_proc_work) {
//这个binder线程既不是主线程,也没有被注册成binder子线程
//这里条件在binder_available_for_proc_work_ilocked中已经做了判断
//似乎永远不会进入到这个case中?
if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))) {
binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n",
proc->pid, thread->pid, thread->looper);
//进程进入休眠状态,等待唤醒
wait_event_interruptible(binder_user_error_wait,
binder_stop_on_user_error < 2);
}
//恢复优先级
binder_restore_priority(current, proc->default_priority);
}

if (non_block) { //如果是非阻塞模式(这里似乎不会执行到)
//线程和进程的todo队列中都没有工作
if (!binder_has_work(thread, wait_for_proc_work))
ret = -EAGAIN;
} else { //如果是阻塞模式
//等待binder工作到来
ret = binder_wait_for_work(thread, wait_for_proc_work);
}

//将线程的等待中状态解除
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

if (ret)
return ret;
...
}

这一部分先检查是否有可用的binder_work待处理,如果有的话进入到下一部分,如果没有的话则需要等待

具体的,我们先看这个函数中的wait_for_proc_work变量,这个变量表示是否需要等待binder_proc中的工作,当在binder_thread中找不到事务栈并且todo队列为空时,此变量值为true

1
2
3
4
5
6
7
static bool binder_available_for_proc_work_ilocked(struct binder_thread *thread)
{
return !thread->transaction_stack &&
binder_worklist_empty_ilocked(&thread->todo) &&
(thread->looper & (BINDER_LOOPER_STATE_ENTERED |
BINDER_LOOPER_STATE_REGISTERED)); //这个binder线程既不是主线程,也没有被注册成binder子线程
}

在上一篇文章Android源码分析 - Binder驱动(中),我们分析过是有分支会将binder_work直接添加到binder_proctodo队列中的,当binder_thread中找不到工作的话,可能就需要从binder_proc中找了

然后会将当前线程的状态置为等待中,等到有可处理的binder_work后再解除这个状态

之后会判断传入的参数是否为阻塞模式,这个是由framework层执行ioctl时传入的flags所决定的,根据framework中的binder代码,这里应该恒为阻塞模式,在阻塞模式下,会调用binder_wait_for_work函数,等待存在可用的binder_work

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
40
41
42
43
44
45
46
47
48
49
50
static int binder_wait_for_work(struct binder_thread *thread,
bool do_proc_work)
{
//定义一个等待队列项
DEFINE_WAIT(wait);
struct binder_proc *proc = thread->proc;
int ret = 0;

freezer_do_not_count();
binder_inner_proc_lock(proc);
for (;;) {
//准备睡眠等待
prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE);
//检查确认是否有binder_work可以处理
if (binder_has_work_ilocked(thread, do_proc_work))
break;
//可以处理binder_proc.todo中的工作的话
if (do_proc_work)
//将该binder线程加入到binder_proc中的等待线程中
list_add(&thread->waiting_thread_node,
&proc->waiting_threads);
binder_inner_proc_unlock(proc);
//睡眠
schedule();
binder_inner_proc_lock(proc);
//将该binder线程从binder_proc中的等待线程中移除
list_del_init(&thread->waiting_thread_node);
//检查当前系统调用进程是否有信号处理
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
}
//结束等待
finish_wait(&thread->wait, &wait);
binder_inner_proc_unlock(proc);
freezer_count();

return ret;
}

static bool binder_has_work_ilocked(struct binder_thread *thread,
bool do_proc_work)
{
//thread->process_todo为false时会延时执行
return thread->process_todo ||
thread->looper_need_return ||
(do_proc_work &&
!binder_worklist_empty_ilocked(&thread->proc->todo));
}

这个函数的结构和Linux函数wait_event_interruptible非常相似,我们先来看看它是怎么做到阻塞等待的


Linux进程调度

DEFINE_WAIT

这是一个宏,它定义了一个wait_queue_t结构体

1
2
3
4
5
6
7
8
#define DEFINE_WAIT_FUNC(name, function)				\
wait_queue_t name = { \
.private = current, \
.func = function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

这个结构体中的private域指向的即是当前执行系统调用所在进程的描述结构体,func域指向唤醒这个队列项进程所执行的函数

prepare_to_wait

这个函数将我们刚刚定义的wait队列项加入到一个等待队列中(在binder中即是加入到thread->wait中),然后将进程的状态设置为我们指定的状态,在这里为TASK_INTERRUPTIBLE(可中断的睡眠状态)

schedule

schedule是真正执行进程调度的地方,由于之前进程状态已经被设置成TASK_INTERRUPTIBLE状态,在调用schedule后,该进程就会让出CPU,不再调度运行,直到该进程恢复TASK_RUNNING状态

wake_up_interruptible

我们在上一篇文章Android源码分析 - Binder驱动(中)中提到过,当binder_transaction工作处理完后,会调用wake_up_interruptible函数唤醒目标binder线程的等待队列

这个函数会唤醒TASK_INTERRUPTIBLE状态下的进程,它会循环遍历等待队列中的每个元素,分别执行其唤醒函数,也就对应着我们DEFINE_WAIT定义出来的结构体中的func域,即autoremove_wake_function,它最终会调用try_to_wake_up函数将进程置为TASK_RUNNING状态,这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行

signal_pending

这个函数是用来检查当前系统调用进程是否有信号需要处理的,当一个进程陷入系统调用并处于等待状态时,如果此时产生了一个信号,仅仅是在该进程的thread_info中标识了一下,所以我们唤醒进程后需要检查一下是否有信号需要处理,如果有的话,返回-ERESTARTSYS,先处理信号,后续Linux上层库函数会根据-ERESTARTSYS此返回值重新执行这个系统调用

finish_wait

最后一步,当进程被唤醒后,调用finish_wait函数执行清理工作,将当前进程置为TASK_RUNNING状态,并把当前wait队列项从等待队列中移除


了解了上面这些知识,我们再看binder_wait_for_work函数应该比较清晰了,这里有一点需要注意,我们在上一篇文章Android源码分析 - Binder驱动(中)提到,在没有TF_ONE_WAY标志的情况下,会使用binder_enqueue_deferred_thread_work_ilocked函数将tcomplete插入到事务发起binder线程的todo队列中,这个函数区别于binder_enqueue_thread_work_ilocked函数,它没有将thread->process_todo设为true,所以结合着binder_has_work_ilocked函数看,我们可以发现,当thread->process_todofalse时,整个binder_has_work_ilocked返回false,即会进入到睡眠状态,延迟执行BINDER_WORK_TRANSACTION_COMPLETE,这么设计可以让binder接收端优先处理事务,提高了性能

第二部分:获取工作,根据type做一定的处理

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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)
{
//用户空间传进来的需要将数据读到的地址
//实际上只是传输一些命令码和一个binder_transaction_data_secctx结构体
//真正的数据已经映射到用户虚拟内存空间中了,根据binder_transaction_data中所给的地址直接读就可以了
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
//起始地址 = 读数据的首地址 + 已读数据大小
void __user *ptr = buffer + *consumed;
//结束地址 = 读数据的首地址 + 读数据的总大小
void __user *end = buffer + size;
...
retry:
...
//循环处理todo队列中的工作
while (1) {
uint32_t cmd;
struct binder_transaction_data_secctx tr;
struct binder_transaction_data *trd = &tr.transaction_data;
struct binder_work *w = NULL;
struct list_head *list = NULL;
struct binder_transaction *t = NULL;
struct binder_thread *t_from;
size_t trsize = sizeof(*trd);

binder_inner_proc_lock(proc);
//找到需要处理的todo队列
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
else {
binder_inner_proc_unlock(proc);

/* no data added */
//只跳过了数据头部的命令码,没有读取任何数据
if (ptr - buffer == 4 && !thread->looper_need_return)
goto retry;
break;
}

//传输过来的数据大小不符合
if (end - ptr < sizeof(tr) + 4) {
binder_inner_proc_unlock(proc);
break;
}
//从todo队列中出队一项binder_work
w = binder_dequeue_work_head_ilocked(list);
if (binder_worklist_empty_ilocked(&thread->todo))
thread->process_todo = false;

switch (w->type) {
case BINDER_WORK_TRANSACTION: {
binder_inner_proc_unlock(proc);
//根据binder_work找到binder_transaction结构
t = container_of(w, struct binder_transaction, work);
} break;
case BINDER_WORK_RETURN_ERROR: {
...
} break;
case BINDER_WORK_TRANSACTION_COMPLETE: {
binder_inner_proc_unlock(proc);
cmd = BR_TRANSACTION_COMPLETE;
//回复给用户进程BR_TRANSACTION_COMPLETE响应码
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);

//更新统计数据
binder_stat_br(proc, thread, cmd);
//释放
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
case BINDER_WORK_NODE: {
...
} break;
case BINDER_WORK_DEAD_BINDER:
case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
...
} break;
}
...
}
done:
...
}

这里先创建了一个binder_transaction_data_secctx结构体,后续会将它拷贝到用户空间去,然后创建了一个指针trd指向tr.transaction_data的地址,这样后续操作trd就相当于操作tr.transaction_data

当进程从睡眠中唤醒,意味着有可用的binder_work了,这时候理论上来说,binder_threadbinder_proc其中总有一个todo队列不为空,这里优先处理binder_threadtodo队列,如果两者都为空,且还未读取过任何数据,重新gotoretry处等待

接着就是将binder_work从相应的todo队列中出队,再根据其类型执行不同的处理操作,这里我们只针对BINDER_WORK_TRANSACTIONBINDER_WORK_TRANSACTION_COMPLETE这两种最重要的类型分析

当类型为BINDER_WORK_TRANSACTION时,表示是别的进程向自己发起binder请求,此时,我们根据binder_work找到对应的binder_transaction结构

当类型为BINDER_WORK_TRANSACTION_COMPLETE时,表示发起的请求BC_TRANSACTION已经完成了,此时将回复给用户空间BR_TRANSACTION_COMPLETE响应码,然后更新统计数据,释放资源

第三部分:处理binder_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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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)
{
//用户空间传进来的需要将数据读到的地址
//实际上只是传输一些命令码和一个binder_transaction_data_secctx结构体
//真正的数据已经映射到用户虚拟内存空间中了,根据binder_transaction_data中所给的地址直接读就可以了
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
//起始地址 = 读数据的首地址 + 已读数据大小
void __user *ptr = buffer + *consumed;
//结束地址 = 读数据的首地址 + 读数据的总大小
void __user *end = buffer + size;
...
retry:
...
//循环处理todo队列中的工作
while (1) {
uint32_t cmd;
struct binder_transaction_data_secctx tr;
struct binder_transaction_data *trd = &tr.transaction_data;
struct binder_work *w = NULL;
struct list_head *list = NULL;
struct binder_transaction *t = NULL;
struct binder_thread *t_from;
size_t trsize = sizeof(*trd);
...
//只有在type == BINDER_WORK_TRANSACTION的情况下,t才会被赋值
if (!t)
continue;

if (t->buffer->target_node) { //binder实体不为NULL,对应着BC_TRANSACTION请求
struct binder_node *target_node = t->buffer->target_node;
struct binder_priority node_prio;

//binder实体在用户空间中的地址
trd->target.ptr = target_node->ptr;
//携带的额外数据
trd->cookie = target_node->cookie;
//优先级
node_prio.sched_policy = target_node->sched_policy;
node_prio.prio = target_node->min_priority;
binder_transaction_priority(current, t, node_prio,
target_node->inherit_rt);
//设置响应码
cmd = BR_TRANSACTION;
} else { //binder实体为NULL,对应着BC_REPLY请求
trd->target.ptr = 0;
trd->cookie = 0;
//设置响应码
cmd = BR_REPLY;
}
//表示要对目标对象请求的命令代码
trd->code = t->code;
//事务标志,详见enum transaction_flags
trd->flags = t->flags;
//请求发起进程的uid
trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid);

//获取发起请求的binder线程
t_from = binder_get_txn_from(t);
if (t_from) {
struct task_struct *sender = t_from->proc->tsk;
//设置发起请求的进程pid
trd->sender_pid =
task_tgid_nr_ns(sender,
task_active_pid_ns(current));
} else {
trd->sender_pid = 0;
}

//数据大小
trd->data_size = t->buffer->data_size;
//偏移数组大小
trd->offsets_size = t->buffer->offsets_size;
//设置数据区首地址(这里通过内核空间地址和user_buffer_offset计算得出用户空间地址)
trd->data.ptr.buffer = (binder_uintptr_t)
((uintptr_t)t->buffer->data +
binder_alloc_get_user_buffer_offset(&proc->alloc));
//偏移数组紧挨着数据区,所以它的首地址就为数据区地址加上数据大小
trd->data.ptr.offsets = trd->data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));

tr.secctx = t->security_ctx;
if (t->security_ctx) {
cmd = BR_TRANSACTION_SEC_CTX;
trsize = sizeof(tr);
}

//回复给用户进程对应的响应码
if (put_user(cmd, (uint32_t __user *)ptr)) {
... //Error
}
ptr += sizeof(uint32_t);
//将binder_transaction_data拷贝至用户空间
if (copy_to_user(ptr, &tr, trsize)) {
... //Error
}
ptr += trsize;
//更新数据统计
binder_stat_br(proc, thread, cmd);

//临时引用计数减1
if (t_from)
binder_thread_dec_tmpref(t_from);
//允许释放这个buffer
t->buffer->allow_user_free = 1;
if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) {
//非异步处理
binder_inner_proc_lock(thread->proc);
//将这个事务插入到事务栈中
t->to_parent = thread->transaction_stack;
//设置目标处理线程
t->to_thread = thread;
thread->transaction_stack = t;
binder_inner_proc_unlock(thread->proc);
} else {
binder_free_transaction(t);
}
break;
}

done:
//更新已读数据大小
*consumed = ptr - buffer;
binder_inner_proc_lock(proc);
//请求线程数为0且没有等待线程,已启动线程数小于最大线程数
//且这个binder线程既不是主线程,也没有被注册成binder子线程
if (proc->requested_threads == 0 &&
list_empty(&thread->proc->waiting_threads) &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
/*spawn a new thread if we leave this out */) {
//向用户空间发送BR_SPAWN_LOOPER响应码,创建新binder线程
proc->requested_threads++;
binder_inner_proc_unlock(proc);
if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
return -EFAULT;
//更新统计信息
binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
} else
binder_inner_proc_unlock(proc);
return 0;
}

只有当type == BINDER_WORK_TRANSACTION的情况下,才会走后面的这些逻辑,这一部分主要做的工作是,将处理事务所需要的信息(命令码、PID等)和数据(数据区首地址和偏移数组地址)准备好,拷贝到用户空间,交给用户空间处理这个事务,还有一些细节都在上面代码注释中标出来了,应该还是比较清晰的

结束

到这里,从clientbinder驱动发起请求,到binder驱动找到server并将请求传递给server这一整个流程我们基本上是看完了,对于被TF_ONE_WAY标记的事务(无需返回),在server中处理完,这一整条binder进程通信流程也就结束了,而对于需要返回的事务,则还需要serverbinder驱动发起BC_REPLY事务请求,进行一次进程间通信,将处理后的返回值传给client进程

总结

我们已经将binder驱动所承担的工作分析的七七八八了,但肯定还有很多地方大家云里雾里(实际上我也是这样的),这是因为binder进程间通信不仅需要binder驱动的处理,还需要framework层的协助,很多事情,比如说binder对象的压扁打包就是在framework层做的,我们在binder驱动层只关注了传输,但却不知道传输的是什么,结构是怎样的,当然会产生各种各样的疑惑,后面我们就将会对framework层对binder提供的支持接着做分析