4. ServiceManager启动与注册流程

4.1 模块边界与角色分层

ServiceManager 在 Binder 体系中处于核心枢纽位置,其职责跨越用户空间与内核空间。从架构分层看:

层次组件/模块核心职责
用户空间servicemanager 进程维护服务名称→句柄映射表;响应注册与查询请求
用户空间各服务进程(如 media、surfaceflinger)调用 defaultServiceManager()->addService() 发起注册
内核空间binder_proc / binder_node管理 Binder 节点、引用计数与句柄分配
关键识别:ServiceManager 本身也是一个 Binder 服务,但它拥有特权句柄 0(即 BINDER_SERVICE_MANAGER),是所有服务注册的“第一入口”。

4.2 ServiceManager 进程初始化流程

servicemanager 进程在 Android 启动早期由 init 进程通过 .rc 文件启动,其初始化包括以下步骤:

  1. 打开 Binder 设备:调用 open("/dev/binder", O_RDWR) 获取文件描述符。
  2. 内存映射:调用 mmap(NULL, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE, fd, 0) 分配内核缓冲区。
  3. 成为上下文管理器:通过 ioctl(fd, BINDER_SET_CONTEXT_MGR, NULL) 向内核声明本进程持有句柄 0,即 ServiceManager 身份。
  4. 进入事件循环:循环调用 ioctl(fd, BINDER_WRITE_READ, &bwr) 以阻塞等待其他进程的注册/查询请求。
风险点:如果 servicemanager 进程在初始化完成前崩溃,则句柄 0 失效,所有后续服务注册将失败。生产环境中建议在 init.rc 中配置 class core critical 与自动重启策略。

4.3 Binder 节点注册机制

当服务进程(如 mediaserver)调用 addService("media.player", service) 时,数据流穿越应用进程→Binder 驱动→ServiceManager:

  • 节点类型:服务进程的 Binder 对象在内核中创建 binder_node,该节点包含指向服务端强引用、节点状态标识。
  • 转换过程:BC_TRANSACTION → 驱动解析目标句柄 0 → 将服务节点信息封装为 BR_TRANSACTION 发送给 servicemanager。
  • 注册特性:servicemanager 将服务名与句柄写入内部哈希表,同一名称重复注册触发覆盖旧条目。
选型权衡:使用字符串哈希表而非红黑树,在服务数量通常不超过 2000 的场景下,O(1) 查找与低内存开销优于复杂树结构。

4.4 句柄分配与信任链路

句柄是 Binder 通信的唯一整数标签。分配规则如下:

句柄值所有者分配方式
0ServiceManager内核通过 BINDER_SET_CONTEXT_MGR 预留
1 ~ 0xFFFF注册的各服务驱动按需分配,binder_node→local_node→proc 链表维护
负值(保留/内部)非用户可见,由内核标志位使用

信任链路:服务进程向句柄 0 发送注册请求 → 驱动校验收发双方 proc 节点合法性 → servicemanager 在用户态校验服务名长度与合法性 → 写入映射表。此过程中,内核为根信任锚点,伪造句柄 0 或仿冒进程 PID 均需 root 权限,形成天然安全边界。

4.5 核心数据流与控制流

一次完整的服务注册流程包含以下数据流阶段:

  1. 服务进程构造 Parcel,包含服务名与 Binder 对象引用。
  2. 通过 IPCThreadState::transact() 发起 BC_TRANSACTION,目标句柄 = 0。
  3. Binder 驱动查找句柄 0 对应的 binder_node,将数据复制到目标进程的 mmap 缓冲区。
  4. servicemanager 从接收到的 Parcel 提取服务名与 Binder 引用,将引用转为内部句柄。
  5. servicemanager 添加条目到 svclist 链表,并发送 BR_TRANSACTION_COMPLETE 回服务进程。

4.6 系统架构流程图

下图描绘了从 servicemanager 初始化到服务注册完成的完整体系结构与数据流向:

ServiceManager启动与服务注册流程 servicemanager进程 打开/dev/binder | mmap | ioctl BINDER_SET_CONTEXT_MGR 服务进程(例:mediaserver) defaultServiceManager() → addService("media.player", service) Binder驱动(内核空间) binder_node 创建 | 句柄分配 mmap 缓冲区 | 数据拷贝 svclist 哈希映射表 "media.player" → handle 1 "surfaceflinger" → handle 2 客户端进程 getService("media.player") 返回 handle 1 → 发起通信 句柄 0 为 ServiceManager 特权节点

4.7 风险点与落地步骤

主要风险:

  • 句柄泄露:服务进程异常退出后,binder_node 未及时释放,累积导致句柄池耗尽。
  • 空引用风险:查询未被注册的服务,返回 NULL 句柄,若调用方未判空会导致空指针访问。

落地步骤:

  1. 在 servicemanager 启动的 .rc 文件中配置 onrestart 触发器,重启时清理残留句柄。
  2. 在 Binder 驱动层增加 binder_node 引用计数与超时回收机制。
  3. 要求在 getService() 调用后增加 Binder 引用判空分支,避免直接使用无效句柄。
验收标准:连续注册 5000 次 / 查询 10000 次后,内核 /proc/binder/stats 中 node 数量稳定在合理区间;模拟服务进程崩溃后,句柄在 5s 内完成回收;客户端对未注册服务调用 getService() 返回 null,不触发崩溃。

4.8 核心结论

ServiceManager 的启动与注册流程定义了 Binder 通信的初始信任锚点与句柄分配规则。内核提供特权句柄 0 与节点管理机制,用户空间的 servicemanager 进程维护名称映射表,二者通过 ioctl 与内存共享缓冲区形成闭环。该架构在 1000 量级服务场景下具有良好的性能与安全边界,但在大规模动态注册场景下需额外关注句柄回收与异常容错。