目录
  1. 1. 一、Android 中使用的 IPC 机制概览
  2. 2. 二、Binder——Android 的核心 IPC
    1. 2.1. 2.1 为什么 Android 选择了 Binder?
    2. 2.2. 2.2 Binder 通信流程回顾
    3. 2.3. 2.3 Binder 驱动的内存映射(mmap)
    4. 2.4. 2.4 Binder 的身份验证机制
    5. 2.5. 2.5 Binder 的死亡通知机制
  3. 3. 三、Unix Domain Socket
    1. 3.1. 3.1 在 Android 中的应用
    2. 3.2. 3.2 Zygote 的 Socket 通信
    3. 3.3. 3.3 installd 的 Socket 通信
    4. 3.4. 3.4 Unix Domain Socket vs TCP Socket 对比
    5. 3.5. 3.5 Zygote 为什么用 Socket 而不是 Binder?
  4. 4. 四、共享内存机制
    1. 4.1. 4.1 ashmem(Android Shared Memory)
      1. 4.1.1. 4.1.1 ashmem 的 pin/unpin 机制详解
    2. 4.2. 4.2 ION / dma-buf
      1. 4.2.1. 4.2.1 ION 与 dma-buf 的区别
    3. 4.3. 4.3 共享内存协同 Binder 的优势
  5. 5. 五、管道与信号(辅助 IPC)
    1. 5.1. 5.1 管道(pipe)
    2. 5.2. 5.2 信号(Signal)
  6. 6. 六、System V IPC 与 POSIX IPC 在 Android 中的状态
  7. 7. 七、各 IPC 机制的性能对比
    1. 7.1. 7.1 基准对比
    2. 7.2. 7.2 Binder 的性能瓶颈与优化
  8. 8. 八、进程间通信的 SELinux 安全控制
  9. 9. 九、核心面试题
【深入内核篇】进程间通信

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/

2.3 Binder 驱动的内存映射(mmap)

Binder 驱动的一次拷贝能力来源于其巧妙的 mmap 设计。在 ProcessState 初始化时,会调用 open("/dev/binder") 然后执行 mmap 映射一块内核缓冲区:

// frameworks/native/libs/binder/ProcessState.cpp
ProcessState::ProcessState(const char* driver)
: mDriverFD(open_driver(driver))
, mVMStart(MAP_FAILED)
{
// mmap 参数:大小 1MB - 8KB(约 1016KB)
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ,
MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}

Binder 驱动的 mmap 实现:

// kernel/drivers/staging/android/binder.c (旧版,新版在 drivers/android/)
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct binder_proc *proc = filp->private_data;

// 1. 在内核空间分配一块连续的虚拟内存
struct vm_struct *area;
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
proc->buffer = area->addr;

// 2. 计算用户空间与内核空间的偏移
// 这样内核可以通过加上偏移直接访问用户空间的地址
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;

// 3. 分配物理页面并建立映射
proc->pages = kzalloc(sizeof(struct page*) * ...);
for (i = 0; i < num_pages; i++) {
proc->pages[i] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
// 映射到用户空间
vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE, proc->pages[i]);
}

return 0;
}

关键设计:内核空间和用户空间映射到同一块物理内存

  • 用户空间通过 mmap 返回的虚拟地址访问。
  • 内核空间通过 proc->buffer 访问。
  • 两者共享相同的物理页面,使 Binder 实现了一次拷贝。

数据拷贝的实际流程:

Client (用户空间)                    Binder 驱动 (内核)
│ │
│ ioctl(BINDER_WRITE_READ) │
├────────────────────────────────────►│
│ copy_from_user() │
│ (数据从 Client 用户空间拷贝到 │
│ 内核缓冲区) │
│ │
│ === Server 侧 ==== │
│ │ 数据已在内核缓冲区
│ │ == Server 用户空间的 mmap 区域
│ │ Server 直接读取,无需再次 copy_to_user

2.4 Binder 的身份验证机制

Binder 在每次跨进程通信中自动附加调用者的 UID 和 PID:

// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle,
uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags)
{
// 写事务数据到 Binder 驱动
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data);

// 等待回复(同步调用)
err = waitForResponse(reply);
}

// Binder 驱动自动记录调用者的 UID/PID
// 在驱动层 binder_transaction() 中:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr,
int reply)
{
// ...
t->from_euid = task_euid(proc->tsk); // 记录调用者的 effective UID
t->from_pid = proc->pid; // 记录调用者的 PID
}

// 服务端可以通过以下方法获取调用者身份:
// Binder.getCallingUid() - 获取调用方 UID
// Binder.getCallingPid() - 获取调用方 PID

这个机制使得服务端可以安全地进行权限检查:

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public int checkPermission(String permission, int pid, int uid) {
// Binder.getCallingUid() 是驱动保证的真实 UID,无法伪造
final int callingUid = Binder.getCallingUid();
return ActivityManager.checkComponentPermission(permission, callingUid, ...);
}

2.5 Binder 的死亡通知机制

// 客户端注册死亡通知
class DeathRecipient : public IBinder::DeathRecipient {
virtual void binderDied(const wp<IBinder>& who) {
// 服务端 Binder 进程死亡,执行清理
}
};

sp<IBinder> binder = sm->getService(String16("myservice"));
binder->linkToDeath(new DeathRecipient());

// 驱动层实现(简化):
// 当 Binder 实体所在进程死亡时,驱动遍历该实体的引用列表
// 向所有注册了死亡通知的进程发送 BR_DEAD_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:密钥管理守护进程
  • lmkd:低内存守护进程通过 socket 与 AMS 通信
  • tombstoned:崩溃 dump 采集守护进程
  • vold:卷管理守护进程

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;
}

Zygote 的 socket 初始化在 frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

// ZygoteInit.java
public static void main(String argv[]) {
ZygoteServer zygoteServer = new ZygoteServer();
// 注册 socket(通常是 /dev/socket/zygote)
zygoteServer.registerServerSocketFromEnv(socketName);
// 进入 accept 循环
Runnable caller = zygoteServer.runSelectLoop(abiList);
}

3.3 installd 的 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/

installd 的 socket 通信使用 Binder 风格的命令码 + Parcel 数据格式:

InstallerConnection  ──── socket ────►  installd 守护进程
(Java) /dev/socket/ (Native C++)
installd

3.4 Unix Domain Socket vs TCP Socket 对比

特性 Unix Domain Socket TCP Socket
地址 文件系统路径 IP:Port
数据拷贝 2 次 2 次 + 网络协议栈
延迟 ~200us ~1ms (localhost)
吞吐 ~80MB/s ~50MB/s (localhost)
安全性 文件系统权限 + UID/GID IP + Port,需要额外认证
作用域 单机 跨网络
sendmsg/recvmsg 传 fd 支持 (SCM_RIGHTS) 不支持
sendmsg/recvmsg 传凭证 支持 (SCM_CREDENTIALS) 不支持

UDS 的两个关键优势:

  1. fd 传递:通过 SCM_RIGHTS 控制消息可以在进程间传递文件描述符。这在 Android 中广泛使用(如传递 ashmem fd)。
  2. 对端凭证:通过 SO_PEERCRED 可以获取对端进程的 UID/GID/PID,实现基本的身份验证。

3.5 Zygote 为什么用 Socket 而不是 Binder?

Zygote 的特殊性在于它 fork 子进程。fork 会复制父进程的文件描述符表。如果 Zygote 已经打开 /dev/binder 并通过 Binder 通信,那么 fork 出的每个子进程都继承了这些 Binder 相关的 fd 和 binder_proc 状态。

具体原因:

  1. fd 清理:Zygote fork 子进程后,子进程通过 exec 执行具体的 Android 应用。在 exec 之前,Zygote 会关闭所有不需要的 fd(通过 Zygote.closeDescriptors())。Socket 的 fd 可以简单地关闭,但 Binder 的 fd 涉及驱动状态(binder_proc、binder_thread、todo 队列),清理复杂。
  2. Binder 身份冲突:fork 后的子进程继承了 Zygote 的 Binder 身份(binder_proc),如果子进程也在同一 Binder 上下文中通信,会导致 identity 混乱。
  3. 通信模型简单:Zygote 只需要”请求-响应”模式(”请 fork 一个进程,参数是…”),不需要 Binder 的面向对象 RPC 能力。
  4. 启动时序:Zygote 在 init 阶段启动,此时 Binder 的 ServiceManager 可能尚未就绪。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);
}

// pin/unpin 机制
int ashmem_pin_region(int fd, size_t offset, size_t len) {
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_PIN, &pin);
}

int ashmem_unpin_region(int fd, size_t offset, size_t len) {
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_UNPIN, &pin);
}

应用场景:

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

4.1.1 ashmem 的 pin/unpin 机制详解

这是 ashmem 与标准 POSIX 共享内存最本质的区别。当系统内存紧张时,内核的 LMK 或内存回收机制可以回收 unpin 的 ashmem 页:

// drivers/staging/android/ashmem.c
static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend) {
// 将指定范围设置为不可回收(pinned)
// 对应页面的 PG_unevictable 标志被设置
}

static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend) {
// 将指定范围设置为可回收(unpinned)
// 对应页面的 PG_unevictable 标志被清除
// 内存紧张时这些页面可以被回收
}

在 SurfaceFlinger 的 BufferQueue 中:

  • 当一帧被渲染完成并传递给 SurfaceFlinger 后,该 buffer 被 unpin。
  • 如果 SurfaceFlinger 还需要该 buffer(如作为下一帧的参考),它会被重新 pin。
  • 当系统内存紧张时,unpin 的 buffer 可以被内核回收,释放物理内存。
  • 如果没有 pin/unpin 机制,所有已分配的 GraphicBuffer 都会一直占用物理内存,即使它们已经不再被需要。

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.2.1 ION 与 dma-buf 的区别

ION 是 Android 特有的内存管理器(在 drivers/staging/android/ion/),它将内存按 heap 类型分类:

  • ION_HEAP_TYPE_SYSTEM:通用系统内存
  • ION_HEAP_TYPE_SYSTEM_CONTIG:连续物理内存
  • ION_HEAP_TYPE_CARVEOUT:预留的专用内存
  • ION_HEAP_TYPE_DMA:DMA 可访问内存

dma-buf 是 Linux 内核标准框架(3.3+),用于在不同设备驱动之间共享 DMA 缓冲区。Android 的迁移路径是将 ION 的 heap 功能逐步迁移到标准的 dma-buf heaps 框架。

4.3 共享内存协同 Binder 的优势

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

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

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

具体示例(ContentProvider 的 Cursor window):

// frameworks/base/core/jni/android_database_CursorWindow.cpp
static jlong nativeCreate(JNIEnv* env, jclass clazz,
jstring nameObj, jint cursorWindowSize) {
// 1. 创建 ashmem 共享内存
int ashmemFd = ashmem_create_region(name, cursorWindowSize);

// 2. mmap 到当前进程
void* data = mmap(NULL, cursorWindowSize, PROT_READ | PROT_WRITE,
MAP_SHARED, ashmemFd, 0);

// 3. 通过 Binder 将 fd 传递给调用方进程
// (在 CursorWindow 的 native 方法中)
CursorWindow* window = new CursorWindow();
window->mAshmemFd = ashmemFd; // 此 fd 将被打包到 Binder 事务中
return reinterpret_cast<jlong>(window);
}

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

5.1 管道(pipe)

管道在 Android 中的核心应用:

Looper 的唤醒机制

// system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) {
// 创建 eventfd 或 pipe 用于唤醒
// eventfd 是 Linux 的轻量级替代方案(2.6.22+)
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));

// 将唤醒 fd 加入 epoll 监听
rebuildEpollLocked();
}

void Looper::wake() {
uint64_t inc = 1;
write(mWakeEventFd.get(), &inc, sizeof(uint64_t)); // 写入一个值唤醒
}

Zygote 的子进程同步:USAP(Unspecialized App Process)Pool 使用 pipe 来管理预 fork 的进程池,通过 pipe 的阻塞读实现进程的同步等待。

Looper 为什么用 eventfd 而非 pipe?

  • eventfd 只需要一个 fd(管道需要两个)
  • eventfd 更轻量(内核中只有 16 字节的结构体,管道需要至少一页的环形缓冲区)
  • eventfd 语义更清晰(计数器型,而非流型)

5.2 信号(Signal)

Android 中信号的使用场景:

  • SIGCHLD:Zygote 监控子进程退出。当应用进程异常退出时,Zygote 收到 SIGCHLD,清理子进程资源。
  • SIGQUIT(kill -3):ANR 时触发线程 dump。AMS 检测到 ANR 后,向目标进程发送 SIGQUIT,ART 的信号处理器捕获后打印所有线程的堆栈到 /data/anr/traces.txt
  • SIGSTOP / SIGCONT:进程的暂停和恢复。AMS 可以使用这些信号来暂停后台进程(Android 实际上不使用 SIGSTOP,而是使用 cgroup 的 freezer)。
  • SIGPIPE:管道/Socket 对端关闭时的默认行为。Android 应用通常忽略 SIGPIPE,改用 EPIPE 错误处理。
  • SIGUSR1:用于 ART GC 的某些场景,如 hprof 堆转储触发。
// frameworks/base/core/jni/android_os_Process.cpp
// 向目标进程发送 SIGQUIT 触发 ANR trace
static void android_os_Process_sendSignal(int pid, int sig) {
kill(pid, sig);
}

六、System V IPC 与 POSIX IPC 在 Android 中的状态

Android 基本上不使用 SysV IPC(shmgetmsggetsemget)和 POSIX 消息队列(mq_open)。原因:

  1. 安全模型不匹配:SysV IPC 使用全局 key(IPC_PRIVATEftok() 生成的 key),容易冲突和被恶意猜测。虽然 Android 通过 CONFIG_SYSVIPC 内核选项默认禁用 SysV IPC。

  2. 生命周期管理困难:SysV IPC 对象是全局的、持久的(需要通过 ipcrm 或程序显式删除),进程崩溃后可能残留。Binder 有自动引用计数和死亡通知。

  3. 缺乏身份传递:SysV IPC 不传递调用者 UID/PID,服务端无法做权限检查。

  4. 资源限制:SysV IPC 有全局数量限制(/proc/sys/kernel/shmmni/proc/sys/kernel/msgmni),在大规模 Android 系统上可能不够。

Android 内核编译配置中 CONFIG_SYSVIPC 通常是关闭的,这意味着 shmget() / msgget() / semget() 调用会失败。

七、各 IPC 机制的性能对比

7.1 基准对比

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

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

7.2 Binder 的性能瓶颈与优化

Binder 的事务大小限制为 1MB - 8KB(约 1016KB),超过此大小会导致 FAILED_TRANSACTION 错误。这是因为 Binder 驱动为每个进程分配的 mmap 缓冲区大小为 (1MB - 8KB)。

// frameworks/native/libs/binder/ProcessState.cpp
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
// 默认:1MB - 8KB * 2 = 1008KB

对于大数据传输场景,应该使用 Binder + ashmem 的组合:

  • Binder 传递控制命令和 ashmem fd
  • ashmem 承载实际数据

八、进程间通信的 SELinux 安全控制

Android 使用 SELinux 对所有 IPC 机制施加安全限制:

# system/sepolicy/public/
# Binder 调用限制
neverallow { appdomain } system_server:binder { call transfer };

# Socket 创建限制
neverallow { appdomain } self:unix_dgram_socket create;

# ashmem 访问限制
allow appdomain ashmem_device:chr_file { read write open ioctl };

SELinux 在 Binder 上的特殊规则:

  • binder { call }:允许向某进程发起 Binder 调用
  • binder { transfer }:允许将一个 Binder 对象引用传递给另一个进程
  • binder { impersonate }:允许以另一个进程的身份执行 Binder 调用

这些 SELinux 策略在 system/sepolicy/ 中定义,通过 MAC(Mandatory Access Control)机制确保即使应用获得了 Binder 引用,也必须满足 SELinux 策略才能成功通信。

九、核心面试题

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 控制,分配后会一直占用物理内存直到进程退出或显式释放。

其他区别:

  • ashmem 通过引用计数确定何时真正释放物理内存(当所有 mmap 和 fd 引用都关闭时才释放)。
  • ashmem 的内存可以在 pin 和 unpin 之间动态切换,而 POSIX shm 是静态的。
  • ashmem 支持更大的区域(受限于虚拟内存),而 System V shm 受系统全局限制。

Q4:Binder 的 mmap 缓冲区为什么是 1MB - 8KB?如果超过这个限制怎么办?

Binder 驱动为每个进程分配一个固定的 mmap 缓冲区(默认约 1016KB)。这个值是在 ProcessState 初始化时硬编码的。原因:

  • Binder 最初设计用于系统服务调用,传输的数据量通常很小(几十到几百字节)。
  • 内核空间不能无限分配内存给用户空间。
  • 1MB 是足够的大小,同时避免单个进程占用过多内核资源。

超过限制的处理方案:

  1. 减少单次传输的数据量(分批处理)。
  2. 使用 Binder + ashmem 组合:Binder 传递 ashmem fd,实际数据通过 ashmem 共享。
  3. 对于大文件/大块数据,使用 ContentProvider 的 openFile 接口(底层通过 pipe 或 fd 传递)。
  4. 在设备出厂时,可以通过修改 BINDER_VM_SIZE 宏来增大缓冲区(需要重新编译 Binder 驱动和 libbinder)。

Q5:Unix Domain Socket 如何实现文件描述符传递?在 Android 中哪些场景用到了这个特性?

Unix Domain Socket 通过 SCM_RIGHTS 辅助消息实现 fd 传递:

// 发送端:将 fd 附着在消息上发送
struct msghdr msg = {0};
struct cmsghdr *cmsg;
char buf[CMSG_SPACE(sizeof(int))];

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
*(int *)CMSG_DATA(cmsg) = fd_to_send; // 要传递的 fd

sendmsg(sockfd, &msg, 0); // 发送

// 接收端:从消息中提取 fd
struct msghdr msg = {0};
struct cmsghdr *cmsg;

recvmsg(sockfd, &msg, 0);
cmsg = CMSG_FIRSTHDR(&msg);
int received_fd = *(int *)CMSG_DATA(cmsg); // 提取 fd

在 Android 中的应用场景:

  1. Zygote -> App:Zygote 通过 socket 将应用的相关 fd 传递给刚刚 fork 出的子进程。
  2. installd:installd 守护进程创建数据目录后,可以通过 socket 将目录 fd 传回 system_server。
  3. adb:ADB 在 host 和 device 之间传递文件时使用 socket 对传递 fd。

核心:fd 传递是零拷贝的——接收方拿到的 fd 指向同一内核文件描述符,不需要再复制数据。

AOSP 核心路径参考:

  • frameworks/native/libs/binder/ — Binder 用户空间库
  • drivers/android/binder.c — Binder 驱动(Android 10+ 新版路径)
  • 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 守护进程
  • system/core/libutils/Looper.cpp — Looper 唤醒(eventfd)
  • frameworks/base/core/jni/android_database_CursorWindow.cpp — ashmem 在 ContentProvider 中的使用
  • drivers/staging/android/ion/ — ION 内存分配器(逐步废弃)
  • drivers/dma-buf/ — dma-buf 框架
打赏
  • 微信
  • 支付宝

评论