共计 7317 个字符,预计需要花费 19 分钟才能阅读完成。
Binder 驱动中每一次传输都代表一个传输事件,通过 binder_transaction 来描述。
struct binder_thread {
struct binder_proc *proc; /* 线程所属的而进程 */
struct rb_node rb_node; /* 红黑树节点,插入到 binder_proc->threads 中 */
int pid; /* 线程 PID */
int looper; /* 线程 looper 状态,用上面的枚举描述 */
struct binder_transaction *transaction_stack; /* Binder 传输栈 */
struct list_head todo; /* Binder 线程 todo 队列 */
uint32_t return_error; /* 写失败时的返回错误码 */
uint32_t return_error2; /* 写失败时的返回错误码 2 */
wait_queue_head_t wait; /* Binder 线程等待队列 */
struct binder_stats stats; /* Binder 线程统计信息 */
};
struct binder_transaction {
int debug_id; /* 全局 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; /* 传输数据的 buffer */
unsigned int code; /* 传输的命令码 */
unsigned int flags; /* 传输标识,例如 TF_ONE_WAY */
long priority; /* 传输优先级 */
long saved_priority; /* 传输缺省优先级 */
kuid_t sender_euid; /* 发送端的 uid */
}
Binder 传输时通过 Binder 线程为主体进行交互的,所以 Binder 线程中会保存 Binder 传输事件,在 binder_thread 中使用 transaction_stack 做为一种栈的形式来记录所有的传输事件。transaction_stack 保存着当前正在进行的传输事件,采取压栈的方式保存,所以栈顶为最新的传输,栈底为最早的传输。这种方式也表现了线程中传输事件的依赖关系,一个传输事件必须等待上一个栈的传输完成才能进行。
- 线程 transaction_stack 中记录着当前线程正在进行的传输事件。
- 发送中的传输事件通过 from_parent(BC_TRANSACTION)入栈,接收中的传输事件通过 to_thread(BR_TRANSACTION)进行入栈。
- 通过 from_parent 入栈的传输事件,from 为当前当前 Binder 线程。通过 to_parent 入栈的传输事件,to_thread 为当前 Binder 线程。
- 系统中所有进行中的传输事件都存在各 Binder 线程的栈中,传输完成会出栈。传输事件通过 from_parent 形成一个链表,用来表明当前传输事件的上一个传输事件。这个链表可以用来对传输事件溯源,链表的头就是当前传输关联的第一个传输事件。
简单的传输
通过一个简单的传输来分析传输事件的流程。
- Binder 进程 Proc A 的线程 Thread A1 向 Binder 进程 Proc B 的线程 Thread B1 发起 IPC 通信。
- A1 发送命令 BC_TRANSACTION 到 Binder 驱动。
- Binder 驱动发送命令 BR_TRANSACTION 给 B1。
- B1 完成处理后回复 BC_REPLY 给 Binder 驱动。
- Binder 驱动再将 BR_REPLY 发送给 A1.
BC_TRANSACTION
A1 发起的传输事件(暂且称为 T1_tr) 在 BC_TRANSACTION 过程中通过 from_parent 入站到 A1 的 transaction_stack 中。
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
......
// 分配 binder transaction
t = kzalloc(sizeof(*t), GFP_KERNEL);
......
// 分配 binder_work 用于处理传输完成
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
......
// 同步的非 reply 传输,设置当前线程为 from
if (!reply && !(tr->flags & TF_ONE_WAY))
t->from = thread;
else
t->from = NULL;
t->sender_euid = proc->tsk->cred->euid;
// 设置传输的目标进程和线程
t->to_proc = target_proc;
t->to_thread = target_thread;
t->code = tr->code;
t->flags = tr->flags;
t->priority = task_nice(current);
......
if (reply) {......} else if (!(t->flags & TF_ONE_WAY)) {
// 当前线程的传输入栈
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
} else {
......
// 将传输添加到目标队列中
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list);
// 将传输完成添加到当前线程 todo 队列中
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
list_add_tail(&tcomplete->entry, &thread->todo);
// 唤醒目标线程或进程
if (target_wait)
wake_up_interruptible(target_wait);
return;
......
}
可以看到,在传输发起时先分配内存给 T1_tr,之后对 T1_tr 的数据进行赋值。然后入栈,将 T1_tr 的 from_parent 指向 A1 的 transaction_stack,在将 transaction_stack 指向 T1_tr。如果 A1 中已有进行中的传输,则表明原有的传输依赖 T1_tr 完成才能继续。在这个简单的例子中,之前没有传输发生,所以 from_parent=null。入栈完成后需要将 T1_tr 挂到 B1 线程的 todo 队列中,已便 B1 可以获取到 T1_tr,这时通过 T1_tr 的 work 完成的。
BR_TRANSACTION
在 BR_TRANSACTION 中,会将 T1_tr 入栈到 B1 的 transaction_stack 中。
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)
{
......
while (1) {
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL;
// 获取 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);
else {
......
switch (w->type) {// binder_transaction() 将工作 BINDER_WORK_TRANSACTION 加入队列后唤醒目标进程
case BINDER_WORK_TRANSACTION: {
// 通过 work 中获取 binder_transaction
t = container_of(w, struct binder_transaction, work);
} break;
......
// 从队列中移除当前工作事件
list_del(&t->work.entry);
t->buffer->allow_user_free = 1;
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
// 同步传输时,命令为 BR_TRANSACTION 的情况下,将工作事件入栈
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
} else {......}
......
}
BR_TRANSACTION 处理传输事件的大致流程就是:B1 获取 todo 队列进行处理,通过工作任务获得到传输事件 T1_tr,然后将 T1_tr 通过 to_parent 入栈到 B1 的 transaction_stack 中。
BC_REPLY
T1_tr 的出栈是通过 BC_REPLY 完成的,同时在 BC_REPLY 过程中会创建新的传输事件(暂且称为 T1_re)用于 Reply。T1_re 时没有入栈动作的,因为 Reply 时不需要后续处理的。
static void binder_pop_transaction(struct binder_thread *target_thread,
struct binder_transaction *t)
{
// 通过 from_parent 来出栈,用于传输发起端出栈
if (target_thread) {
target_thread->transaction_stack =
target_thread->transaction_stack->from_parent;
t->from = NULL;
}
t->need_reply = 0;
if (t->buffer)
t->buffer->transaction = NULL;
kfree(t);
// 删除 TRANSACTION 状态
binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
......
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
......
if (reply) {
// 从当前线程中出栈
in_reply_to = thread->transaction_stack;
......
thread->transaction_stack = in_reply_to->to_parent;
// 目标线程为发起端线程
target_thread = in_reply_to->from;
......
target_proc = target_thread->proc;
} else {......}
if (target_thread) {
e->to_thread = target_thread->pid;
target_list = &target_thread->todo;
target_wait = &target_thread->wait;
} else {......}
......
// reply 传输的 from 为空
if (!reply && !(tr->flags & TF_ONE_WAY))
t->from = thread;
else
t->from = NULL;
......
if (reply) {
// 从目标线程中出栈
binder_pop_transaction(target_thread, in_reply_to);
} else if (!(t->flags & TF_ONE_WAY)) {......} else {......}
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);
return;
......
}
BC_REPLY 是由 B1 发起的,所以当前线程的 T1_tr 出栈是通过 to_parent 完成。其目标线程 A1 是由统一的出栈函数 binder_pop_transaction,通过 from_parent 完成。出栈完成后将 T1_tr 释放掉。在 BC_REPLY 过程中,新建的传输事件 t 就是之前说的 T1_re,用于传输 Reply 事件。可以看到 T1_re 并没有入栈。
T1_re 的释放是在 BR_REPLY 中完的,这里不再详细分析。所有传输事件完成时都会释放相应的事件。
传输流程
根据上面的源码分析,可以将传输事件的变化用下图表示。
进程间的反复调用
通过上面对简单传输的分析,可以清晰的看清传输事件的流程。接下分析两个进程间的反复调用,看看 Binder 事件是如何传输的。
首先 Proc A 向 Proc B 发起 IPC 传输 T1,这时跟上面的例子相同,是 Thread A1 调用到 Thread B1。B1 收到 BR_TRANSACTION 后,在处理的过程中向 Proc A 发起 IPC 传输 T2,这时 Porc A 是使用哪个线程来处理 T2 的?如果 Proc A 在这个处理过程中又向 Proc B 发起 IPC 传输 T3,那么将会发生什么?看看源码中是如何处理这种情况的。
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
......
if (reply) {......} else {
......
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;
}
}
}
......
}
寻找 Binder 对端线程的核心代码就是上面这段。如果当前线程传输栈不为空,则表明当前线程还有未完成的传输。然后沿着 from_parent 寻找是否有来自对端进程的传输,如果有就复用这个传输的线程来处理这个新发起的传输。这中复用线程的方式时合理的,因为 from_parent 这条链表记录了传输调用的流程,链表内的传输有依赖关系,链表中的一个节点上的传输依赖上一个节点传输的完成。所以链表中的节点放在同一线程中处理不会产生影响,并且可以节约线程。
在我们的例子中,T1 的进程 A 就是 T2 的对端进程,所以 T2 将使用 A1 做为目标线程。然后 A1 向进程 B 发起 T3 传输时,B1 同样会被选做目标线程。用一个图来表示可能会更清晰些。
- A1->B1:A1->transaction=T1,B1->transaction=T1,T1->from->proc=A。
- B1->A1:A1->transaction=T2,B1->transaction=T2,T2->from->proc=B。T2->from_parent=T1。
- A1->B1:A1->transaction=T3,B1->transaction=T3,T3->from->proc=A。T3->from_parent=T2。
多进程的调用
两个进程间调用的例子给人一个错觉,好像 from_parent 与 to_parent 记录着同一个传输,实际上并不是这样。看一个多进程调用的例子加深理解一下。
- A1->B1:T1 入栈 A1 和 B1,T1->from->proc=A。
- B1->C1:T2 入栈 B1 和 C1,T2->from->proc=B,T2->from_parent=T1。
- C1->A1:T3 入栈 C1 和 A1,T3->from->proc=C,T3->from_parent=T2,T3->to_parent=T1。
这里的关键点时传输 T3 时,C1 是如何找到 A1 的。寻找流程还是上面那段代码,沿着 from_parent 最终找到 T1,T1->from=A1,决定了 T3 会从 C1 调用到 A1。