binder-transcation_stack机制

概述

看一个图

这是在server进程中的一个结构图,server进程有可能会创建出其他的子线程,每个线程都会有todo链表,之前的分析过程中我们可以知道,在寻找todo链表时,如果thread的todo没数据就会去找proc的todo

那么,client应该把数据给谁,给proc,还是给thread,给哪一个thread

另外,server是如何找到目的进程的?因为驱动是没有handle的,所以肯定是在某个记录中去找,这个记录结构体就是 transaction_stack,我们之前分析的过程中,看到过这个,只是讲流程时并没有进入细节

解答第一个问题,发给谁

一般是放在 binder_proc.todo里面,并且唤醒binder_proc中的wait空闲线程
如果他是双向传输,那么它会存在binder_thread.todo 里,并唤醒该线程

什么是双向传输

什么是双向传输,看图

P代表 proc,进程
S代表 service,服务
T代表 Thread,线程

假设我现在P1中的T1把数据传到P2的T1,然后再传给P3的T3,那么当我T3想要去调用P1中的一些处理的时候,要找谁?

如果是在P1中去新建新线程做这个处理的话,调用层次一旦多起来,线程的数量就会特别多,很浪费资源,因此,android在这里处理是,既然T1已经是在休眠状态,何不把它给唤醒来进行数据的处理

所以,P1中的T1发出请求给P3的T3,T3要用到P1中的服务时,使用T1来处理,这个过程叫双向传输,那么不需要回调原来进程的服务的过程,为单向传输

分析单向传输

先分析单向传输,我们以C/S架构来分析,从client端发数据到server端

看看简单的一个传输图

首先进入 binder_transaction,前面文章分析过了,发送请求后它最终会走这个函数

binder_transaction流程

binder_transaction的大致工作如下

  • 找目标binder_node
  • 找目标binder_proc
  • 分析并插入红黑树节点
  • 创建binder_transaction节点,并将其插入目标进程的todo列表
  • 尝试唤醒目标进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct binder_transaction {
int debug_id;
struct binder_work work;
//从哪个线程来
struct binder_thread *from;
//表示发送者的栈
struct binder_transaction *from_parent;
//目的进程
struct binder_proc *to_proc;
//目的线程
struct binder_thread *to_thread;
//表示接收者的栈
struct binder_transaction *to_parent;
unsigned need_reply:1;
/* unsigned is_dead:1; */ /* not used at the moment */

struct binder_buffer *buffer;
unsigned int code;
unsigned int flags;
long priority;
long saved_priority;
kuid_t sender_euid;
};

cmd是 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
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
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
. . . . . .
. . . . . .
// 先从tr->target.handle句柄值,找到对应的binder_ref节点,及binder_node节点
// client传过来的 handle
if (tr->target.handle){
. . . . . .
target_node = ref->node;
}else{
// 如果句柄值为0,则获取特殊的binder_context_mgr_node节点,
// 即Service Manager Service对应的节点
target_node = binder_context_mgr_node;
. . . . . .
}
. . . . . .
// 先确定target_proc,得到目标进程的binder_proc
target_proc = target_node->proc;
. . . . . .
//双向传输 之前讲过在binder_ioctl的时候,会为调用方创建线程,这里指我们的client
// 带TF_ONE_WAY 所以不走这
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack)
{
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
. . . . . .
// 找到from_parent这条链表中,最后一个可以和target_proc匹配
// 的binder_transaction节点,
// 这个节点的from就是我们要找的“目标线程”
//只需要判断 栈中存的proc信息和目的proc是否一致
while (tmp)
{
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from;
tmp = tmp->from_parent;
}
}
. . . . . .
// 要确定target_list和target_wait了,如果能找到“目标线程”,它们就来自目标线程,否则
// 就只能来自目标进程了
// 一开始 thread->transaction_stack 传输栈肯定是空,所以target_thread也为空
if (target_thread)
{
e->to_thread = target_thread->pid;
target_list = &target_thread->todo;
target_wait = &target_thread->wait;
}
else {
//所以一开始数据是放在 proc的todo链表中的
target_list = &target_proc->todo;
//会去唤醒该进程下中的等待队列中的线程 具体的唤醒操作在下面代码
target_wait = &target_proc->wait;
}
. . . . . .
// 创建新的binder_transaction节点。
t = kzalloc(sizeof(*t), GFP_KERNEL);
. . . . . .
. . . . . .

t->from = thread; // 新节点的from域记录事务的发起线程

t->sender_euid = proc->tsk->cred->euid;
//注意这里的赋值
t->to_proc = target_proc;
t->to_thread = target_thread; // 新节点的to_thread域记录事务的目标线程

t->code = tr->code;
t->flags = tr->flags;
t->priority = task_nice(current);
// 把目的进程所映射的空间分配出一个内存给buffer
// 那么之后操作这个buffer 就是操作目的进程的空间数据
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size,
!reply && (t->flags & TF_ONE_WAY));
. . . . . .
t->buffer->transaction = t;
t->buffer->target_node = target_node;
. . . . . .
// 从用户态拷贝来待传输的数据
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
. . . . . .
}
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
. . . . . .
}
// 遍历每个flat_binder_object信息,创建必要的红黑树节点 ....
for (; offp < off_end; offp++)
{
struct flat_binder_object *fp;
. . . . . .
}

. . . . . .
t->need_reply = 1;
// 新binder_transaction节点成为发起端transaction_stack栈的新栈顶
// 把栈信息 给from_parent ,单向传输这个为 null
// 因此 client这里的工作就是处理 from_parent
t->from_parent = thread->transaction_stack;
// 入栈操作
thread->transaction_stack = t;
. . . . . .
t->work.type = BINDER_WORK_TRANSACTION;
// 这里就是放入目的进程的todo链表中 把binder_transaction节点插入target_list
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);
return;
. . . . . .
. . . . . .
}

流程图

可以看下流程图

进行了入栈操作后,唤醒目的进程,也就是我们的server,它会调用自己的 binder_thread_read,cmd是 cmd是 BR_TRANSACTION

BR_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
//当前进程和线程,这里指的是我们的server进程
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
void __user *buffer, int size,
signed long *consumed, int non_block)
{
. . . . . .
retry:
// 优先考虑thread节点的todo链表中有没有工作需要完成
wait_for_proc_work = thread->transaction_stack == NULL
&& list_empty(&thread->todo);
. . . . . .
. . . . . .
if (wait_for_proc_work)
{
. . . . . .
ret = wait_event_interruptible_exclusive(proc->wait,
binder_has_proc_work(proc, thread));
}
else
{
. . . . . .
ret = wait_event_interruptible(thread->wait, binder_has_thread_work(thread));
}
. . . . . .
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

// 如果是非阻塞的情况,ret值非0表示出了问题,所以return。
// 如果是阻塞(non_block)情况,ret值非0表示等到的结果出了问题,所以也return。
if (ret)
return ret;

while (1)
{
. . . . . .
// 读取binder_thread或binder_proc中todo列表的第一个节点
if (!list_empty(&thread->todo))
w = list_first_entry(&thread->todo, struct binder_work, entry);
else if (!list_empty(&proc->todo) && wait_for_proc_work)
w = list_first_entry(&proc->todo, struct binder_work, entry);
. . . . . .

switch (w->type)
{
case BINDER_WORK_TRANSACTION: {
// 根据从 todo中获取的 w中获取 binder_transaction
t = container_of(w, struct binder_transaction, work);
} break;

case BINDER_WORK_TRANSACTION_COMPLETE: {
cmd = BR_TRANSACTION_COMPLETE;
. . . . . .
// 将binder_transaction节点从todo队列摘下来
list_del(&w->entry);
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
. . . . . .
. . . . . .
}

if (!t)
continue;

. . . . . .
//中间部分的代码我们前面已经分析过了

// 将cmd命令写入用户态,此时应该是BR_TRANSACTION
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
// 当然,binder_transaction_data本身也是要copy到用户态的
if (copy_to_user(ptr, &tr, sizeof(tr)))
return -EFAULT;
. . . . . .
. . . . . .
// 将binder_transaction节点从todo队列摘下来
list_del(&t->work.entry);
t->buffer->allow_user_free = 1;
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
//这里只更改 to_parent,原来的数据不做更改
//把 to_parent 指向自身的 transaction_stack
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
//入栈操作
thread->transaction_stack = t;
} else {
t->buffer->transaction = NULL;
// TF_ONE_WAY情况,此时会删除binder_transaction节点
kfree(t);
binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
break;
}
. . . . . .
. . . . . .
return 0;
}

那在注册过程中分析过,处理完数据之后,它会进行回复数据处理 binder_send_reply,最终调用到linux层的 binder_transaction

前面分析的是非回复的情况,现在它是回复数据了,我们就找 reply 即可 cmd为 BC_REPLY

BC_REPLY

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
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply,
binder_size_t extra_buffers_size)
{
if (reply) {
binder_inner_proc_lock(proc);
//要回复给谁
in_reply_to = thread->transaction_stack;
if (in_reply_to == NULL) {
......
}
// 要回复的线程和当前线程不等也出错
// 当前分析的当前线程为 server
if (in_reply_to->to_thread != thread) {
......
}
//当前端的thread出栈
thread->transaction_stack = in_reply_to->to_parent;
binder_inner_proc_unlock(proc);
binder_set_nice(in_reply_to->saved_priority);
//找出目的线程,就是之前处理的时候的 binder_transaction 中的 from
target_thread = binder_get_txn_from_and_acq_inner(in_reply_to);

//错误处理
......

//找出要回复的目的进程
target_proc = target_thread->proc;
target_proc->tmp_ref++;
binder_inner_proc_unlock(target_thread->proc);
}
......
if (reply) {
binder_enqueue_thread_work(thread, tcomplete);
binder_inner_proc_lock(target_proc);
if (target_thread->is_dead) {
binder_inner_proc_unlock(target_proc);
goto err_dead_proc_or_thread;
}
BUG_ON(t->buffer->async_transaction != 0);
//目的进程中的线程 也就是client端出栈
binder_pop_transaction_ilocked(target_thread, in_reply_to);
binder_enqueue_thread_work_ilocked(target_thread, &t->work);
binder_inner_proc_unlock(target_proc);
wake_up_interruptible_sync(&target_thread->wait);
binder_free_transaction(in_reply_to);
} else if (!(t->flags & TF_ONE_WAY)) {
......
} else {
......
}
}

之后就是把数据放入todo链表,唤醒目的进程等等操作

然后client会收到一个 BR_REPLY,把数据返回给用户空间

流程回顾

用一个图,大致回顾下流程

得出一些结论
cmd是BC_TRANSACTION TR是通过from_parent来入栈的
cmd是BR_TRANSACTION TR是通过to_parent来入栈的
cmd是BC_REPLY TR是通过to_parent来出栈,from_parent来出栈的
cmd是BR_REPLY 不做栈操作

分析双向传输

下面分析双向传输,双向传输其实并不复杂,因为它只是在单向的基础做了其他的处理,就是它的一些赋值以及入栈出栈的逻辑比较多而已

单向传输的server接收到数据后,会发送reply给client,双向中也就是server先不发送reply,而是再次发送数据给另一个server,然后另一个server再进行数据处理(调用client中的方法)以及发送reply,server收到reply后,再发送reply给client,也就是中间多了另一个server

假想流程图

以上图的流程为例来做流程分析

为了让大家逻辑清楚一些,我们把P2中的T1修改为T2

流程文字描述

T1-T2-T3,整体流程是这样

那么重点就是T3如何找到T1,也就是T3要调用T1时,怎么找到T3,以及如何处理这些数据

现在T3想要找到T1,那肯定要先找到T1所在的进程P1,可以根据传进来的handle获取到目的proc,也就是P1

进程的通信过程中,肯定会调用binder_call的,调用binder_call的参数中,是需要指定 target的,也就是目的进程的handle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 先从tr->target.handle句柄值,找到对应的binder_ref节点,及binder_node节点
// client传过来的 handle
if (tr->target.handle){
. . . . . .
target_node = ref->node;
}else{
// 如果句柄值为0,则获取特殊的binder_context_mgr_node节点,
// 即Service Manager Service对应的节点
target_node = binder_context_mgr_node;
. . . . . .
}
. . . . . .
// 先确定target_proc,得到目标进程的binder_proc
target_proc = target_node->proc;

获取到目的进程后,接下来的功能就是找线程,while就是找到目的线程的重要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//双向传输 之前讲过在binder_ioctl的时候,会为调用方创建线程,这里指我们的client
// 不带 TF_ONE_WAY 走这
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack)
{
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
. . . . . .
// 找到from_parent这条链表中,最后一个可以和target_proc匹配
// 的binder_transaction节点,
// 这个节点的from就是我们要找的“目标线程”
//只需要判断 栈中存的proc信息和目的proc是否一致
while (tmp)
{
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from;
tmp = tmp->from_parent;
}
}

可以看图来促进你的理解

具体流程

当T3要找到T1时,走循环,直到 tmp 为null为止,也就是找到 tmp->from_parent 并且线程对应的目的进程相等 为止

要先找到目的线程,那么就是 T3 还没发送 BC_TRANSACTION 命令,因此它的栈中的信息还是TR2的栈信息

target_proc 为P1, 找到TR2的from为T2,T2的proc为P2,不等于P1,target_thread等于T2,继续找

tmp这时候等于TR1,TR1的from为T1,T1的proc为P1,P1等于P1,找到,target_thread等于T1

找到之后发送 BC_TRANSACTION 命令给 T1,T1接收到后处理数据,处理完就发送 BC_REPLY 给T3

在发送 BC_REPLY 之前,会做出栈操作

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
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply,
binder_size_t extra_buffers_size)
{
if (reply) {
binder_inner_proc_lock(proc);
//要回复给谁
in_reply_to = thread->transaction_stack;

......

//当前端的thread出栈
thread->transaction_stack = in_reply_to->to_parent;
binder_inner_proc_unlock(proc);
binder_set_nice(in_reply_to->saved_priority);
//找出目的线程,就是之前处理的时候的 binder_transaction 中的 from
target_thread = binder_get_txn_from_and_acq_inner(in_reply_to);

//找出要回复的目的进程
target_proc = target_thread->proc;
target_proc->tmp_ref++;
binder_inner_proc_unlock(target_thread->proc);
}
......
if (reply) {

......

//目的进程中的线程 也就是client端出栈
binder_pop_transaction_ilocked(target_thread, in_reply_to);

......
}
......
}

发送 BC_REPLY 命令后,T3会接收到 BR_REPLY,然后因为T2正在等待T3的结果,所以T3又要发送 BC_REPLY 给T2,同样T2也会接收到 BR_REPLY,直到T1接收到 BR_REPLY 命令后,整个传输过程就结束了