深入Android系统 Binder-4-驱动

true);

if (ref) {

//引用项的node字段保存了Binder对象节点的指针

//复制给target_node

target_node = binder_get_node_refs_for_txn(

ref->node, &target_proc,

&return_error);

} else {

//…

}

//…

} else {

//把管理进程的节点名放到target_node中

target_node = context->binder_context_mgr_node;

//…

}

  1. 搜寻目标线程

if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {

struct binder_transaction *tmp;

tmp = thread->transaction_stack;

//…

while (tmp) {

struct binder_thread *from;

spin_lock(&tmp->lock);

from = tmp->from;

if (from && from->proc == target_proc) {

atomic_inc(&from->tmp_ref);

target_thread = from;

spin_unlock(&tmp->lock);

break;

}

spin_unlock(&tmp->lock);

tmp = tmp->from_parent;

}

}

上面这段代码的逻辑是:

  • 如果本次调用不是异步调用,并且调用者线程中的transaction_stack不为NULL,则在其中查找和本次调用具有相同目标进程的transaction_stack,如果找到,则把它的目标线程设置为本次调用的目标线程。

  • transaction_stack是一个列表,保存了本线程所有正在执行的Binder调用的binder_transaction结构体。

  • Binder驱动中用binder_transaction来保存一次Binder调用的所有数据,包括传递的数据、通信双发的进程、线程信息等。因为Binder调用涉及两个进程,还要向调用端传递返回值。

  • 所以,驱动中用结构binder_transaction保存还没结束的Binder调用

  • 通常情况下执行Binder调用时不会存在相同进程的Binder调用,因此target_thread的值大多为NULL

  1. 如果有目标线程,则使用目标线程中的todo队列,否则使用目标进程的todo队列

if (target_thread)

e->to_thread = target_thread->pid;

e->to_proc = target_proc->pid;

4.为当前的Binder调用创建binder_transaction结构,并用调用的数据填充它。

t = kzalloc(sizeof(*t), GFP_KERNEL);

//…

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

//…

if (!reply && !(tr->flags & TF_ONE_WAY))

t->from = thread;

else

t->from = NULL;

t->sender_euid = task_euid(proc->tsk);

t->to_proc = target_proc;

t->to_thread = target_thread;

t->code = tr->code;

t->flags = tr->flags;

if (!(t->flags & TF_ONE_WAY) &&

binder_supported_policy(current->policy)) {

/* Inherit supported policies for synchronous transactions */

t->priority.sched_policy = current->policy;

t->priority.prio = current->normal_prio;

} else {

/* Otherwise, fall back to the default priority */

t->priority = target_proc->default_priority;

}

5.在目标进程(也就是服务端)的缓冲区分配空间,复制用户进程的数据到内核

t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,

tr->offsets_size, extra_buffers_size,

!reply && (t->flags & TF_ONE_WAY));

//…

if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)

tr->data.ptr.buffer, tr->data_size)) {

//…

}

if (copy_from_user(offp, (const void __user *)(uintptr_t)

tr->data.ptr.offsets, tr->offsets_size)) {

//…

}

  • 这里复制的数据是Binder调用的参数数据,也是需要传递给服务进程的数据,因此需要缓冲区。这个缓冲区是在目标进程的大的缓冲区中分配的
  1. 处理传输中的Binder对象

off_end = (void *)off_start + tr->offsets_size;

sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));

sg_buf_end = sg_bufp + extra_buffers_size;

off_min = 0;

for (; offp < off_end; offp++) {

//…

}

  • 通过参数传递的Binder对象需要进行转换,这里的for循环就是进行转换操作,内容较多,会在下面处理传递的Binder对象章节进行讲解
  1. 将本次调用的binder_transaction结构体链接到线程的binder_stack列表中

if (!(t->flags & TF_ONE_WAY)) {

//…

t->need_reply = 1;

t->from_parent = thread->transaction_stack;

thread->transaction_stack = t;

// 书中下面注释部分的代码已经整合到 binder_proc_transaction 函数中了

// 首先,将结构 binder_transaction中的binder_work放到目标目标进程或线程的todo队列

// 然后,创建一个新的binder_work的结构体,并将它放到发送线程的todo队列

if (!binder_proc_transaction(t, target_proc, target_thread)) {

//… 失败处理

}

}

  • binder_transaction结构中包含了一个binder_work的结构体,因此它可以被放到todo队列

  • 本地调用时binder_worktype被设置为BINDER_WORK_TRANSACTION后,插入了目标进程目标线程todo队列

  • 为了使客户进程能收到回复消息,这里也会创建一个新的binder_work的结构体,并把它的type设置成了BINDER_WORK_TRANSAXTION_COMPLETET,并将它插入到当前线程的todo队列

前面介绍过,在binder_thread_write函数执行完后,还会去判断是否需要执行binder_thread_read函数:

  • 由于调用端执行完BC_TRANSACTION后,会立刻执行ioctl的读指令

  • 所以,在调用操作上,binder_thread_read函数算是紧接着binder_thread_write函数后执行的

刚才新建的binder_work的结构体已经插入todo队列了,我们看下binder_thread_read函数会进行哪些和todo队列相关的操作:

while (1) {

//…

// 获得todo队列,获取失败则goto retry

if (!binder_worklist_empty_ilocked(&thread->todo))

list = &thread->todo;

else if (!binder_worklist_empty_ilocked(&proc->todo) &&

wait_for_proc_work)

list = &proc->todo;

else {

binder_inner_proc_unlock(proc);

/* no data added */

if (ptr - buffer == 4 && !thread->looper_need_return)

goto retry;

break;

}

//取出todo队列中的元素

w = binder_dequeue_work_head_ilocked(list);

//…

switch (w->type) {

//…省略大量case语句

case BINDER_WORK_TRANSACTION_COMPLETE: {

binder_inner_proc_unlock(proc);

cmd = BR_TRANSACTION_COMPLETE;

//把返回消息通过put_user放到用户空间的指针中

if (put_user(cmd, (uint32_t __user *)ptr))

return -EFAULT;

ptr += sizeof(uint32_t);

binder_stat_br(proc, thread, cmd);

kfree(w);

binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);

} break;

//…省略大量case语句

}

//…

}

  • binder_thread_read函数所做的处理就是把回复消息BR_TRANSACTION_COMPLETE复制到用户空间

这样客户进程就可以收到回复消息了

  • 到这里,客户进程的调用结束了。但是,Binder调用才完成一半,接下来,看看服务进程是如何调用数据的。

服务进程的调用流程

服务进程至少有一个线程会在ioctl上等待调用的到来。服务进程调用ioctl时传递的是读数据的请求,所以最后调用的也是binder_thread_read函数,我们看下binder_thread_read函数完整的处理流程:

  1. 如果保存返回结果的缓冲区中还没有数据,先写入BR_NOOP消息:

if (*consumed == 0) {

if (put_user(BR_NOOP, (uint32_t __user *)ptr))

return -EFAULT;

ptr += sizeof(uint32_t);

}

  1. 进入循环处理所有todo队列中的工作

while (1) {

//…

}

  1. 读取线程或进程todo队列中需要完成的工作

struct binder_work *w = NULL;

//…

if (!binder_worklist_empty_ilocked(&thread->todo))

list = &thread->todo;

else if (!binder_worklist_empty_ilocked(&proc->todo) &&

wait_for_proc_work)

list = &proc->todo;

//…

w = binder_dequeue_work_head_ilocked(list);

  1. switch语句处理所有类型的工作

switch (w->type) {

case BINDER_WORK_TRANSACTION: {

binder_inner_proc_unlock(proc);

t = container_of(w, struct binder_transaction, work);

} break;

}

  • 经过客户进程的调用流程后,此时的服务进程中已经存在一个类型为BINDER_WORK_TRANSACTION的工作需要处理

  • 这里只是取出了和binder_work关联的binder_transaction结构体指针

  • 并保存到变量t

  1. 调整线程的优先级

if (!t)

continue;

if (t->buffer->target_node) {

struct binder_node *target_node = t->buffer->target_node;

struct binder_priority node_prio;

tr.target.ptr = target_node->ptr;

tr.cookie = target_node->cookie;

node_prio.sched_policy = target_node->sched_policy;

node_prio.prio = target_node->min_priority;

binder_transaction_priority(current, t, node_prio,

target_node->inherit_rt);

cmd = BR_TRANSACTION;

}

  • 这里复制的数据是Binder调用的参数数据,也是需要传递给服务进程的数据,因此需要缓冲区。这个缓冲区是在目标进程的大的缓冲区中分配的
  1. 处理传输中的Binder对象

off_end = (void *)off_start + tr->offsets_size;

sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));

sg_buf_end = sg_bufp + extra_buffers_size;

off_min = 0;

for (; offp < off_end; offp++) {

//…

}

  • 通过参数传递的Binder对象需要进行转换,这里的for循环就是进行转换操作,内容较多,会在下面处理传递的Binder对象章节进行讲解
  1. 将本次调用的binder_transaction结构体链接到线程的binder_stack列表中

if (!(t->flags & TF_ONE_WAY)) {

//…

t->need_reply = 1;

t->from_parent = thread->transaction_stack;

thread->transaction_stack = t;

// 书中下面注释部分的代码已经整合到 binder_proc_transaction 函数中了

// 首先,将结构 binder_transaction中的binder_work放到目标目标进程或线程的todo队列

// 然后,创建一个新的binder_work的结构体,并将它放到发送线程的todo队列

if (!binder_proc_transaction(t, target_proc, target_thread)) {

//… 失败处理

}

}

  • binder_transaction结构中包含了一个binder_work的结构体,因此它可以被放到todo队列

  • 本地调用时binder_worktype被设置为BINDER_WORK_TRANSACTION后,插入了目标进程目标线程todo队列

  • 为了使客户进程能收到回复消息,这里也会创建一个新的binder_work的结构体,并把它的type设置成了BINDER_WORK_TRANSAXTION_COMPLETET,并将它插入到当前线程的todo队列

前面介绍过,在binder_thread_write函数执行完后,还会去判断是否需要执行binder_thread_read函数:

  • 由于调用端执行完BC_TRANSACTION后,会立刻执行ioctl指令

  • 所以,在调用操作上,binder_thread_read函数算是紧接着binder_thread_write函数后执行的

刚才新建的binder_work的结构体已经插入todo队列了,我们看下binder_thread_read函数会进行哪些和todo队列相关的操作:

while (1) {

//…

// 获得todo队列,获取失败则goto retry

if (!binder_worklist_empty_ilocked(&thread->todo))

list = &thread->todo;

else if (!binder_worklist_empty_ilocked(&proc->todo) &&

wait_for_proc_work)

list = &proc->todo;

else {

binder_inner_proc_unlock(proc);

/* no data added */

if (ptr - buffer == 4 && !thread->looper_need_return)

goto retry;

break;

}

//取出todo队列中的元素

w = binder_dequeue_work_head_ilocked(list);

//…

switch (w->type) {

//…省略大量case语句

case BINDER_WORK_TRANSACTION_COMPLETE: {

binder_inner_proc_unlock(proc);

cmd = BR_TRANSACTION_COMPLETE;

//把返回消息通过put_user放到用户空间的指针中

if (put_user(cmd, (uint32_t __user *)ptr))

return -EFAULT;

ptr += sizeof(uint32_t);

binder_stat_br(proc, thread, cmd);

kfree(w);

binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);

} break;

//…省略大量case语句

}

//…

}

  • binder_thread_read函数所做的处理就是把回复消息BR_TRANSACTION_COMPLETE复制到用户空间

  • 这样客户进程就可以收到回复消息了

到这里,客户进程的调用结束了。但是,Binder调用才完成一半,接下来,看看服务进程是如何调用数据的。

服务进程的调用流程

服务进程至少有一个线程会在ioctl上等待调用的到来。服务进程调用ioctl时传递的是读数据的请求,所以最后调用的也是binder_thread_read函数,我们看下binder_thread_read函数完整的处理流程:

  1. 如果保存返回结果的缓冲区中还没有数据,先写入BR_NOOP消息:

if (*consumed == 0) {

if (put_user(BR_NOOP, (uint32_t __user *)ptr))

return -EFAULT;

ptr += sizeof(uint32_t);

}

  1. 进入循环处理所有todo队列中的工作

while (1) {

//…

}

  1. 读取线程或进程todo队列中需要完成的工作

struct binder_work *w = NULL;

//…

if (!binder_worklist_empty_ilocked(&thread->todo))

list = &thread->todo;

else if (!binder_worklist_empty_ilocked(&proc->todo) &&

wait_for_proc_work)

list = &proc->todo;

//…

w = binder_dequeue_work_head_ilocked(list);

  1. switch语句处理所有类型的工作

switch (w->type) {

case BINDER_WORK_TRANSACTION: {

binder_inner_proc_unlock(proc);

t = container_of(w, struct binder_transaction, work);

} break;

}

  • 经过客户进程的调用流程后,此时的服务进程中已经存在一个类型为BINDER_WORK_TRANSACTION的工作需要处理

  • 这里只是取出了和binder_work关联的binder_transaction结构体指针

  • 并保存到变量t

  1. 调整线程的优先级

if (!t)

continue;

if (t->buffer->target_node) {

struct binder_node *target_node = t->buffer->target_node;

struct binder_priority node_prio;

tr.target.ptr = target_node->ptr;

tr.cookie = target_node->cookie;

node_prio.sched_policy = target_node->sched_policy;

node_prio.prio = target_node->min_priority;

binder_transaction_priority(current, t, node_prio,

target_node->inherit_rt);

cmd = BR_TRANSACTION;

}

  • 如果t为NULL,继续循环

  • 否则,开始准备返回的消息BR_TRANSACTION

  • 同时,设置线程的优先级

  • 如果调用线程的优先级低于当前线程指定的最低优先级,则把当前线程的优先级设为调用线程的优先级

  • 否则,把当前线程设为指定的最低优先级

  • 这意味着Binder线程会以尽量低的优先级运行

  1. 准备返回的数据

tr.code = t->code;

tr.flags = t->flags;

tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);

//…

// 让tr中的data指针指向内核中保存的数据缓冲区

tr.data_size = t->buffer->data_size;

tr.offsets_size = t->buffer->offsets_size;

tr.data.ptr.buffer = (binder_uintptr_t)

((uintptr_t)t->buffer->data +

binder_alloc_get_user_buffer_offset(&proc->alloc));

tr.data.ptr.offsets = tr.data.ptr.buffer +

ALIGN(t->buffer->data_size,

sizeof(void *));

// 把 BR_TRANSACTION 消息复制到用户空间

if (put_user(cmd, (uint32_t __user *)ptr)) {

//…

return -EFAULT;

}

ptr += sizeof(uint32_t);

if (copy_to_user(ptr, &tr, sizeof(tr))) {

//把结构体tr数据复制到用户空间

//…

return -EFAULT;

}

ptr += sizeof(tr);

//…

break;//跳出while循环

这一段代码都是为消息BR_TRANSACTION准备返回数据,要注意的是:

  • 调用copy_to_user复制到用户空间的只是结构体tr的数据

  • 服务进程得到这个结构体之后,会直接读取它里面的data指针的数据

  • 数据准备完毕后,使用break语句跳出while循环

  1. 启动新线程

if (proc->requested_threads == 0 &&

list_empty(&thread->proc->waiting_threads) &&

proc->requested_threads_started < proc->max_threads &&

(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |

BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */

/*spawn a new thread if we leave this out */) {

proc->requested_threads++;

//…

if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))

return -EFAULT;

}

当前线程要执行Binder调用,新来的调用也需要线程来处理,因此:

  • 函数结束前会检查进程中可用的线程数

  • 如果需要创建新线程,则在返回的buffer数据中增加BR_SPAWN_LOOPER消息,服务进程收到这个消息会启动新的线程

完整的Binder调用过程还需要把回复消息传递给客户进程,这个过程使用的函数还是前面的这些,暂时不分析了。消化一下先

处理传递的Binder对象


前面介绍了Binder对象传递的原理和用户层的实现。(原理上整个人还是晕晕的),我们来看下Binder驱动如何实现Binder对象的传递的。

  • Binder驱动中,代表每个进程的结构binde_proc中有两个字段:nodesrefs_by_node

  • 这两个字段各指向两颗红黑树的头

  • nodes:指向的是Binder节点对象表,储存本进程中Binder实体对象相关的数据

  • refs_by_node:指向的是Binder引用对象表,存储本进程的Binder引用对象的数据以及对应的实体对象的节点指针

来个抽象点的图:

Binder驱动中处理对象转换的代码位于函数binder_transaction

case BINDER_TYPE_BINDER:

case BINDER_TYPE_WEAK_BINDER: {

struct flat_binder_object *fp;

fp = to_flat_binder_object(hdr);

ret = binder_translate_binder(fp, t, thread);

} break;

static int binder_translate_binder(struct flat_binder_object *fp,

struct binder_transaction *t,

struct binder_thread *thread)

{

struct binder_node *node;

struct binder_proc *proc = thread->proc;

struct binder_proc *target_proc = t->to_proc;

struct binder_ref_data rdata;

int ret = 0;

//根据binder值查找Binder对象表中的节点

node = binder_get_node(proc, fp->binder);

if (!node) { //没有则新建一个节点

node = binder_new_node(proc, fp);

if (!node)

return -ENOMEM;

}

//…

if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {

ret = -EPERM;

goto done;

}

// 在接收端进程中寻找节点的引用,找不到会创建一个新的引用

ret = binder_inc_ref_for_node(target_proc, node,

fp->hdr.type == BINDER_TYPE_BINDER,

&thread->todo, &rdata);

if (ret)

goto done;

// 将传递的binder数据结构的type的值改为 BINDER_TYPE_HANDLE

if (fp->hdr.type == BINDER_TYPE_BINDER)

fp->hdr.type = BINDER_TYPE_HANDLE;

else

fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;

fp->binder = 0;

// rdata.desc存放的是节点引用表中的序号,赋值给handle

fp->handle = rdata.desc;

fp->cookie = 0;

//…

}

上面的流程是

  • 通过binder_get_node函数在发送进程的Binder对象节点表中查找节点

  • 查找是通过比较数据中的binder字段和节点中对应的字段进行的

  • binder字段中存放的是Binder对象的弱引用指针

  • 如果没找到

  • 新建节点

  • binder字段cookie字段的值保存到新节点

  • 通过binder_inc_ref_for_node函数在目标进程查找Binder节点引用

  • 没找到会新建一个

  • 引用指的是节点引用表中的refs_by_node节点,包含指向Binder节点的指针

  • 把数据的type字段的值改为BINDER_TYPE_HANDLEBINDER_TYPE_WEAK_HANDLE

  • handle字段的值设为在节点引用表的序号,这也是Binder引用对象handle值的来历

下面再看看如何处理类型BINDER_TYPE_HANDLEBINDER_TYPE_WEAK_HANDLE

case BINDER_TYPE_HANDLE:

case BINDER_TYPE_WEAK_HANDLE: {

struct flat_binder_object *fp;

fp = to_flat_binder_object(hdr);

ret = binder_translate_handle(fp, t, thread);

} break;

static int binder_translate_handle(struct flat_binder_object *fp,

struct binder_transaction *t,

struct binder_thread *thread)

{

//…

// 通过handle值查找节点引用

node = binder_get_node_from_ref(proc, fp->handle,

fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);

if (!node) {

//…

return -EINVAL;

}

//…

if (node->proc == target_proc) {

// 如果目标进程就是Binder对象的进程,开始转换

if (fp->hdr.type == BINDER_TYPE_HANDLE)

fp->hdr.type = BINDER_TYPE_BINDER;

else

fp->hdr.type = BINDER_TYPE_WEAK_BINDER;

fp->binder = node->ptr;

fp->cookie = node->cookie;

if (node->proc)

binder_inner_proc_lock(node->proc);

binder_inc_node_nilocked(node,

fp->hdr.type == BINDER_TYPE_BINDER,

0, NULL);

//…

} else {

//…

// 如果不是,则在目标进程新建一个Binder节点的引用

ret = binder_inc_ref_for_node(target_proc, node,

fp->hdr.type == BINDER_TYPE_HANDLE,

NULL, &dest_rdata);

//…

fp->handle = dest_rdata.desc;

fp->cookie = 0;

trace_binder_transaction_ref_to_ref(t, node, &src_rdata,

//…

}

}

这部分代码的流程是:

  • 通过传输数据的handle字段在发送进程的节点引用表中查找

  • 正常情况下是可以找到,不能就错误返回

  • 如果目标进程就是Binder对象所在的进程

  • 开始进行转换,把数据的type字段转为BINDER_TYPE_BINDERBINDER_TYPE_WEAK_BINDERBINDER_WORK_TRANSACTION

  • bindercookie字段设置为节点中保存的值

  • 如果目标进程不是Binder对象所在的进程

  • 在目标对象中建立一个节点对象的引用

到这里呢,Binder原理部分就差不多了,已经了解了包括:

  • 客户端调用接收消息的过程

  • 服务端监听回复消息的过

  • 驱动传输数据调度线程的操作

现在,我们再来看最后的一小部分:ServiceManager的作用

ServiceManager的作用

=================================================================================

关于ServiceManager,先简单描述:

  • ServiceManagerBinder架构中用来解析Binder名称的模块

  • ServiceManager本身就是一个Binder服务

  • ServiceManager并没有使用libbinder来构建Binder服务

  • 2020-09-04 同步了下项目代码,发现也替换成libbinder那一套了。。。。。

  • 不确定是不是更换仓储了

  • 要是这样就没意思了,简易版本对于加深binder理解还是很有帮助的

  • 我们暂且按照老的版本来看下吧

  • ServiceManager自己实现了一个简单的binder框架来直接和驱动通信。。。

ServiceManager源码路径在:frameworks/native/cmds/servicemanager,主要包含两个文件:

  • binder.c:用来实现简单的Binder通信功能

  • 简单版本的binder协议实现

  • 直接的ioctl操作与binder驱动通信

  • 不是本节的重点,感兴趣可以参照源码来学习啦

  • service_manager.c:用来实现ServiceManager的业务逻辑

  • 重点是了解ServiceManager如何响应Binder服务的注册和查询的

  • 这么重要的服务要在什么时间启动呢?

  • 这部分是在 system/core/rootdir/init.rc中配置

on post-fs

start essential services

start logd

start servicemanager

start hwservicemanager

start vndservicemanager

  • hwservicemanager 用来支持HIDL

  • vndservicemanager 第三方厂商使用,应该是从Treble架构中出现的

ServiceManager的架构


ServiceManagermain函数开始:

int main(int argc, char** argv)

{

struct binder_state *bs;

union selinux_callback cb;

char *driver;

if (argc > 1) {

driver = argv[1];

} else {

driver = “/dev/binder”;

}

bs = binder_open(driver, 128*1024);

if (!bs) {

//…

// 省略一些宏判断

return -1;

}

if (binder_become_context_manager(bs)) {

ALOGE(“cannot become context manager (%s)\n”, strerror(errno));

return -1;

}

// 设置selinux callback

cb.func_audit = audit_callback;

selinux_set_callback(SELINUX_CB_AUDIT, cb);

cb.func_log = selinux_log_callback;

selinux_set_callback(SELINUX_CB_LOG, cb);

//…

// 省略一些宏判断,都是为了获取sehandle(检查SELinux权限)

sehandle = selinux_android_service_context_handle();

selinux_status_open(true);

if (sehandle == NULL) {

ALOGE(“SELinux: Failed to acquire sehandle. Aborting.\n”);

abort();

}

if (getcon(&service_manager_context) != 0) {

ALOGE(“SELinux: Failed to acquire service_manager context. Aborting.\n”);

abort();

}

binder_loop(bs, svcmgr_handler);

return 0;

}

main函数的流程如下:

  • 首先调用binder_open来打开binder设备和初始化系统

  • 同时创建一块128x1024大小的内存空间

  • 接着调用binder_become_context_manager把本进程设置为Binder框架的管理进程

  • binder_become_context_manager函数代码如下:

int binder_become_context_manager(struct binder_state *bs)

{

return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);

}

  • 函数灰常简单,直接通过ioctl把控制命令BINDER_SET_CONTEXT_MGR发到了驱动

  • 最后执行binder_loop(bs, svcmgr_handler),简单处理后,循环等待消息

我们要说一说binder_loop(bs, svcmgr_handler),小弟C语言不熟,感觉这个操作很SAO:

  1. binder_loop的函数定义是:

void binder_loop(struct binder_state *bs, binder_handler func)

  • 第二个参数传入的是一个binder_handler类型
  1. 再来看下binder_handler类型的定义:

typedef int (*binder_handler)(struct binder_state *bs,

struct binder_transaction_data *txn,

struct binder_io *msg,

struct binder_io *reply);

  • 这是定义了一个函数指针类型

  • 返回值是int

  1. 再来看下binder_loop(bs, svcmgr_handler)中的svcmgr_handler函数的声明

int svcmgr_handler(struct binder_state *bs,

struct binder_transaction_data *txn,

struct binder_io *msg,

struct binder_io *reply)

{

//…

// 省略一些switch语句,稍后详解

return 0;

}

  • 传入的指针是svcmgr_handler函数指针

  • 应该会自动转型为binder_handler指针类型

  • 毕竟函数参数、返回值都是一样的

  • 这部分是我觉得最神奇的地方。。。。。

  • 特意写了个C代码试了下,

  • 如果定义的函数的参数返回值typedef定义的函数指针不一致的话,强转编译会失败

  • 不过这种操作感觉还是很不正经。。(还是Java更严谨一些)

  1. 参数理解的差不多了,我们仔细看下binder_loop的实现,这两个参数传进去干了啥。

PS:简化版就是更容易理解些。。。。。。

void binder_loop(struct binder_state *bs, binder_handler func)

{

int res;

struct binder_write_read bwr;

uint32_t readbuf[32];

// 老样子,用来记录写入到驱动的一些相关参数

bwr.write_size = 0;

bwr.write_consumed = 0;

bwr.write_buffer = 0;

// 该属性应该是通知驱动已经准备好接收数据了

readbuf[0] = BC_ENTER_LOOPER;

binder_write(bs, readbuf, sizeof(uint32_t));

for (;😉 {

//无限循环

bwr.read_size = sizeof(readbuf);

bwr.read_consumed = 0;

bwr.read_buffer = (uintptr_t) readbuf;

// 开始读取数据

res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

if (res < 0) {

// 异常通信,退出循环

ALOGE(“binder_loop: ioctl failed (%s)\n”, strerror(errno));

break;

}

// 读取到数据后

// 调用 binder_parse 进行数据解析

// 顺便把 binder_handler 函数指针也传递进去

res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);

// 异常情况,退出循环

if (res == 0 || res < 0) {

//…

break;

}

}

}

  • 有注释,很很简洁的逻辑

  • 最后执行到了binder_parse函数

  1. 我们继续跟进binder_parse函数

int binder_parse(struct binder_state *bs, struct binder_io *bio,

uintptr_t ptr, size_t size, binder_handler func)

{

int r = 1;

uintptr_t end = ptr + (uintptr_t) size;

while (ptr < end) {

//取指令

uint32_t cmd = *(uint32_t *) ptr;

switch(cmd) {

//…

// 由于binder_handler 在这里会被执行到

// 所以我们先重点看这个,当Binder调用过来时

case BR_TRANSACTION: {

struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;

// 数据检查

if ((end - ptr) < sizeof(*txn)) {

ALOGE(“parse: txn too small!\n”);

return -1;

}

binder_dump_txn(txn);

// 如果函数指针存在

if (func) {

unsigned rdata[256/4];

struct binder_io msg;

struct binder_io reply;

int res;

// 一些初始化操作

bio_init(&reply, rdata, sizeof(rdata), 4);

bio_init_from_txn(&msg, txn);

//执行 binder_handler 函数指针指向的函数

res = func(bs, txn, &msg, &reply);

if (txn->flags & TF_ONE_WAY) {

// 这种情况不会返回结果

binder_free_buffer(bs, txn->data.ptr.buffer);

} else {

// 返回结果数据

binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);

}

}

ptr += sizeof(*txn);

break;

}

//…

}

return r;

}

  • 还是看注释哈,代码很简洁,注释很详细,哈哈哈

  • 到这里,我们可以看出来,真正处理调用服务的是binder_handler这个函数指针啊

  • 也就是说远程调用的处理逻辑在svcmgr_handler这个函数呀

  • 666!

  1. 我们来看下svcmgr_handler函数

int svcmgr_handler(struct binder_state *bs,

struct binder_transaction_data *txn,

struct binder_io *msg,

struct binder_io *reply)

{

//…

// 如果请求的目标服务不是ServiceManager,直接返回

if (txn->target.ptr != BINDER_SERVICE_MANAGER)

return -1;

// 如果请求消息内容只是简单的测试通路,不需要继续执行,直接返回 0

if (txn->code == PING_TRANSACTION)

return 0;

// 检查收到的消息 id 串

strict_policy = bio_get_uint32(msg);

s = bio_get_string16(msg, &len);

if (s == NULL) {

return -1;

}

//…

// 检查SELinux 的权限

if (sehandle && selinux_status_updated() > 0) {

struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();

if (tmp_sehandle) {

selabel_close(sehandle);

sehandle = tmp_sehandle;

}

}

switch(txn->code) {

case SVC_MGR_GET_SERVICE:

case SVC_MGR_CHECK_SERVICE:

// 处理查询或者获取服务的指令

//…

break;

case SVC_MGR_ADD_SERVICE:

// 处理注册服务的的指令

//…

break;

case SVC_MGR_LIST_SERVICES: {

//…

// 处理获取服务列表的指令

}

default:

ALOGE(“unknown code %d\n”, txn->code);

return -1;

}

//发送返回消息

bio_put_uint32(reply, 0);

return 0;

}

  • ServiceManager的架构非常简单高效,只有一个循环来和binder驱动进行通信

ServiceManager提供的服务


svcmgr_handler函数中可以看到,ServiceManager提供了三种服务功能:

  • 注册Binder服务

  • 查询Binder服务

  • 获取Binder服务列表

注册Binder服务


case SVC_MGR_ADD_SERVICE中实现的注册Binder服务功能,具体的实现函数是do_add_service,代码如下:

int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle,

uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid) {

struct svcinfo *si;

// 一些基础的信息判断

if (!handle || (len == 0) || (len > 127))

return -1;

// 检查调用进程是否有权限注册服务

if (!svc_can_register(s, len, spid, uid)) {

//…省略log打印

return -1;

}

// 查看要注册的服务是否已经存在

si = find_svc(s, len);

if (si) {

// 如果存在,先把以前的Binder对象的引用计数减一

if (si->handle) {

svcinfo_death(bs, si);

}

// 把原先节点中的handle替换成新的handle

si->handle = handle;

} else {

// 服务不存在,则生成新的列表项,初始化后加入列表

si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));

if (!si) {

// 内存申请失败,直接退出

return -1;

}

// 一些初始化操作

si->handle = handle;

si->len = len;

memcpy(si->name, s, (len + 1) * sizeof(uint16_t));

si->name[len] = ‘\0’;

si->death.func = (void*) svcinfo_death;

si->death.ptr = si;

si->allow_isolated = allow_isolated;

si->dumpsys_priority = dumpsys_priority;

si->next = svclist;

svclist = si;

}

// 增加Binder服务的引用计数

binder_acquire(bs, handle);

// 注册该Binder服务的死亡通知

binder_link_to_death(bs, handle, &si->death);

return 0;

}

do_add_service函数的流程是:

  • 首先检查调用的进程是否有注册服务的权限

  • 这部分是通过SELinux来控制的,后面会学习到

  • 接着检查需要注册的服务是否已经存在

  • 存在,把原来Binder服务在驱动的引用计数减一

  • 不存在

  • 新创建一个scvinfo结构

  • 填充需要待注册服务的相关信息到结构中

  • 把结构加入到服务列表svclist

  • 通知内核把Binder服务的引用计数加一

  • 注册该服务的死亡通知

有木有发现对于服务已经存在的情况:

  • 进行了先减一再加一的操作

  • 是不是可以不用操作计数也可以呢?

可以思考下

查询Binder服务


case SVC_MGR_CHECK_SERVICE中处理查询服务的功能,具体的实现接口是do_find_service函数,代码如下:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

实战系列

话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示


《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

e = handle;

} else {

// 服务不存在,则生成新的列表项,初始化后加入列表

si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));

if (!si) {

// 内存申请失败,直接退出

return -1;

}

// 一些初始化操作

si->handle = handle;

si->len = len;

memcpy(si->name, s, (len + 1) * sizeof(uint16_t));

si->name[len] = ‘\0’;

si->death.func = (void*) svcinfo_death;

si->death.ptr = si;

si->allow_isolated = allow_isolated;

si->dumpsys_priority = dumpsys_priority;

si->next = svclist;

svclist = si;

}

// 增加Binder服务的引用计数

binder_acquire(bs, handle);

// 注册该Binder服务的死亡通知

binder_link_to_death(bs, handle, &si->death);

return 0;

}

do_add_service函数的流程是:

  • 首先检查调用的进程是否有注册服务的权限

  • 这部分是通过SELinux来控制的,后面会学习到

  • 接着检查需要注册的服务是否已经存在

  • 存在,把原来Binder服务在驱动的引用计数减一

  • 不存在

  • 新创建一个scvinfo结构

  • 填充需要待注册服务的相关信息到结构中

  • 把结构加入到服务列表svclist

  • 通知内核把Binder服务的引用计数加一

  • 注册该服务的死亡通知

有木有发现对于服务已经存在的情况:

  • 进行了先减一再加一的操作

  • 是不是可以不用操作计数也可以呢?

可以思考下

查询Binder服务


case SVC_MGR_CHECK_SERVICE中处理查询服务的功能,具体的实现接口是do_find_service函数,代码如下:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-U3gc4yYh-1713678064079)]

[外链图片转存中…(img-XS8rnrxa-1713678064080)]

[外链图片转存中…(img-O57cDBbA-1713678064081)]

[外链图片转存中…(img-GTsVjKTw-1713678064082)]

[外链图片转存中…(img-hxVJw9RL-1713678064083)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

实战系列

话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示

[外链图片转存中…(img-WUonwriT-1713678064085)]
[外链图片转存中…(img-l8wRHgGY-1713678064086)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值