Binder 设计篇分析

Android Binder

Binder 是安卓进程间通讯(IPC)方式之一,同时也是一种RPC调用方式之一, 其实Linxu 实现有socket,信号量等进程间通讯方式
Binder和传统的进程间通讯,有无可以比拟的优势.Binder提供以下几个功能

  • 进程间通讯
  • 共享内存(主要靠传递文件描述符实现)
  • 多线程支持(线程池)
  • 进程间同步异步调用
  • 对象引用计数,死亡通知
  • 用户校验(pid uid)

其中最常用的就是Binder同步调用,跟人感觉就是像 在一个进程里面调用感觉一样,其实最主要的实现思想就是利用Binder驱动实现两个线程的同步协作(只不过这两个线程在不同的进程),调用方请求服务端时候通过系统调用进入内核态,然后挂起当前线程,然后唤醒目标进程,目标进程读取通讯数据,然后调用相关接口,之后再通过系统调用进入内核态,然后挂起当前线程,唤醒调用进程,这是最常用功能,当然也有异步调用,也就是onway的调用,客户端不care 服务端返回的结果.

[TOC]

Android Binder设计篇

Binder是典型的C/S架构 ,服务端等待客服端调用,如果服务端没有客服端调用,服务端进程通过系统调用进入内核态,之后让出CPU使用权,进入休眠状态,此时调度器不会调度当前线程.客户端调用的时候就会挂起客户端线程,唤醒目标线程,这里就涉及一个问题就是,如何找到目标线程,以及目标线程处理之后怎么找到调用者线程的问题,这方面就要借助Binder 驱动了,和ServiceManger,其中对于系统级别的服务,都注册到ServiceManger中,其中注册到ServiceManger又是一个IPC过成,Linux内核空间是共享的,用户进程空间是独立的,对于4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,IPC实现主要借助内核空间,只不过这个内核空间和用户空间被映射到同一个物理页上,驱动找到目标进程的时候,会在目标进程分配传输数据的空间,直接把客户端数据copy到内核地址空间,只不过这个内核地址空间,在目标进程中也可以访问,目标进程用户空间分配接收数据的空间,和内核空间的起始地址错一个固定的偏移量,通过偏移量就可以算出数据在用户空间的地址.这些数据就是就是序列化的的数据(Parcel),这样就实现客户端的数据通过一次Copy就到了目标进程,不用通过先copy的内核空间,之后再从内核空间copy到目标进程空间,这也是一次copy的根源,当然在IPC过程中也有少量通讯协议数据由客户端copy到内核态,再通过内核态copy到用户空间,但是这些数据相对于通讯的数据是小之又少的.

1 总架构

这里需要介绍binder架构的中几个相关名词.(这里直接拿着相关数据结构来说明更加直接)

  • binder_node和BBbinder
  • binder_ref BPBinder
  • ServiceManger
  • BinderProc
  • Binder线程池
  • binder_buffer( 接受数据内存映射)
  • ProcessState(进程相关数据结构)
  • IPCThreadState

这里还涉及几个名词SeviceManger 系统服务(有名binder),匿名binder(Service其实里面可以有匿名binder). 其中ServiceManger 是系统服务的大管家,有名binder的通过名字存放在这里面这里,ServiceManger进程保存binder引用值,启动进程可以通过0号binder引用访问ServiceManger,然后获得对应的有名Binder的引用,只不过Binder驱动在传输有名Binder引用的同时会对有名Binder的引用的值进行更改,在不同进程相同的binde引用,可能对应的不同的binder实体,不同Binder引用可能对应者相同的binder实体,这个有点像文件描述符,在不同的进程控件文件文件描述符不同,但是在内核空间对应同一个文件结构体,只不过在这里的引用对应这相同的binder_node.

相关数据模型

2 ServiceManger

ServiceMnanger作为系统服务的大管家,当然比系统服务先起来,这里可以从init.rc里面看出来.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
on boot
..............
class_start core
class_start main
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
onrestart restart qcamerasvr
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
onrestart setprop ro.yulong.ylcrash syscrash
onrestart restart qcamerasvr
onrestart start crash_and_reset

可以看出on boot阶段 先class_start core 之后在class_start main ,servicemanger 属于class core 类型的service 而zygote 属于class main 类型的service.

servicemanager 进程起来主要做三件事

  • 映射一个内核和进程公用的空间(128K) 接收远程调用传来的数据
  • 通知内核当前进程为servicemanger 为 Service大管家,其实就是在内核初始化一个全局变量binder_context_mgr_node ,该binder node 对应的引用为0,这也就是系统能通过固定的手法,RPC调用的原因.
  • 当前进程监听远程调用,如果没有调用则进入休眠状态.

其中svcinfo结构体对应一个系统服务,name 是系统服务的名字, ptr 是相应binder实体的引用在servicemanger进程的句柄, svclist 是链表头,获取系统服务一般只需提供name 然后通过svclist 编译链表找到对用的binder实体的引用.

3 注册获取系统服务

系统服务就是有名Binder实体,这些服务注册到ServiceManager , 客户端可以通过ServiceManager获取相关服务端的合法代理!,其中system_server 里面注册很多系统级别的Services 比如 AMS PMS WMS 等,当然应用也可以注册系统服务,比如Phone , NFC 蓝牙, 因为NFC 蓝牙一般和硬件相关具体的实现不同,所以放在一个APK里面更好维护和验证问题, 至于AMS PMS WMS 为啥在一个进程,一个主要原因可能是这些服务之间相互依赖,而减少RPC调用! 这里大致给一个图.具体细节可以看代码跟踪

至于匿名Binder 一般都是通过有名Binder 获取的 其中比较典型的就是Service ,这个是靠AMS和PMS 协同合作获取匿名的Binde实体的合法代理,这样客户端就可以调用服务端,这里AMS扮演一个匿名Service大管家!其实AMS功能不止这些,还有Activity 广播 其中动态广播的注册也涉及匿名Binder .这里就不细说了!

4 binder RPC 细节

这里假定已经通过ServiceManager或者有名Binder实体获取合法代理端,之后就是拿着代理端访问服务端实现同步调用,其中调用可以分one_way 和非one_way,其中one_way 客户端不care服务端的结果或者不care是否调用到服务端!, no_way 可以带回结果或者异常!

其中最核心的就是在内核态实现相关的线程的唤醒和休眠而实现同步RPC调用!

4.1 client-server端 one_way 调用流程.

service端一般都是一个循环,监听客户端的请求,当客户端有请求的时候一般都会开启一个线程来处理客户端的请求,这样有多个client 请求的时候就能及时响应.client 调用server端一般分为两种,一种是需要返回结果的,一种是不care结果的.

4.2 client-server端 none_one_way 调用流程.

5 binder 内核数据结构以及相关结构

内核的数据接口主要设计数据的传输相关摘要,比如客户端数据传来的大小和相关binder实体在数据中的位置, 线程的to-do 队列 binder实体引用的相关数据结构 内存管理和释放相关的红黑树! 全局的进程对象相关的数据结构

其中和和通讯协议相关的数据结构

6 binder 一次copy数据 和内存管理

Binder对于协议数据其实也是copy两次,只不过是用户数据是copy一次,相对于用户数据,协议数据基本可以忽略不计,这里只说下一次copy的原理,一次copy就是把用户进程的数据同时映射到内核空间和用户空间,驱动管理释放的分配!

7 binder 多线程支持

其实binder驱动已经帮我们实现多线程支持,就如服务器支持多线程一样!而且有相关协议设置支持线程的个数! 一般是16 但是对已ServiceManager这种服务是4个! 一般Service都会起来两个线程,一个是主线程非Binder 线程,和Binder线程,其中这两个线程都Wait到进程的Thread todo 队列 刚开没数据 所以都休眠,只有有请求起来就会唤醒这两个线程的其中之一,此时就会检查可用线程的个数如果可用的为0 就会通请求一起发送到服务端,服务端会起来一个线程继续wait,启动实现一个比较简单方法管理这些binder线程,就是不够就建立直到上限!,之后就不回收了! 除非系统回收相关进程!

8 总结

其实binder里面细节远非这些,都经过很多优化,对于业务端只有care 数据封包和解包,以及通过binder驱动来注册相关死亡监听来完成一些清理工作,其实网上早有大牛总结过binder的设计篇,读过代码过后,再去看总结,豁然开朗,其实binder早就想啃下,只不过当时水平不够,看了一点就看不动了,尝试过很多次,在工作中业余实现也一直在看,其实具体的细节还是没能看完,只不过了解了具体的实现思想和使用方法! 其实这些思想都是在做交通卡业务中看BaodingZhou的对于公交卡的RPC调用的写法再回顾下Binder 慢慢才能消化掉binder基本原理.其中部分图取决网上,时间有限,没能够坚持都自己绘制一遍.实在惭愧!

最后奉上这份神总结! 真是句句都是真经!

Android Binder设计与实现 - 设计篇