1

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中完的,这里不再详细分析。所有传输事件完成时都会释放相应的事件。

传输流程

根据上面的源码分析,可以将传输事件的变化用下图表示。
biner_transaction.png

 进程间的反复调用

通过上面对简单传输的分析,可以清晰的看清传输事件的流程。接下分析两个进程间的反复调用,看看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同样会被选做目标线程。用一个图来表示可能会更清晰些。

biner_transaction2.png

  1. A1->B1:A1->transaction=T1,B1->transaction=T1,T1->from->proc=A。
  2. B1->A1:A1->transaction=T2,B1->transaction=T2,T2->from->proc=B。T2->from_parent=T1。
  3. A1->B1:A1->transaction=T3,B1->transaction=T3,T3->from->proc=A。T3->from_parent=T2。

多进程的调用

两个进程间调用的例子给人一个错觉,好像from_parent与to_parent记录着同一个传输,实际上并不是这样。看一个多进程调用的例子加深理解一下。

biner_transaction3.png

  1. A1->B1:T1入栈A1和B1,T1->from->proc=A。
  2. B1->C1:T2入栈B1和C1,T2->from->proc=B,T2->from_parent=T1。
  3. 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。


戈壁老王
143 声望64 粉丝

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,