Android笔记 - Binder之处理注册Service组件请求

Posted demonyan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android笔记 - Binder之处理注册Service组件请求相关的知识,希望对你有一定的参考价值。

上篇文章以 MediaPlayerService 为例,分析了 Service 通过 Binder 驱动发送注册请求给 servicemanager 的过程。本文在此基础上分析 servicemanager 如何处理注册请求,以及如何反馈处理结果给 MediaPlayerService。

1. servicemanager 被唤醒

在上一篇文章 Binder之请求注册Service组件 中,分析到 binder_transaction 函数会创建一个待处理事务 t(事务类型是 BINDER_WORK_TRANSACTION),并将其添加到 servicemanager 进程的待处理工作队列 target_list 中,然后唤醒睡眠中的 servicemanager 进程,如下所示:

代码路径:linux/drivers/staging/android/binder.c

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)

    ......
    list_add_tail(&t->work.entry, target_list);
    ....
    if (target_wait)
        wake_up_interruptible(target_wait);
    ......

Binder之守护进程servicemanager 这篇文章中,讲到 servicemanager 进程通过 ioctl 系统调用检查 Binder 驱动是否有进程间通信请求需要它来处理。如果没有请求需要处理,那么 servicemanager 进程会在 binder_thread_read 函数中调用 wait_event_freezable_exclusive 进入睡眠等待状态。

代码路径:linux/drivers/staging/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
                  struct binder_thread *thread,
                  void  __user *buffer, int size,
                  signed long *consumed, int non_block)
    ....

    if (wait_for_proc_work) 
        ......
        if (non_block) 
            if (!binder_has_proc_work(proc, thread))
                ret = -EAGAIN;
         else
            ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
     else 
        ......
    

当 servicemanager 进程被唤醒时,会通过 binder_has_proc_work 函数来检查是否有新的请求需要处理,也就是检查当前进程的待处理工作队列 todo 是否为空,如下所示:

代码路径:linux/drivers/staging/android/binder.c

static int binder_has_proc_work(struct binder_proc *proc,
                struct binder_thread *thread)

    return !list_empty(&proc->todo) ||
        (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);

如果检测到待处理工作队列 todo 不为空,因此退出睡眠状态,接下来处理待处理工作队列中的进程间通信请求。

2. Binder 驱动处理待处理事务 - servicemanager 内核空间

servicemanager 退出睡眠后,继续执行 binder_thread_read 函数,将待处理事务从 Binder 驱动转发到 servicemanager 用户空间。过程如下所示:

代码路径:linux/drivers/staging/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
                  struct binder_thread *thread,
                  void  __user *buffer, int size,
                  signed long *consumed, int non_block)

    ......
    while (1) 
        uint32_t cmd;
        struct binder_transaction_data tr;
        struct binder_work *w;
        struct binder_transaction *t = NULL;

        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);                         [1]
        else 
            if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN))
                goto retry;
            break;
        

        if (end - ptr < sizeof(tr) + 4)
            break;

        switch (w->type) 
        case BINDER_WORK_TRANSACTION: 
            t = container_of(w, struct binder_transaction, work);                                 [2]
         break;
        ......
        

        if (!t)
            continue;

        BUG_ON(t->buffer == NULL);
        if (t->buffer->target_node) 
            struct binder_node *target_node = t->buffer->target_node;
            tr.target.ptr = target_node->ptr;                                                    [3]
            tr.cookie =  target_node->cookie;                                                    [4]
            t->saved_priority = task_nice(current);
            if (t->priority < target_node->min_priority &&
                !(t->flags & TF_ONE_WAY))
                binder_set_nice(t->priority);
            else if (!(t->flags & TF_ONE_WAY) ||
                 t->saved_priority > target_node->min_priority)
                binder_set_nice(target_node->min_priority);
            cmd = BR_TRANSACTION;
         else 
            ......
        
        tr.code = t->code;
        tr.flags = t->flags;
        tr.sender_euid = t->sender_euid;

        if (t->from) 
            struct task_struct *sender = t->from->proc->tsk;
            tr.sender_pid = task_tgid_nr_ns(sender,                                               [5]
                            current->nsproxy->pid_ns);
         else 
            tr.sender_pid = 0;
        

        tr.data_size = t->buffer->data_size;
        tr.offsets_size = t->buffer->offsets_size;
        tr.data.ptr.buffer = (void *)t->buffer->data +
                    proc->user_buffer_offset;                                                     [6]
        tr.data.ptr.offsets = tr.data.ptr.buffer +
                    ALIGN(t->buffer->data_size, sizeof(void *));                                  [7]

        if (put_user(cmd, (uint32_t __user *)ptr))                                                [8]
            return -EFAULT;
        ptr += sizeof(uint32_t);
        if (copy_to_user(ptr, &tr, sizeof(tr)))                                                   [9]
            return -EFAULT;
        ptr += sizeof(tr);

        ......
        list_del(&t->work.entry);
        t->buffer->allow_user_free = 1;
        if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) 
            t->to_parent = thread->transaction_stack;
            t->to_thread = thread;
            thread->transaction_stack = t;                                                       [10]
         else 
            ......
        
        break;
    

    ......
    return 0;

整个过程实际上是初始化描述事务数据的结构体 binder_transaction_data,并通过 copy_to_user 函数将其传递给 servicemanager 用户空间。

[1] 从待处理工作队列 todo 中取出一个工作项 w。
[2] 由于工作项类型为 BINDER_WORK_TRANSACTION,获取待处理事务 t 作为当前正在处理的事务。
[3] 由于 target_node 为 binder_context_mgr_node,所以 target_node->ptr 值为 NULL。
[4] 由于 target_node 为 binder_context_mgr_node,所以 target_node->cookie 值为 NULL。
[5] 将事务发起方的 pid 保存在 tr.sender_pid 中。
[6] 将通信数据 buffer 在内核空间的地址加上一个固定差值 proc->user_buffer_offset,得到通信数据 buffer 在 servicemanager 用户空间的地址。
[7] 通过通信数据 buffer 在 servicemanager 用户空间的地址,得到偏移数组在 servicemanager 用户空间的地址。

拷贝有深拷贝和浅拷贝之分:深拷贝需要重新分配内存资源,并且将内容完整的拷贝到分配好的内存空间;浅拷贝只是复制指向同一内存空间的地址,不需要分配新的内存资源。上述过程通过浅拷贝,实现了内存在内核空间和用户空间之间的共享,详细过程可以参考 Binder之守护进程servicemanager 中的打开和映射 Binder 设备文件小节。

[8] 调用 put_user 系统函数将协议 BR_TRANSACTION 拷贝回 servicemanager 用户空间。
[9] 调用 copy_to_user 系统函数将初始化好的结构体 binder_transaction_data 拷贝回 servicemanager 用户空间。
[10] 将当前处理事务 t 添加到 thread->transaction_stack 中,下面流程中还会用到这个事务。

3. servicemanager 处理注册请求 - servicemanager 用户空间

binder_thread_read 函数执行完返回到 binder_ioctl 函数,然后 ioctl 系统调用返回到 binder_loop 函数中,也就是从 Binder 驱动重新回到了 servicemanager 用户空间。接下来调用 binder_parse 函数解析从 Binder 驱动程序拷贝回来的 binder_transaction_data 结构体,将解析得到的 MediaPlayerService 名称以及句柄值保存到结构体 svcinfo,然后将结构体 svcinfo 添加到 svclist 链表中。详细过程请参考 Binder之守护进程servicemanager 中的 servicemanager 如何提供服务小节。

当 Client 进程需要获取某个 Service 的代理对象时,servicemanager 根据服务名称遍历 svclist 链表,然后将对应的句柄值返回给 Client 进程,最后 Client 进程根据句柄值获得 Service 的代理对象。

servicemanager 处理完注册请求后,继续调用 binder_send_reply 函数返回处理结果。binder_send_reply 函数定义如下:

代码路径:frameworks/native/cmds/servicemanager/binder.c

void binder_send_reply(struct binder_state *bs,
                       struct binder_io *reply,
                       void *buffer_to_free,
                       int status)

    struct 
        uint32_t cmd_free;
        void *buffer;
        uint32_t cmd_reply;
        struct binder_txn txn;
     __attribute__((packed)) data;                                                               [1]

    data.cmd_free = BC_FREE_BUFFER;                                                               [2]
    data.buffer = buffer_to_free;
    data.cmd_reply = BC_REPLY;                                                                    [3]
    data.txn.target = 0;
    data.txn.cookie = 0;
    data.txn.code = 0;
    if (status) 
        data.txn.flags = TF_STATUS_CODE;
        data.txn.data_size = sizeof(int);
        data.txn.offs_size = 0;
        data.txn.data = &status;
        data.txn.offs = 0;
     else 
        data.txn.flags = 0;
        data.txn.data_size = reply->data - reply->data0;
        data.txn.offs_size = ((char*) reply->offs) - ((char*) reply->offs0);
        data.txn.data = reply->data0;
        data.txn.offs = reply->offs0;
    
    binder_write(bs, &data, sizeof(data));                                                        [4]

[1] 定义结构体 data 用于保存返回给 Binder 驱动的内容。其中变量 cmd_free 用于保存释放内存的协议,指针 buffer 保存需要释放的内存地址。变量 cmd_reply 用于保存返回协议,结构体 binder_txn 用于保存请求处理返回结果。
[2] 释放内存协议为 BC_FREE_BUFFER。注册请求处理完后,需要释放之前 Binder 驱动在 binder_transaction 函数中分配的用于保存通信数据的内存。
[3] 返回协议为 BC_REPLY。
[4] 调用 binder_write 函数将结构体 data 传递给 Binder 驱动,binder_write 函数内部也是通过系统调用 ioctl(bs->fd, BINDER_WRITE_READ, &bwr) 来与 Binder 驱动进行交互。

4. Binder 驱动将注册结果返回给 Service - servicemanager 内核空间

上述 ioctl 系统调用最终也会执行到 Binder 驱动的 binder_ioctl 函数。通过之前几篇文章分析可知,binder_ioctl 中会调用 binder_thread_write 函数,后者又会调用 Binder 驱动的核心处理函数 binder_transaction,需要注意的是此时参数 reply 的值为 true。接下来看看这个过程:

代码路径:linux/drivers/staging/android/binder.c

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)

    struct binder_transaction *t;
    struct binder_work *tcomplete;
    size_t *offp, *off_end;
    struct binder_proc *target_proc;
    struct binder_thread *target_thread = NULL;
    struct binder_node *target_node = NULL;
    struct list_head *target_list;
    wait_queue_head_t *target_wait;
    struct binder_transaction *in_reply_to = NULL;

    ......
    if (reply) 
        in_reply_to = thread->transaction_stack;                                                  [1]
        ......
        thread->transaction_stack = in_reply_to->to_parent;                                       [2]
        target_thread = in_reply_to->from;                                                        [3]
        ......
        target_proc = target_thread->proc;                                                        [4]
     else 
        ......
    
    if (target_thread) 
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;                                                       [5]
        target_wait = &target_thread->wait;                                                       [6]
     else 
        target_list = &target_proc->todo;
        target_wait = &target_proc->wait;
    
    e->to_proc = target_proc->pid;

    /* TODO: reuse incoming transaction for reply */
    t = kzalloc(sizeof(*t), GFP_KERNEL);
    if (t == NULL) 
        return_error = BR_FAILED_REPLY;
        goto err_alloc_t_failed;
    
    binder_stats_created(BINDER_STAT_TRANSACTION);

    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
    ......

    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);

    trace_binder_transaction(reply, t, target_node);

    t->buffer = binder_alloc_buf(target_proc, tr->data_size,
        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
    if (t->buffer == NULL) 
        return_error = BR_FAILED_REPLY;
        goto err_binder_alloc_buf_failed;
    
    t->buffer->allow_user_free = 0;
    t->buffer->debug_id = t->debug_id;
    t->buffer->transaction = t;
    t->buffer->target_node = target_node;
    trace_binder_transaction_alloc_buf(t->buffer);
    if (target_node)
        binder_inc_node(target_node, 1, 0, NULL);

    offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));

    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)) 
        ......
    

    off_end = (void *)offp + tr->offsets_size;
    for (; offp < off_end; offp++)                                                               [7]
        ......
    

    if (reply) 
        BUG_ON(t->buffer->async_transaction != 0);
        binder_pop_transaction(target_thread, in_reply_to);                                       [8]
     

    ......
    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;
    ......

[1] 初始化 in_reply_to 为指向当前线程 thread(servicemanager 线程) 的事务堆栈 binder_transaction 的指针。前面 binder_thread_read 函数将处理事务加入到了 thread 的事务堆栈,因此 in_reply_to 指针实际指向了之前加入的事务。
[2] 将 in_reply_to 指向的事务从当前线程的事务堆栈中退栈。
[3] 根据 in_reply_to 初始化目标线程 target_thread 为 MediaPlayerService 线程。
[4] 根据 target_thread 初始化目标进程 target_proc 为 MediaPlayerService 进程。
[5] 初始化 target_list 为目标线程 target_thread 的待处理工作队列。
[6] 初始化 target_wait 为目标线程 target_thread 的等待队列。
[7] 由于返回结果数据中不存在 Binder 对象,因此不会进入 for 循环。
[8] 调用 binder_pop_transaction 函数将 in_reply_to 指向的事务从目标线程的事务堆栈中退栈。

这里省略了与文章 Binder之请求注册Service组件中分析binder_transaction 函数重复的内容。

和之前的分析一样,最后会将待处理事务 t 和待完成工作项 tcomplete 分别添加到 target_list 和 thread->todo 中,因此待处理事务 t 将由 MediaPlayerService 来处理,而待完成工作项 tcomplete 将由 servicemanager 来处理,处理过程之前也都分析过。

至此,Service 注册流程终于分析完成,下图为注册流程所涉及的 MediaPlayerService,Binder 驱动和 servicemanager 三者之间的交互过程:

参考文献:
1. Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析
2. Android Binder机制(六) addService详解之请求的处理

以上是关于Android笔记 - Binder之处理注册Service组件请求的主要内容,如果未能解决你的问题,请参考以下文章

Android笔记 - Binder之请求注册Service组件

Android笔记 - Binder之Client请求Service代理对象

Android笔记 - Binder之Client请求Service代理对象

Android笔记 - Binder之守护进程servicemanager

Android笔记 - Binder之servicemanager代理对象

Android笔记 - Binder之servicemanager代理对象