【碎片八股文 #002】Binder 是怎么实现跨进程通信的?
一、面试题原文
面试官: Android 中为什么要用 Binder 来做跨进程通信?它和传统的 IPC 方式有什么区别?
候选人: Binder 比较高效吧……具体怎么实现的不太清楚。
面试官心里想: 能说出共享内存、Copy Once、安全性就算过关了。
二、常见误答
很多人只知道"Binder 用于跨进程通信",但说不清楚:
- 为什么 Linux 已经有管道、Socket、共享内存了,还要搞个 Binder?
- Binder 的"一次拷贝"是什么意思?
- Binder 怎么保证安全性?
这些都是面试高频追问点。
三、正确理解
什么是 Binder?
Binder 是 Android 系统中的一种 跨进程通信(IPC)机制,基于 Linux 内核的驱动实现。
核心特点:
- 只需要 一次数据拷贝(传统 IPC 需要两次)
- 天然支持 UID/PID 验证,安全性高
- 面向对象的调用方式,使用起来像本地调用
为什么要用 Binder?
Android 是多进程系统,不同应用运行在不同进程中,进程之间内存隔离。
常见场景:
- App 调用系统服务(ActivityManagerService、PackageManagerService)
- App 之间通过 AIDL 互相调用
- ContentProvider 跨进程访问数据
Linux 本身有多种 IPC 方式,但都有缺陷:
| IPC 方式 | 缺点 |
|---|---|
| 管道/消息队列 | 需要两次拷贝,性能差 |
| Socket | 网络协议栈开销大,不适合本地通信 |
| 共享内存 | 需要额外的同步机制,复杂且不安全 |
Binder 综合了性能、安全、易用性的优势。
四、Binder 的核心原理
1. 一次拷贝是怎么做到的?
传统 IPC(比如管道)需要两次拷贝:
发送进程 → 内核缓冲区 → 接收进程
(拷贝1) (拷贝2)
Binder 通过内核驱动 + 内存映射(mmap)实现一次拷贝:
发送进程 → 内核空间(Binder 驱动) ← 接收进程
(拷贝) ↑
(映射)
- 发送进程把数据拷贝到内核的 Binder 驱动
- 接收进程通过 mmap 内存映射 直接访问内核中的数据,不需要再拷贝一次
关键技术: mmap 让用户空间和内核空间共享同一块物理内存。
2. Binder 的通信流程
┌──────────────┐ ┌──────────────┐
│ Client 进程 │ │ Server 进程 │
│ │ │ │
│ Proxy 代理 │ │ Stub 本地对象│
└───────┬──────┘ └──────▲───────┘
│ │
│ 1. 发起调用 │ 4. 返回结果
▼ │
┌─────────────────────────────────┴─────-┐
│ Binder 驱动(内核空间) │
│ │
│ - 管理 Binder 对象引用 │
│ - 数据传输(一次拷贝) │
│ - UID/PID 验证 │
└────────────────────────────────────────┘
│ ▲
│ 2. 数据拷贝到内核 │ 3. 通知 Server
▼ │
步骤解析:
- Client 通过 Proxy 代理调用方法
- Proxy 将参数打包(序列化),通过 Binder 驱动 发送
- Binder 驱动找到对应的 Server 进程,通知其处理
- Server 的 Stub 对象接收数据,反序列化后执行真正的方法
- 结果通过 Binder 驱动返回给 Client
3. Binder 如何保证安全?
每次通信时,Binder 驱动会自动在内核层记录调用方的 UID 和 PID。
Server 端可以通过这些信息验证调用者身份:
// Server 端可以获取调用方信息
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
// 根据 UID 判断权限
if (callingUid != Process.SYSTEM_UID) {
throw new SecurityException("权限不足");
}
这个机制是内核层强制的,应用层无法伪造。
五、图解核心概念
Binder 对象的映射关系
Client 进程 Binder 驱动 Server 进程
│ │ │
│ IBinder 引用 │ Binder 实体 │ 真实对象
│ (handle = 1) │ (映射表) │ (IMyService)
│ │ │
└──────── 调用 ───────────→ 驱动查表 ──────────────→ 执行方法
Client 持有的不是真实对象,而是一个 Binder 引用(handle) ,驱动通过这个引用找到 Server 端的真实对象。
六、延伸提问
1. Binder 为什么比 Socket 快?
- Socket 要走完整的网络协议栈(TCP/IP),即使是本地通信也有开销
- Binder 直接在内核层传递数据,没有协议栈开销,且只需一次拷贝
2. Binder 有大小限制吗?
有。普通进程的 Binder 传输限制是 1MB - 8KB(约 1016KB)。
超过这个大小会抛出 TransactionTooLargeException。
解决方案:
- 大数据用 文件描述符 或 匿名共享内存(Ashmem) 传递
- 分批传输
3. AIDL 和 Binder 是什么关系?
AIDL(Android Interface Definition Language) 是 Binder 通信的高层封装。
- 开发者写
.aidl文件定义接口 - 编译时自动生成 Proxy 和 Stub 代码
- 底层仍然是 Binder 驱动在工作
本质: AIDL 是语法糖,让你不用手写 Binder 通信代码。
4. ServiceManager 是干什么的?
ServiceManager 是 Binder 机制中的"注册中心"。
- 所有系统服务(AMS、PMS)启动后都会向 ServiceManager 注册
- Client 要使用某个服务时,通过 ServiceManager 查询对应的 Binder 引用
// 获取 ActivityManagerService 的 Binder 引用
IBinder binder = ServiceManager.getService("activity");
七、记忆口诀
"Binder 拷贝一次,驱动做中介;UID 验身份,安全又高效。"
八、碎片笔记
核心关键词: Binder、一次拷贝、mmap、UID/PID 验证、ServiceManager
重点记忆:
- Binder 通过 mmap 内存映射实现一次拷贝
- Binder 驱动在内核层自动记录调用方 UID/PID
- AIDL 是 Binder 的高层封装,本质还是 Binder 通信
- Binder 传输有大小限制(约 1MB)
实际应用:
- 跨进程传递大数据时,要用文件描述符或 Ashmem
- 自定义系统服务时,需要在 ServiceManager 中注册
- 开发 AIDL 接口时,注意方法参数不要太大
今天的碎片,帮你面试少挂一次。
下一篇预告: 【碎片八股文 #003】ARC 为什么能自动管理内存?