Android Binder

进程隔离:

  1. 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据
  2. 为了保证系统的安全,用户空间和内核空间是天然隔离的
  3. 每个进程有自己的虚拟内存空间,为了安全,每个进程只能操作自己的虚拟内存空间,只有操作系统才有权限操作物理内存空间

为什么要用Binder?

  1. Android系统内核是Linux内核
  2. Linux内核进程通信有:管道、内存共享、Socket、File;
  3. 对比:
管道

效率低;两次拷贝;

数据从应用A拷贝到内核的管道中,管道再拷贝到应用B;

安全1v1
内存共享效率最高;无需拷贝,A、B、C...共享一块内存;不安全N v N
Socket效率最低;两次拷贝;用户和CPU内核状态切换;安全C/S模型
File文件操作更不安全
Binder一次拷贝安全:为每个应用分发UUID,通信时进行验证;支持实名和匿名C/S模型

Binder的一次拷贝发生在用户空间拷贝到内核空间

用户空间:

App进程运行的内存空间;

内核空间:

系统驱动、和硬件相关的代码运行的内存空间,也就是进程ID为0的进程运行的空间;

程序局部性原则:

只加载少量代码;应用没有运行的代码放在磁盘中,运行时高速缓冲区进行加载要运行的代码;默认一次加载一个页(4K),若不够4K就用0补齐;

MMU:内存管理单元;

给CPU提供虚拟地址;

当对变量操作赋值时:

  1. CPU拿着虚拟地址和值给到MMU
  2. MMU用虚拟地址匹配到物理地址,MMU去物理内存中进行赋值;

物理地址:

物理内存的实际地址,并不是磁盘;

虚拟地址:

MMU根据物理内存的实际地址翻译出的虚拟地址;提供给CPU使用;

------------------------------------

页命中:CPU读取变量时,MMU在物理内存的页表中找到了这个地址;

页未命中:CPU读取变量时,MMU在物理内存的页表中没有找到了这个地址,此时会触发MMU去磁盘读取变量并存到物理内存中;

普通的二次拷贝:

应用A拷贝到服务端:coay_from_user

从服务端拷贝到应用B:coay_to_user

mmap():

  1. 在物理内存中开辟一段固定大小的内存空间
  2. 将磁盘文件与物理内存进行映射(理解为绑定)
  3. MMU将物理内存地址转换为虚拟地址给到CPU(虚拟地址映射物理内存)

共享内存进程通信:

  1. 进程A调用mmap()函数会在内核空间中虚拟地址和一块同样大小的物理内存,将两者进行映射
  2. 得到一个虚拟地址
  3. 进程B调用mmap()函数,传参和步骤1一样的话,就会得到一个和步骤2相同的虚拟地址
  4. 进程A和进程B都可以用同一虚拟地址对同一块映射内存进行操作
  5. 进程A和进程B就实现了通信
  6. 没有发生拷贝,共享一块内存,不安全

Binder通信原理:

角色:Server端A、Client端B、Binder驱动、内核空间、物理内存

  1. Binder驱动在物理内存中开辟一块固定大小(1M-8K)的物理内存w,与内核空间的虚拟地址x进行映射得到
  2. A的用户空间的虚拟地址ax和物理内存w进行映射
  3. 此时内核空间虚拟地址x和物理内存w已经进行了映射,物理内存w和Server端A的用户空间虚拟地址ax进行了映射:也就是 内核空间的虚拟地址x = 物理内存w = Server端A的用户空间虚拟地址ax
  4. B发送请求:将数据按照binder协议进行打包给到Binder驱动,Binder驱动调用coay_from_user()将数据拷贝到内核空间的虚拟地址x
  5. 因步骤3中的三块区域进行了映射
  6. Server端A就得到了Client端B发送的数据
  7. 通过内存映射关系,只发生了一次拷贝

-----------------

Activity跳转时,最多携带1M-8k(1兆减去8K)的数据量;

真实数据大小为:1M内存-两页的请求头数据=1M-8K;

应用A直接将数据拷贝到应用B的物理内存空间中,数据量不能超过1M-8K;拷贝次数少了一次,少了从服务端拷贝到用户;

--------------

​​​​​​IPC通信机制:

  1. 服务注册
  2. 服务发现
  3. 服务调用

以下为简单的主进程和子进程通信:

1、服务注册:

缓存中心中有三张表(暂时理解为三个HashMap,Binder用的是native的红黑树):

  1. 第一种:放key :String - value:类的Class;
  2. 第二种:放key :Class的类名 - value:类的方法集合;
  3. 第三种:放key :Class的类名 - value:类的对象;

类的方法集合:key-value;

key:方法签名:“方法名” 有参数时用 “方法名-参数类型-参数类型-参数类型......”;

value: 方法本身;

注册后,服务若没被调用则一直处于沉默状态,不会占用内存,这种情况只是指用户进程里自己创建的服务,不适用于AMS这种;

2、服务发现:

当被查询到时,要被初始化;

  1. 客户端B通过发送信息到服务端A
  2. 服务端解析消息,反序列化
  3. 通过反射得到消息里的类名,方法,从注册时的第一种、第二种表里找到Class,若对象没初始化则初始化对象,并将对象添加到第三种的表里;

3、服务调用:

  1. 使用了动态代理
  2. 客户端在服务发现时,拿到对象(其实是代理)
  3. 客户端调用对象方法
  4. 代理发送序列化数据到服务端A
  5. 服务端A解析消息,反序列化,得到方法进行处理,得到序列化数据结果
  6. 将序列化结果写入到客户端进程的容器中;
  7. 回调给客户端;

-----------------------------------------------

AIDL:

BpBinder:数据发送角色

BbBinder:数据接收角色

编译器生成的AIDL的java接口.Stub.proxy.transact()为数据发送处;

发送的数据包含:数据+方法code+方法参数等等;

  1. 发送时调用了Linux的驱动
  2. 调用copy_from_user()拷贝用户发送的数据到内核空间
  3. 拷贝成功后又进行了一次请求头的拷贝:copy_from_user()
  4. 也就是把一次的数据分为两次拷贝

请求头:包含了目的进程、大小等等参数,这些参数占了8K

编译器生成的AIDL的java接口.Stub.onTransact()为数据接收处;

-------------------------------------------------------

Binder中的IPC机制:

  1. 每个App进程启动时会在内核空间中映射一块1M-8K的内存
  2. 服务端A的服务注册到ServiceManager中:服务注册
  3. 客户端B想要调用服务端A的服务,就去请求ServiceManager
  4. ServiceManager去让服务端A实例化服务:服务发现
  5. 返回一个用来发送数据的对象BpBinder给到客户端B
  6. 客户端B通过BpBinder发送数据到服务端A的内核的映射区域(传参时客户端会传一个reply序列化对象,在底层会将这个地址一层一层往下传,直至传到回调客户端):这里发生了一次通信copy_from_user:服务调用
  7. 服务端A通过BBBinder得到数据并处理数据
  8. 服务端唤醒客户端等待的线程;将返回结果写入到客户端发送请求时传的一个reply容器地址中,调用onTransact返回;
  9. 客户端在onTransac中得到数据;通信结束;

--------------------------------------------------

ServiceManager维持了Binder这套通信框架;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值