1. Binder驱动缓冲区如何工作?
Binder驱动缓冲区是Binder IPC高效性的核心设计。它的工作方式可以概括为:一次拷贝。这与传统的IPC(如管道、消息队列、Socket)需要两次甚至多次数据拷贝形成鲜明对比。
其工作原理基于内存映射:
-
内存映射的建立:
- 在Binder驱动初始化时,它会在内核空间预留一块内存区域作为数据缓冲区。
- 当每个使用Binder的进程(无论是App还是系统服务)启动时,它会通过
mmap()
系统调用,将进程中的一块用户空间内存 与驱动中的这块内核空间缓冲区进行映射。这意味着用户空间和内核空间看到的是同一块物理内存。
-
数据传输过程:
- 发送方(Client) :将要传输的数据(例如一个
Parcel
对象)拷贝 到其用户空间中已被映射的内存区域。由于内存映射的存在,这次拷贝直接发生在了内核缓冲区中。 - Binder驱动 :驱动不再需要将数据从用户空间拷贝到内核空间(因为已经在了)。它只需要根据目标进程的标识,找到目标进程对应的映射区,并将一个指向该数据缓冲区的指针 传递给接收方(Server) 进程。同时,驱动还会将待处理的事务(Transaction)加入目标进程的待处理队列,唤醒可能正在等待的线程。
- 接收方(Server) :接收方的Binder线程被唤醒后,直接通过指针访问同一块内核缓冲区,将数据反序列化到自己的用户空间。这个过程几乎没有数据拷贝。
- 发送方(Client) :将要传输的数据(例如一个
总结:Binder的"一次拷贝"发生在发送方用户空间到内核缓冲区(也是接收方的映射区)之间。接收方通过指针直接访问,避免了内核到用户空间的第二次拷贝。这是Binder性能卓越的根本原因。
2. 每次打开启动新的App都需要打开Binder驱动吗?
不需要。
Binder驱动是一个全局的、系统级别 的字符设备(/dev/binder
,/dev/hwbinder
等)。它在Android系统启动的早期由init进程负责加载和打开。
- 对于每个App进程 :当Zygote(孵化器进程)
fork
出一个新的应用进程时,子进程会继承Zygote已经打开的所有文件描述符(File Descriptor),其中就包括了指向Binder驱动的描述符。 binder_open
操作 :应用进程在启动后,会调用binder_open()
。这个操作并不是 去"打开"驱动设备文件(因为文件描述符已经存在了),而是在驱动中为当前进程初始化一个binder_proc
结构体。这个结构体记录了进程的所有Binder相关信息,如线程池、待处理事务队列、映射的缓冲区信息等。
所以,流程是:系统启动时打开驱动 -> Zygote打开驱动 -> App从Zygote继承驱动fd -> App初始化时在驱动内注册自己的binder_proc
。
3. 关闭应用时候需要关闭Binder驱动吗?
应用进程正常退出时,不需要也不应该显式地去"关闭Binder驱动"。
- 文件描述符的清理 :当应用进程终止时,Linux内核会自动回收该进程所占用的所有资源,包括其打开的所有文件描述符。对Binder驱动文件的描述符也会在此刻被关闭。
- 驱动内部的清理 :当驱动检测到某个进程的文件描述符被关闭(无论是主动
close
还是进程退出导致内核清理),它会触发回调函数。这个函数会销毁为该进程创建的binder_proc
结构体,释放其内核缓冲区映射、清空事务队列等所有相关资源,确保没有内存泄漏。
因此,作为一个应用开发者,你无需也不应该 在你的代码中尝试去close
这个Binder驱动描述符。系统已经为你完美地处理了生命周期管理。
4. 驱动的缓冲区大小有限制吗?
是的,有限制。
每个通过Binder进行通信的进程,其映射的缓冲区大小都有一个上限。这个限制是在进程初始化时(调用 mmap
)设定的。
- 默认大小 :在Android系统中,每个进程的Binder映射缓冲区默认大小通常是 1MB (即
1024 * 1024
字节)。这个值定义在Android源码中(如frameworks/native/libs/binder/processes.cpp
的DEFAULT_BINDER_VM_SIZE
)。 TransactionTooLargeException
:当你尝试传输的数据(例如一个巨大的Intent或Bundle)超过了一次Binder事务所能容纳的容量(通常是缓冲区大小的一半左右,约512KB-1MB)时,系统就会抛出这个著名的异常。这是因为驱动无法在预留的缓冲区中分配足够的空间来存放你的数据。- 修改限制(需系统权限) :对于某些特殊的、需要传输大量数据的系统进程,可以在其初始化时指定一个更大的
mmap
大小。但这对于普通第三方App来说是不可行的。
5. Binder调用时序图
下图描述了一次完整的Binder IPC调用所涉及的App进程、Binder驱动和Server进程之间的交互时序。

时序图步骤详解:
-
准备阶段:
- 客户端和服务端进程在启动时都已通过
binder_open
在驱动中初始化了自己的binder_proc
。 - 服务端向驱动注册自己的Binder实体(例如AMS注册自己)。
- 客户端通过
ServiceManager
等机制,获取到服务端Binder实体的引用(代理对象) 。
- 客户端和服务端进程在启动时都已通过
-
IPC调用(REQUEST) :
- 客户端调用代理对象的方法(如
startActivity
)。 - 代理对象将参数序列化到
Parcel
中。 - 代理对象通过
ioctl
系统调用,向驱动发送BC_TRANSACTION
命令,包含目标引用、标识符和数据Parcel。 - 驱动 :接收命令,将数据从客户端用户空间拷贝到内核缓冲区。根据目标引用找到服务端进程,将事务加入其待处理队列,并唤醒一个空闲的Binder线程。
- 客户端调用代理对象的方法(如
-
请求处理:
- 服务端的某个Binder线程被唤醒,从驱动读取
BR_TRANSACTION
命令。 - 该线程根据命令中的标识符,找到正确的Binder实体对象,并调用其
onTransact()
方法。 onTransact()
方法解包Parcel,执行真正的业务逻辑(如AMS处理启动Activity的请求)。
- 服务端的某个Binder线程被唤醒,从驱动读取
-
IPC回复(REPLY) :
- 服务端处理完毕,将返回值序列化到新的Parcel中。
- 服务端通过
ioctl
向驱动发送BC_REPLY
命令。 - 驱动 :将回复数据从服务端用户空间拷贝到内核缓冲区。找到最初发起调用的客户端进程,将回复事务加入其待处理队列。
-
接收回复:
- 客户端(原本可能在阻塞等待)被驱动唤醒,读取
BR_REPLY
命令。 - 客户端代理对象解包回复Parcel,将结果返回给最初的调用者。
- 一次完整的同步Binder调用结束。
- 客户端(原本可能在阻塞等待)被驱动唤醒,读取