Android Binder 分析

写在前面

回看了一下,发现有近两年断更了。因为在18年换了新工作后就一直没有时间管理自己的博客,当然也不是这段时间没有写文章,主要是因为公司自己有一个内部的平台,文章或者工作中的总结都发到公司内网去了(毕竟kpi要求)。最近想了想,还是想把自己的博客给运行起来,当作总结也好分享也罢。

进入正题

没想到自己差点被太监的,第一篇续更的文章就是Android Binder。一说到binder其实我的内心是恐惧的,生怕自己说不好,或者有误导读者的地方。如果有的话,大家也可以在评论中留言,我们可以一起探讨一下,另外本文参考了很多的他人的博客或者书籍,如果有侵权的地方也可以联系我。

为什么Android撇弃了传统的进程间通讯方式,设计了binder机制

众所周知,传统的linux系统有一下几种进程间通讯的机制,包括
1 管道
2 共享内存
3 socket
4 信号量
5 消息队列
但是当我们去熟悉Android的系统的时候,会发现传统的ipc通讯方式在Android 系统中用的非常的少,Android只要有涉及到需要跨进程的,首选必然就是binder,当然也有例外,那就是zygote在fork进程的时候,采用的是socket的方式,而是不是binder。为什么这个时候要用socket而不是binder呢?感兴趣的话我可以另外写一篇文章去介绍,这里可以先提一个小点,就是线程安全。
ok这里不扯远了。我们先介绍一下binder较传统ipc方面的优势
1 高效。binder在进行数据传输的时候,只需要把数据从用户的内存拷贝到内核内存中,只需要一次copy from user的动作,而传统的IPC进程通讯的方式,需要两次的copy from user,这个流程我们下文会进行介绍。
2 安全。binder在传输通讯的时候,会校验当前的uid和pid,而这个uid和pid是Android系统给每个应用程序生成的,无法篡改的。server端或者client端都可以校验当前来自某个进程的通讯是不是自己的“白名单”内的消息,如果不是就拒绝处理当前的消息,这样就可以防止恶意进程攻击或者篡改当前应用进程的数据。传统的共享内存自然就没有这个优势,因为传统的共享内存中当前进程的uid/pid都是请求方或者发送方自己生成的,非常的不可靠,容易被黑产进行攻击。

Binder 通讯的主要流程

Android Binder 分析_第1张图片

对于整个Binder体系最重要的核心就是上面的几个大部分,分别是Client 端,Server 端,ServiceManager 和Binder 驱动端。因此我们可以看出,binder架构设计也是基于C/S架构进行设计的
整个binder的工作流程就是
1 serviceManager 向binder驱动注册,表名自己的ServiceManager身份
2 server通过binder,向ServiceManager注册服务addService()
3 client 通过binder,向ServiceManager查询当前需要的server,如果查询到,那么把当前的server通过binder返回给client端。

认识ServiceManager

我们从上文知道,serviceManager其实就是管理各个server的一个进程。那么binder驱动要怎么知道当前注册的进程是ServiceManager而不是其他的普通进程呢?其实很简单,ServiceManager是通过两个步骤去告诉binder驱动当前我需要成为一个ServiceManager的
1 注册binder的时候,发送BINDER_SET_CONTEXT_MGR 命令
2 Binder 驱动返回当前ServiceManager的handle =0

一个进程,它成为ServiceManager的时候,它需要做什么?

打开binder 驱动

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
   // 创建进程对应的binder_proc对象
    proc = kzalloc(sizeof(*proc), GFP_KERNEL); 
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    proc->tsk = current;
    // 初始化binder_proc
    INIT_LIST_HEAD(&proc->todo);
    init_waitqueue_head(&proc->wait);
    proc->default_priority = task_nice(current);
    binder_lock(__func__);
    binder_stats_created(BINDER_STAT_PROC);
    // 添加到全局列表binder_procs中
    hlist_add_head(&proc->proc_node, &binder_procs);
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;
    binder_unlock(__func__);
    return 0;
}

从上面的代码块我们也可以看出,binder驱动维护了一个叫binder-procs的列表,每一个binder对象都有一个binder-proc。
其实binder的结构体是一个挺“多元化”的东西,里面包含了很多的数据结构

name 解释
binder-proc 描述当前binder的进程
binder-thread 描述当前binder的线程
binder-ref 当前binder的实际引用
binder-buffer 当前binder存储的实际数据
binder-node 当前binder的实际节点

binder-proc里面包含这很多的信息,比如thread、node、buffer、ref等,都是跟binder的数据结构相互引用,主要是为了便于相互查找

采用mmap 映射内存地址

从标题中我们也可以看到,binder采用调用mmap方法,主要是为了两个作用
1 向内核内存中申请一块内存,用于接受binder通讯中的数据
2 将当前的内存地址进行映射,那么以后binder读取数据的时候只需要读取这个内存地址,即可进行数据的读取和写入。这也就是为什么binder在进行通讯传输数据的时候,只需要一个copy from user的原因。
好了我们总结这个流程

1 binder驱动通过 mmap,向内核空间申请块内存,并同时把这块内存映射到server端
2 client端(这里client/server/serviceManager都可以成为client端)通过BINDER-READ-WRITE 向binder发送读写命令,同时把用户内存空间中的数据copy到第一步申请到的内核空间中
3 binder驱动收到命令后,发送Br-transaction 命令,向server端发出当前需要处理用户请求的指令,由于这个内存已经在用户空间进行了映射,所以server端可以读取到这一部分的数据

开启looper循环处理client端请求,提供server端注册能力

查询当前注册列表,将符合client端查询结果的server返回给client端。ServiceManager内部保存了一个叫svclist的列表,用于保存已经注册成功的server,那么client进行查询的时候,自然也是查询这个列表。

Server 端流程分析

同样的,在server需要向ServiceManager 注册服务的时候,也同样需要使用binder进行通讯,那么自然的也需要和ServiceManager一样的流程

1 打开binder 驱动,把自己当前的binder-proc放入binder-procs列表中
2 使用mmap 申请和映射内存
3 这个时候就不是开启looper循环了,而是addServices

Client 端流程分析

client 端流程和server 端类似,这里不做赘述。只是要明确Client端向serviceManager查询的时候,使用的是字符串进行查询,如果ServiceManger 查询到对应的server,会返回当前server的句柄给到Client端,这样经过一系列的转换,Client 端就可以调用到Server端中的消息了

最后的小结

上面的流程分析偏向于驱动层,其实framework层的流程跟这个分析其实都是一样的,只是framework层调用的c++方法去调用驱动层的方法,流程其实是一样的。

你可能感兴趣的:(Android Binder 分析)