草庐IT

Android Binder 原理

朱兰婷 2023-03-28 原文

前言

本文基于 Android S。

Binder 是什么

Android 设计了一个轻量级的进程间通信机制,也称 远程调用机制,Binder 是这个机制中的 远程对象 的基础类。

即,Binder 对象实现了一些接口,供远程进程调用。

为了被远程进程调用,它必须遵循某种定义好的协议,这个协议为 IBinder。

同时,为了使远程进程有统一调用其方法的方式,Android 规定它实现的接口必须继承自 IInterface。

google 官网 - Binder:Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by IBinder.

Binder 是什么

其核心是如何被远程进程调用,即,其遵循的由 IBinder 接口描述的协议。这个协议由 RPC 协议衍变而来。

RPC 协议

一个通用的 RPC 框架实现如下:

通用 RPC 框架

其重点是:代理模式传输

代理模式指在 Client 使用 Server 端接口对象的代理。这样 Client 端在调用接口时,无需关注 RPC 实现,和调用本地对象的方法一样使用就可以。

RPC 框架中的代理模式

传输即 RPC 协议定义的东西。为了使两端进程能理解对方的 方法/参数,我们需要定义 序列化/反序列化打包/解包 的标准。就 Binder 而言,因为中间涉及到 Java 代码和 C++ 代码的转换,所以我们在传输之前必须把 参数/返回值类型 序列化成 JNI 可以理解的基础类型。

RPC 数据传输

Android 中封装了一个 Parcel 类来负责 序列化/反序列化 及 打包/解包。

通常 RPC 框架里面还会涉及到一个服务中心,所有意图公开给所有用户使用的服务都在里面注册,然后想使用这个服务的客户端从中取出服务来使用。

Binder 架构中的 ServiceManager#addService/ServiceManager#getService 采用的就是这个设计。

Binder RPC 架构

线程迁移

线程迁移指进程间的 IPC 看起来像是,Client 端的线程跳到 Server 端执行代码,再带着结果跳回来。

但实际上,Binder IPC 机制,并不是使用线程迁移来实现,而是采用一种模拟线程迁移的方式。

Binder 系统的用户空间代码在它运行的每个进程中维护了一个线程池,这个线程池中的线程用来处理来自其它进程的 IPC。内核模块通过以下手段模拟线程迁移:在分派 IPC 时跨进程传递线程优先级,并确保如果 IPC 递归回原始进程,由其原始线程处理。

OpenBinder 中“线程迁移”的思想

Binder IPC 机制中的 RPC 架构

先看下总的流程图:

Binder RPC 调用流程

我们可以看到 Client 端调用 Binder 代理的方法,方法和参数最后被封装后传递给了 Server 端,Server 端再把方法和参数解析出来,调用真正的 Binder 对象执行方法,再把结果返回给 Client 端。

其中一次 RPC 的来回在 Android 的 Binder 机制中被称为 transaction(事务)。

Binder 事务

其中的核心是如何把封装好的数据从 Client 端传到 Server 端。

Binder 机制中的数据传输

Binder 机制中的数据传输,是通过内存映射来实现的。这个内存映射机制由位于内核中的 Binder 驱动实现。

数据传输

可以看到映射的重点为 Server 进程中的 binder_mmap() 方法。
binder_mmap() 方法对应的代码在 binder.c 中。
其主要流程如图:

binder_mmap()

Binder 的服务中心

上面的数据传输中有个问题,即,Client 进程如何知道 Server 进程 binder_proc->buffer 所指向的物理地址空间?
答案是,通过一个服务中心:ServiceManager 进程实现。

如何查找 binder_proc-_buffer 对应的物理地址

当愿意公开自己的 Server 通过 Binder 驱动向服务中心 ServiceManager 注册自己时,Binder 驱动会做以下事情:

  1. 为这个 Server 创建其对应的 binder_node,binder_node 中包含了 binder_proc 对象;
  2. 创建与 binder_node 对应的 binder_ref,并向服务中心注册 [服务名称,binder_ref 对象]

当 Client 需要获取 Server 时,只需要使用 服务名称 通过 Binder 驱动向服务中心查找该服务名称对应的 binder_ref 对象即可。服务中心会通过 Binder 驱动把 Server 对应的 binder_ref 返回给 Client 进程。

上面过程有个问题,即,Binder 驱动是如何知道发送给它的请求是需要转交给 ServiceManager 进程的呢?
答,开机时,ServiceManager 会向 Binder 驱动设置自己为上下文管理者。其它进程只要把命令发送给上下文管理者就可以了。

如何告知 Binder 驱动请求是经 ServiceManager
创建 ServiceManager 的 BBinder,并设置为 Binder 驱动的上下文管理者

BBinder: Server 进程中,native 代码中的 本地(local) Binder;
BpBinder: Client 进程中,native 代码中的 远端(remote) Binder,即 Binder 代理;
IInterfaceImpl.Stub: Server 进程中,Java 代码中的本地 Binder;
IInterfaceImpl.Stub.Proxy: Client 进程中,Java 代码中的 Binder 代理。

开机时,ServiceManager 会向 Binder 驱动设置自己为上下文管理者,并把自己加入到 ServiceManager 的 [name, BBinder] Map 中。

ServiceManager 设置自己为 Binder 驱动的上下文管理者
在 Java 代码中获取 ServiceManager(获取 BpBinder 在 Java 层的对象)
Java 层获取 ServiceManager

具体流程如下:

Java 层获取 native ServiceManager 的 IInterface

公共 Server 向 ServiceManager 注册自己

向 ServiceManager 注册 Server

简单概括为:

  1. Server 进程新建一个要公开的 Binder,Java 层为 Stub 对象,native 层为 JavaBBinder 对象;
  2. 把 Binder 对象放入 Parcel 中;
  3. 调用 ServiceManager 的 Binder 代理(BpBinder)的 addService() 方法;
  4. 通过 Binder 驱动把请求转发给 ServiceManager 的本地 Binder(ServiceManager 的 BBinder);
  5. 从 Parcel 中解析出封装的要做为公开 Server 的 BBinder 对象,并创建其 BpBinder;
  6. 并加入到 ServiceManager 的 map 中。
Java 层添加公共 Server 流程

具体流程如下:

addService 代码流程

Client 向 ServiceManager 获取一个公共 Server

向 ServiceManager 获取 Server 代理

其主要过程和添加 Server 大致一致,唯一不一样的是,transact 后会返回从 Parcel reply 中读取出来的 BinderProxy:

Java 层获取公共 Server 流程

其具体代码流程为:

getService 代码流程
取出公开的 Server 后,调用其 BinderProxy 接口完成一个普通的 RPC 流程的例子
transact -> ontransact

匿名 Server 的获取

一个匿名 Binder Server 与实名 Server 的差异主要就在于后者是通过 Service Manager 来获取对它的引用;而前者则是以其它实名 Server 为中介来传递这一引用信息,仅此而已。另外,对于 Binder 驱动而言,只要 “路过” 它且以前没有出现过的 Binder 对象,都会被记录下来。

—— 林学森《深入理解 Android 内核设计思想》

匿名 Server 的获取

我们以 bindService 为例:

  1. 客户端应用调用 IActivityManager#bindService 时传入 IServiceConnection;
  2. IActivityManager 在做 RPC 时就创建了 IServiceConnection 的本地 Binder 和其远程代理 Binder;
  3. IActivityManager 的本地 Binder 实现者 AMS 在执行 bindService 时收到了 IServiceConnection 的 Binder 代理 BinderProxy,并通过 asInterface 接口把 BinderProxy 转换成 IServiceConnection 接口即 IServiceConnection.Stub.Proxy;
  4. AMS 启动 Service 成功后,调用 IServiceConnection#connected 把 Service.Stub 放入 Parcel;
  5. RPC 过程中会为 Service 创建本地 Binder 和 远程 Binder 代理;
  6. 客户端应用的 IServiceConnection 本地 Binder 在执行 connected() 方法时可以获得 Service Binder 代理 IInterfaceImpl.Stub.Proxy;
  7. 客户端应用通过 IInterfaceImpl.Stub.Proxy 调用 Service 的方法。

总结

Binder 是 Android 实现的远程过程调用机制中的远程对象,它提供一系列接口给远程进程使用。Server 端实现接口,Client 端调用接口。Server 端创建本地 Binder 并通过 Binder 驱动把 Binder 代理提供给 Client 端。Client 端在调用接口时把封装好的 方法名、参数、返回值、RPC 类型 放入 Server 进程的内核物理空间,因为该物理空间同时被映射到了 Server 进程的用户虚拟地址空间,所以 Server 进程的专为 RPC 调用而创建的线程可以直接从内核物理空间取出封装好的数据。数据取出后,Server 进程的本地 Binder 将数据解包,并执行其实现方法。

参考链接:

OpenBinder 官网之 Binder IPC 机制
google 官网:Binder
林学森《深入理解 Android 内核设计思想》
skywangkw:Android Binder机制(三) ServiceManager守护进程
skywangkw:Android Binder机制(一) Binder的设计和框架
小林coding:内存管理
《Linux 是怎样工作的》
gityuan:彻底理解 Android Binder 通信架构
gityuan:Binder 系列10—总结

原创文章,欢迎转载,但请注明出处。

有关Android Binder 原理的更多相关文章

  1. 【Unity游戏破解】外挂原理分析 - 2

    文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat

  2. Slowloris DoS攻击的原理与简单实现 - 2

    前言    Slowloris攻击是我在李华峰老师的书——《MetasploitWeb 渗透测试实战》里面看的,感觉既简单又使用,现在这种攻击是很容易被防护的啦。不过我也不敢真刀实战的去试,只是拿个靶机玩玩罢了。         废话还是写在结语里面吧。(划掉)结语可以不看(划掉)Slowloris攻击的原理        Slowloris是一种资源消耗类DoS攻击,它利用部分HTTP请求进行操作。也叫做慢速攻击,这里的慢速并不是说发动攻击慢,而是访问一条链接的速度慢。Slowloris攻击的功能是打开与目标Web服务器的连接,然后尽可能长时间的保持这些连接打开。如果由多台电脑同时发起Slo

  3. [蓝桥杯单片机]学习笔记——串口通信的基本原理与应用 - 2

    目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式  2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器​编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置  3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一

  4. ruby - # Ruby 中识别方法约定的基本原理/历史是什么? - 2

    例如,我一直看到称为String#split的方法,但从未见过String.split,这似乎更合乎逻辑。或者甚至可能是String::split,因为您可以认为#split位于String的命名空间中。当假定/隐含类(#split)时,我什至单独看到了该方法。我知道这是ri中识别方法的方式。哪个先出现?例如,这是为了区分方法和字段吗?我还听说这有助于区分实例方法和类方法。但这从哪里开始呢? 最佳答案 不同之处在于您如何访问这些方法。类方法使用::分隔符来表示消息可以发送到类/模块对象,而实例方法使用#分隔符表示消息可以发送到实例对

  5. H264压缩原理 - 2

    1、为什么压缩的原始数据一般采用YUV格式(1)利用人对图片感觉的生理特性,对于亮度信息比较敏感,对于色度信息不太敏感,所以视频编码是将Y分量和UV分量分开来编码,并且可以减少UV分量.2、视频压缩原理(1)空间冗余:图像相邻像素之间的相关性,比如一帧图片被划分成多个16x16的块之后,相邻的块之间有很多明显的相似性。(2)时间冗余:时间相差较近的两张图片变化较小。(3)视觉冗余:我们的眼睛对某些细节不太敏感,对图像中的高频信息的敏感度小于低频信息,可以去除一些高频信息。(4)编码冗余:一幅图片中不同像素出现的概率是不同的,对于出现次数较多的像素,用少的位数来编码,对于出现次数较少的像素,用多

  6. Python——程序的运行原理 - 2

    Python程序运行原理Python是一种脚本语言,编辑完成的程序,也称源代码,可以直接运行。从计算机的角度看,Python程序的运行过程包含两个步骤:解释器将源代码翻译成字节码(即中间码),然后由虚拟机解释执行。Python程序文件的扩展名通常为.py。在执行时,首先由Python解释器将.py文件中的源代码翻译成中间码,这个中间码是一个扩展名为.pyc的文件,再由Python虚拟机(PythonVirtualMachine,PVM)逐条将中间码翻译成机器指令执行。需要说明的是,pyc文件保存在Python安装目录的pycache文件夹下,如果Python无法在用户的计算机上写人字节码,字节

  7. mysql - Rails 数据库连接池的工作原理 - 2

    我正在学习Rails数据库连接池概念。在Rails应用程序中,我将池大小定义为5。我对连接池大小的理解如下。当服务器启动时,rails会自动创建n个在database.yml文件中定义的连接。在我的例子中,它将创建5个连接,因为池大小为5。在每个http请求上,如果需要访问数据库,rails将使用连接池中的可用连接来处理请求。但我的问题是,如果我一次达到1000个请求,那么大部分请求将无法访问数据库连接,因为我的连接池大小只有5个。我上面对rails连接池的理解对吗??谢谢, 最佳答案 目的:数据库连接不是线程安全的;所以Activ

  8. ElasticSearch——刷盘原理流程 - 2

    ElasticSearch——刷盘原理流程刷盘原理流程名词和操作解释相关设置刷盘原理流程整个过程会分成几步:数据会同时写入buffer缓冲区和translog日志文件buffer缓冲区满了或者到时间了(默认1s),就会将其中的数据转换成新的segment并写入系统文件缓存,这一步叫refresh其中后台会自动合并小的segment成大的segment;这一步叫段合并当translog达到大小的阈值(默认512M)或者flush默认时长(30m),则会执行flush操作:内存中数据写入新的segment放入缓存(清空内存区)一个commitpoint写入磁盘,表示哪些segment已写入磁盘将缓

  9. ruby-on-rails - 了解 establish_connection 在 ActiveRecord 中的工作原理 - 2

    此代码取自ActiveRecord2.3.14的gem类ConnectionHandlerdefestablish_connection(name,spec)@connection_pools[name]=ConnectionAdapters::ConnectionPool.new(spec)end似乎每次ruby​​在模型上调用establish_connection时,它都会创建一个新的连接池。我的问题:如果我有5个模型使用establish_connection连接到同一个数据库,Rails是否足够智能以选择一个已经存在的池而不是创建一个具有相同连接凭据的新池?如果我的5个模型是

  10. 51单片机(郭天祥版)——键盘检测原理及应用实现 - 2

    实验中我们使用的是52单片机目录前言一、单片机是什么?二、实验步骤1.独立键盘检测1.2代码如下(示例):1.3图片1.4视频2.矩阵键盘检测2.2代码如下(示例):2.3图片2.4视频总结:以上就是今天要讲的内容,本文仅仅简单介绍了单片机键盘检测的应用实现,而单片机键盘检测相关理论可以参考教材进行学习前言文章内主要概念引自郭天祥老师《新概念51单片机C语言版》一书主要展示郭天祥老师书中第四章键盘检测原理及应用实现。分为仿真、实体两部分。一、单片机是什么?单片机就是在一块硅片上集成了微处理器、存储器及各种输入/输出接口的芯片,这样一块芯片就具有了计算机的属性,因而被成为单片微型计算机,简称单片

随机推荐