binder机制比较复杂,学习梳理过程中基本把网上可以搜到的文章都看了一遍 下面自己试着梳理一下,也会贴出参考的相关链接
https://blog.csdn.net/universus/article/details/6211589
为什么需要IPC
Android基于Linux,不同应用在不同的进程里,内存空间是隔开的,无法直接互相交互数据。 但安卓是有进程间通讯IPC的需求,则系统提供了Binder方式进行IPC。
为什么选Binder
https://www.zhihu.com/question/39440766 Linux本身有IPC方案,共享内存、管道、socket等, 但Android是取了一个性能、稳定型和安全性等的平衡点; 共享内存:0次数据拷贝,但无法直接解决进程间同步问题 管道/消息队列/socket:需要2次数据拷贝;
Binder机制下IPC的流程
因为是CS结构,所以分为Client和Server,我们日常使用中大部分是作为Client方的。 Android代码中直接context.getSystemtService(xxx)就是获取不同系统已封装好的Server的方法。 如 InputMethod / Download / Windown + Manager,为什么都是manager?
但Client不是直接和Server交互的,Android中是通过一个ServiceManager进行统一管理分配, SM 主要负责Server的注册,和Client的查找。 下图中:
- 灰色线 - 注册服务
- 紫色线 - 查询服务
- 蓝色+绿色线 - Client向Server端发起请求,并收到处理后的响应
传统的2次拷贝socket都是,用户空间发送方数据拷贝到内核空间,再内核空间拷贝到接受方的用户空间。
由于用户空间都是虚拟地址,实际会对应到物理内存的地址的~ 对于binder,会在接收方和内核之间做个映射,最终都指向同一个物理内存地址。 因此当发送方拷贝到内核缓存区后,接受方直接从该地址取数据即可,无需再次拷贝操作。
所以上图共享内存部分可能会有点误解,应该是映射表
具体流程
以上是总体流程,下面是具体
图片来源于 https://blog.csdn.net/freekiteyu/article/details/70082302
参考文章 http://gityuan.com/2015/11/07/binder-start-sm/ http://gityuan.com/2015/11/14/binder-add-service/
1. ServiceManager的启动
系统的init.rc调用service_manager.c的main函数入口
- binder_open 打开驱动并申请128k内存空间
- 系统调用open打开驱动,并创建全局链表binder_procs
- 系统调用mmap进行内存映射
- get_vm_area 分配一个连续的内核虚拟空间,大小和用户虚拟空间一致
- binder_update_page_range 分配物理空间,并将物理空间分别映射到内核和用户空间
- binder_become_context_manager 设为管家
- binder_loop 开始循环等待client请求
2.服务的注册添加(以MediaService为例)
- ProcessState.self 单例创建PS对象
- open_driver 打开Binder驱动
- mmap 给binder分配一块虚拟地址空间并处理映射,大小为 1M-8K(同SM启动中的mmap)
- 默认设置最大并发访问线程数未 16
- defaultServiceManager 获取BpServiceManager对象
- MediaService.instantiate 初始化注册所需服务
- new MediaService() 新建服务的对象
- BpServiceManager.addService(“media.player”, mediaService) 注册服务
- 创建数据包data,写入名字、service对象等信息
- BpBinder.transact(ADD_SERVICE_TRANSACTION, data)
- BpBinder是代理类?实际调用是交给 IPCThreadState.transact
- ProcessState -> startThreadPool 启动Binder线程池
- IPCThreadState:self -> joinThreadPool 当前线程加入到线程池
- self 初始化创建IPCTS对象,并分配线程本地储存空间 TLS
3.获取服务
- defaultServiceManager 获取BpSM对象
- BpSM.getService(“media.player”) 通过名字获取服务
- 和注册流程一样,也是通过代理最终调用 IPCThreadState.transact 传递消息
- 传递的是一个 CHECK_SERVICE_TRANSACTION 命令,就是验证获取服务的
- 每隔0.5s循环获取一次,可能还没注册启动完成