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。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。