【碎片八股文 #002】Binder 是怎么实现跨进程通信的?

115 阅读5分钟

【碎片八股文 #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
        ▼                         │

步骤解析:

  1. Client 通过 Proxy 代理调用方法
  2. Proxy 将参数打包(序列化),通过 Binder 驱动 发送
  3. Binder 驱动找到对应的 Server 进程,通知其处理
  4. Server 的 Stub 对象接收数据,反序列化后执行真正的方法
  5. 结果通过 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 为什么能自动管理内存?