乐趣区

Binder驱动之传输事件

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 同样会被选做目标线程。用一个图来表示可能会更清晰些。

  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 记录着同一个传输,实际上并不是这样。看一个多进程调用的例子加深理解一下。

  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。

退出移动版