注册

如何深入掌握 Android 系统开发的拦路虎 Binder

0. 为什么要深入学习 Binder



  • Binder 是整个 Android 的基石

    • 所有的系统服务都是基于 Binder,比如 AMS WMS PMS SurfaceFlinger Audiofilinger 以及硬件操作服务等等
    • Android 四大组件的底层实现离不开 Binder


  • 做系统开发需要自定义一些系统服务,这些工作需要我们了解 Binder
  • Android O 以后的 Treble 计划,基于 Binder 魔改出了 HwBinder VndBinder。
  • ANR 冻屏 卡顿 卡死等偶现 BUG 可能与 Binder 相关

1. 学习 Binder 的四个阶段



  • 会用,能添加 Java Native 系统服务
  • 熟读应用层各种情景下的源码
  • 熟读内核里面的数据结构和流程
  • 能解决各种奇奇怪怪的 bug

2. 准备工作


下载编译好 AOSP + Kernel,能通过自定义内核的方式启动虚拟机。


这部分内容比较简单,可以参考:



3. 预备基础知识


预备基础知识快速过一遍,忘了再回头再看



4. Binder 基本原理


首先要明确一点 Binder 是一个 RPC(Remote Procedure Call) 框架,也就是说借助于 Binder,我们可以在 A 进程中访问 B 进程中的函数。


4.1 IPC 原理


RPC 一般基于 IPC 来实现的,IPC 就是跨进程数据传输,大白话就是在 A 进程可以访问到 B 进程中的数据,或者说 B 进程中的数据可以传递给 A 进程,都是一个意思。


在 Linux 中,每个进程都有自己的虚拟内存地址空间。虚拟内存地址空间又分为了用户地址空间和内核地址空间。



不同进程之间用户地址空间的变量和函数是不能相互访问的。


使得 A 进程能访问到 B 进程中数据的手段我们就称之为 IPC。


虽然用户地址空间是不能互相访问的,但是不同进程的内核地址空间是相同和共享的,我们可以借助内核地址空间作为中转站来实现进程间数据的传输。


具体的我们在 B 进程使用 copy_from_user 将用户态数据 int a 拷贝到内核态,这样就可以在 A 进程的内核态中访问到 int a



更进一步,可以在 A 进程中调用 copy_to_user 可以将 int a 从内核地址空间拷贝到用户地址空间。至此,我们的进程 A 用户态程序就可以访问到进程 B 中的用户地址空间数据 int a



为了访问 int a ,需要拷贝两次数据。能不能优化一下?我们可以通过 mmap 将进程 A 的用户地址空间与内核地址空间进行映射,让他们指向相同的物理地址空间:



完成映射后,B 进程只需调用一次 copy_from_user,A 进程的用户空间中就可以访问到 int a了。这里就优化到了一次拷贝。


4.2 RPC 原理


接着我们来看以下,Binder 的 RPC 是如何实现的:


一般来说,A 进程访问 B 进程函数,我们需要:



  • 在 A 进程中按照固定的规则打包数据,这些数据包含了:

    • 数据发给那个进程,Binder 中是一个整型变量 Handle
    • 要调用目标进程中的那个函数,Binder 中用一个整型变量 Code 表示
    • 目标函数的参数
    • 要执行具体什么操作,也就是 Binder 协议


  • 进程 B 收到数据,按照固定的格式解析出数据,调用函数,并使用相同的格式将函数的返回值传递给进程 A。


Binder 要实现的效果就是,整体上看过去,进程 A 执行进程 B 中的函数就和执行当前进程中的函数是一样的。


5. Binder 应用层工作流程


Binder 是一个 RPC(Remote Procedure Call) 框架,翻译成中文就是远程过程调用。也就是说通过 Binder:



  • 可以在 A 进程中访问 B 进程中定义的函数
  • 进程 B 中的这些等待着被远程调用的函数的集合,我们称其为 Binder 服务(Binder Service)
  • 进程 A 称之为 Binder 客户端(Binder Client),进程 B 称之为 Binder 服务端(Binder Server)
  • 通常,系统中的服务很多,我们需要一个管家来管理它们,服务管家(ServiceManager) 是 Android 系统启动时,启动的一个用于管理 Binder 服务(Binder Service) 的进程。通常,服务(Service) 需要事先注册到服务管家(ServiceManager),其他进程向服务管家(ServiceManager) 查询服务后才能使用服务。
  • Binder 的 RPC 能力通过 Binder 驱动实现

通常一个完整的 Binder 程序涉及 4 个流程:



  1. 在 Binder Server 端定义好服务
  2. 然后向 ServiceManager 注册服务
  3. 在 Binder Client 中向 ServiceManager 获取到服务
  4. 发起远程调用,调用 Binder Server 中定义好的服务

整个流程都是建立在 Binder 驱动提供的跨进程调用能力之上:



6. Android Binder 整体架构


从源码实现角度来说,Binder 整体架构实现如下:



有点复杂,我们一点点说:




  • VFS 是内核中的一个中间层,向上对应用层提供统一的系统调用函数,这些系统调用函数主要是 open mmap ioctl write read ioctl 等,向下封装不同的外设(字符设备,块设备),系统文件,文件系统的操作。Binder 是一个字符驱动,当应用层调用到 binder 的 open mmap ioctl release 系统调用时,经过 vfs 的一层包装后,就会调用到 Binder 驱动中的 binder_open bider_mmap binder_ioctl binder_release 函数。




  • 不同于一般的驱动,Binder 应用层的使用要复杂不少,如果直接使用 open mmap ioctl release 系统调用会使得应用程序非常复杂且难以复用相同功能的代码,刚开始 google 的工程师做了一套简单的封装,把常用的操作封装为一系列的函数,这些函数都在 binder.c 中,ServiceManger 的就是通过 binder.c 中封装的函数实现的(Android10及以前)。源码中还存在一个 bctest.c 的程序,这个是 binder.c 的一个测试程序。C 语言级别的封装虽然简单,但使用起来还是稍显麻烦,很多细节也没有考虑进去,所以 google 的工程师又封装了一个叫 libbinder 的库,我们 native 层的 binder 服务端与客户端都是基于这个库来实现的,Java 层的 binder 服务端与客户端都是通过 JNI 间接使用 libbinder 库实现的,从使用上来说 libbinder 更为简单,但是 libbinder 本身比 binder.c 复杂了不少。




7. C 层实现分析


很多博客教程会忽略这一层的分析,相比 libbinder 库的封装,binder.c 会简单不少 ,方便初学者理解 binder 应用层工作流程。


我们可以模仿 bctest.c 写一个完整的 Binder 应用层 demo。


这个工作已经有大佬完成了:


github.com/weidongshan…


但是也有一些问题,这个代码是基于 Android5 的,稍微有点老了,我在以上实现的基础上做了一些修改和适配工作,使得代码可以在 Android10 上跑起来:


github.com/yuandaimaah…


关于这个示例程序的分析,可以参考以下几篇文章:



8. 驱动分析


驱动分析这部分结合 C 层应用的实现来分析驱动的实现,主要搞清楚:



  • 三个情景的流程:注册,获取,使用
  • 三个情景下内核中各种数据结构的变化

这部分内容可以参考之前分享的:



9. C++ 层分析


首先我们要写一个基于 libbinder 库的 Demo,能跑起来就行:



接着分析三个情景下的执行过程与各个函数的功能:



当然还有两个特殊的场景也需要进行分析:



  • 死亡通知
  • 多线程

这部分内容会陆续在公众号和掘金平台推送。


10. Java 层分析


学习这部分的前提是了解 JNI 编程。这个可以参考系列文章:



我们先写一个 Demo,能跑起来就行:



接着我们分析三个情景下的执行过程与各个函数的功能:



当然还有一些其他高级特性也需要我们分析,这部分内容会在后续推送:



  • AIDL 中 in out inout oneway 的分析
  • Parcel 数据结构分析
  • Java 层死亡通知
  • Java 层多线程分析

11. 疑难问题


不论是应用开发还是系统开发我们都会遇到一些棘手的 bug,很多时候这些 bug 都和 binder 有关,总结起来,大概可以分为几类:



  • 死锁
  • 线程池满了
  • 代理对象内存泄露
  • 传输数据过大
  • 关键方法内发起 Binder 同步调用导致卡顿
  • Android O 异步远程调用无限阻塞冻屏 bug

这类 bug 很多都难以复现,很多时候都不了了之了,导致拥有这部分经验的同学很少。


这部分内容工作量巨大,我会在接下来的时间陆续在公众号和掘金推送相关的文章。


作者:阿豪元代码
链接:https://juejin.cn/post/7248801879958503480
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册