目录
  1. 1. 一、Android 中使用的 IPC 机制概览
  2. 2. 二、Binder——Android 的核心 IPC
    1. 2.1. 2.1 为什么 Android 选择了 Binder?
    2. 2.2. 2.2 Binder 通信流程回顾
  3. 3. 三、Unix Domain Socket
    1. 3.1. 3.1 在 Android 中的应用
    2. 3.2. 3.2 Zygote 的 Socket 通信
    3. 3.3. 3.3 install 的 Socket 通信
  4. 4. 四、共享内存机制
    1. 4.1. 4.1 ashmem(Android Shared Memory)
    2. 4.2. 4.2 ION / dma-buf
    3. 4.3. 4.3 共享内存协同 Binder 的优势
  5. 5. 五、管道与信号(辅助 IPC)
    1. 5.1. 5.1 管道
    2. 5.2. 5.2 信号
  6. 6. 六、各 IPC 机制的性能对比
  7. 7. 七、核心面试题
【深入内核篇】进程间通信

Android 基于 Linux 内核,继承了 Linux 所有的 IPC 机制,同时引入了专用的 Binder 作为核心 IPC 框架。理解 Android 使用的各种 IPC 机制的差异、优劣和适用场景,是深入系统架构的必修课。

一、Android 中使用的 IPC 机制概览

IPC 机制 是否主要 典型用途 数据拷贝次数 安全性
Binder 系统服务通信、四大组件调度 1 次 基于能力+UID 检查
Unix Domain Socket Zygote ↔ system_server, init, adbd 2 次 基于 UID/GID
ashmem(匿名共享内存) SurfaceFlinger, AudioFlinger 0 次(映射后) 基于 fd 传递
管道 (pipe) / 信号 (signal) 辅助 进程同步、轻量通知 2 次 基于 fd
dma-buf / ION 图形缓冲区、Camera 0 次(硬件直接访问) 基于 fd
SysV IPC / POSIX 消息队列 极少 无广泛使用 2 次 较弱

二、Binder——Android 的核心 IPC

2.1 为什么 Android 选择了 Binder?

Android 在设计之初评估了多种 IPC 方案,选择了 Binder 作为核心 IPC 而非传统 Linux IPC(如 Socket、管道、SysV)的原因:

1. 安全模型:Binder 天然支持基于能力的访问控制。每个 Binder 对象有唯一的标识(binder_node),不能伪造。配合 UID/PID 检查(IPCThreadState 在每次调用时自动传递调用者的 UID/PID),实现了细粒度的权限控制。相比之下,Socket/管道缺乏内置的身份传递机制。

2. 一次拷贝:通过 mmap 机制,Binder 将数据拷贝从两次减少为一次,提升了大数据量场景下的性能。

3. 对象引用计数:Binder 内置强/弱引用计数(通过 binder_node 的 internal_strong_refs / local_weak_refs 等字段),配合死亡通知机制,使得跨进程对象生命周期管理自动化。

4. 线程池管理:Binder 驱动管理服务端线程池(最多 15 个线程),自动根据负载请求创建或回收线程。

5. 同步语义(RPC 模型):Binder 天然支持同步调用(Client 阻塞等待 Server 回复),也支持异步的 oneway 调用。

2.2 Binder 通信流程回顾

Client 进程                        Server 进程
BpBinder.transact() BBinder.onTransact()
↓ ↑
IPCThreadState.transact() IPCThreadState.joinThreadPool()
↓ ↑
ioctl(BINDER_WRITE_READ) ioctl(BINDER_WRITE_READ)
↓ ↑
===== Binder Driver (内核) ============
binder_transaction()
copy_from_user (一次拷贝)
挂入目标进程 todo 队列
wake_up 目标进程

用户空间库:frameworks/native/libs/binder/

三、Unix Domain Socket

3.1 在 Android 中的应用

Unix Domain Socket(UDS)是 Android 中第二重要的 IPC 机制,用于以下关键场景:

  • init → Zygote:init 进程通过 socket 向 Zygote 发送 fork 请求
  • adbd:ADB 守护进程使用 socket 与 system_server 和应用通信
  • installd:PKMS 通过 socket 与 installd 守护进程通信执行 dexopt、数据目录创建等
  • netd:网络管理守护进程
  • keystore:密钥管理守护进程

3.2 Zygote 的 Socket 通信

// frameworks/base/core/java/android/os/ZygoteProcess.java
public static Process.ProcessStartResult start(...) {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith,
packageName, zygoteArgs);
}

private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
ZygoteState zygoteState, ArrayList<String> args)
throws IOException {
// 通过 LocalSocket 发送参数
final BufferedWriter writer = zygoteState.writer;
final DataInputStream inputStream = zygoteState.inputStream;

writer.write(Integer.toString(args.size()));
writer.newLine();
for (String s : args) {
writer.write(s);
writer.newLine();
}
writer.flush();

// 等待 Zygote 返回结果(新进程的 PID)
return result;
}

3.3 install 的 Socket 通信

// frameworks/base/services/core/java/com/android/server/pm/Installer.java
public class Installer extends SystemService {
private final InstallerConnection mInstaller;

public long createAppData(String uuid, String packageName, int userId,
int flags, int appId, String seInfo, int targetSdkVersion) {
// 通过 Unix Domain Socket 向 installd 发送指令
return mInstaller.createAppData(uuid, packageName, userId, flags,
appId, seInfo, targetSdkVersion);
}
}

installd 守护进程路径:frameworks/native/cmds/installd/

Zygote 为什么用 Socket 而不是 Binder?因为 Binder 依赖 /dev/binder 设备,而 Zygote 在 fork 后通过 exec 执行具体应用时,需要关闭所有不必要的文件描述符。使用 Socket 可以在 fork 时更简单地进行 fd 管理。而且 Zygote 的通信模式是简单的 “请求-响应” 模式,Socket 完全足够。

四、共享内存机制

4.1 ashmem(Android Shared Memory)

ashmem 是 Android 对 Linux 共享内存的扩展,增加了引用计数和 pin/unpin 机制:

// system/core/libcutils/ashmem-dev.cpp
int ashmem_create_region(const char *name, size_t size) {
int fd = open("/dev/ashmem", O_RDWR);
ioctl(fd, ASHMEM_SET_NAME, name);
ioctl(fd, ASHMEM_SET_SIZE, size);
return fd; // 返回 fd,可以通过 Binder 传递
}

int ashmem_set_prot_region(int fd, int prot) {
return ioctl(fd, ASHMEM_SET_PROT_MASK, prot);
}

应用场景:

  • SurfaceFlinger:应用通过 GraphicBuffer(底层使用 ashmem)将渲染的帧传递给 SurfaceFlinger。
  • AudioFlinger:音频数据缓冲区。
  • ContentProvider 的 Cursor window:通过 ashmem 在 Provider 进程和 Client 进程间共享查询结果。

4.2 ION / dma-buf

ION 是 Android 为多媒体场景设计的内存分配器,dma-buf 是 Linux 标准的 DMA 缓冲区共享机制:

Camera HAL  →  ION buffer  →  Gralloc  →  SurfaceFlinger

dma-buf fd 跨进程传递

Android 11+ 逐步用标准的 Linux dma-buf 取代 ION。AOSP 路径:drivers/dma-buf/

4.3 共享内存协同 Binder 的优势

共享内存的典型使用模式是:

  1. 使用 ashmem 创建共享内存,获取 fd
  2. 通过 Binder 将 fd 传递给对端进程
  3. 对端进程通过 fd 做 mmap,直接访问同一块物理内存
  4. 数据读写不再经过 Binder 通道,零拷贝

这种模式结合了 Binder 的安全性和共享内存的高效性。

五、管道与信号(辅助 IPC)

5.1 管道

  • Looper 的唤醒:addFd() 注册的管道 fd
  • Zygote 的 USAP(Unspecialized App Process)Pool:使用管道同步

5.2 信号

  • SIGCHLD:Zygote 监控子进程退出
  • SIGQUIT:ANR trace 采集(kill -3 <pid> 触发线程 dump)
  • SIGSTOP / SIGCONT:进程的暂停和恢复

六、各 IPC 机制的性能对比

IPC 机制 延迟 (小数据) 吞吐 (大数据) 内存开销 安全模型
Binder ~100us ~50MB/s 低(一次拷贝) 能力+UID
Unix Domain Socket ~200us ~80MB/s 高(两次拷贝) UID/GID
ashmem (mmap) 初始 ~1ms ~无限(物理内存带宽) 最低 fd 传递
SysV 消息队列 ~150us ~10MB/s

延迟数据仅供参考(取决于内核版本和硬件)。Binder 的综合性能在小到中等数据量场景(大多数系统服务调用)下最优,这也是 Android 选择它的重要原因。

七、核心面试题

Q1:为什么 Android 不用传统 Linux 的 SysV / POSIX IPC,而要单独设计 Binder?

主要有三点:(1) 安全:Binder 自动传递调用者 UID/PID,服务端可以通过 Binder.getCallingUid() 做权限检查;SysV IPC 依赖全局 key,容易冲突也容易被恶意进程猜测和利用。(2) 性能:Binder 的一次拷贝优于多数传统 IPC 的两次拷贝。(3) 生命周期管理:Binder 通过引用计数和死亡通知自动管理跨进程对象生命周期,传统 IPC 需要额外机制实现。

Q2:Zygote 为什么使用 Socket 而不是 Binder 来通信?

Zygote 的特殊性在于它 fork 子进程。fork 会复制父进程的文件描述符表。如果 Zygote 已经打开 /dev/binder 并通过 Binder 通信,那么 fork 出的每个子进程都继承了这些 Binder 相关的 fd 和 binder_proc 状态。这会导致混乱——子进程继承了 Zygote 的 Binder 身份。实际上,在 Zygote fork 和 exec 新进程之前,会关闭大部分不需要的 fd。Socket 在此场景下更简单:对端明确(只有 system_server 中的 ZygoteProcess),通信模型简单(发送 fork 请求,等待返回 PID),不需要复杂的能力模型。

Q3:ashmem 的 pin/unpin 机制有什么作用?和普通的 POSIX shm(如 shmget/shm_open)有什么区别?

ashmem 的 pin/unpin 是 Android 特有的内存管理功能。当系统内存紧张时,内核可以 “回收” unpin 的 ashmem 页面(将其内容丢弃),这在 SurfaceFlinger 的 BufferQueue 中非常有用——当 Buffer 已经被消费且不再需要时,可以 unpin 让内核回收内存。POSIX 标准 shm 缺乏这种 fine-grained 控制,分配后会一直占用物理内存直到进程退出或显式释放。

AOSP 核心路径参考:

  • frameworks/native/libs/binder/ — Binder 用户空间库
  • system/core/libcutils/ashmem-dev.cpp — ashmem 封装
  • frameworks/base/core/java/android/os/ZygoteProcess.java — Zygote Socket 通信
  • frameworks/native/cmds/installd/ — installd 守护进程
  • system/core/adb/ — ADB 守护进程
打赏
  • 微信
  • 支付宝

评论