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 文件启动,其初始化包括以下步骤:
- 打开 Binder 设备:调用
open("/dev/binder", O_RDWR)获取文件描述符。 - 内存映射:调用
mmap(NULL, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE, fd, 0)分配内核缓冲区。 - 成为上下文管理器:通过
ioctl(fd, BINDER_SET_CONTEXT_MGR, NULL)向内核声明本进程持有句柄 0,即 ServiceManager 身份。 - 进入事件循环:循环调用
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 通信的唯一整数标签。分配规则如下:
| 句柄值 | 所有者 | 分配方式 |
|---|---|---|
| 0 | ServiceManager | 内核通过 BINDER_SET_CONTEXT_MGR 预留 |
| 1 ~ 0xFFFF | 注册的各服务 | 驱动按需分配,binder_node→local_node→proc 链表维护 |
| 负值 | (保留/内部) | 非用户可见,由内核标志位使用 |
信任链路:服务进程向句柄 0 发送注册请求 → 驱动校验收发双方 proc 节点合法性 → servicemanager 在用户态校验服务名长度与合法性 → 写入映射表。此过程中,内核为根信任锚点,伪造句柄 0 或仿冒进程 PID 均需 root 权限,形成天然安全边界。
4.5 核心数据流与控制流
一次完整的服务注册流程包含以下数据流阶段:
- 服务进程构造
Parcel,包含服务名与 Binder 对象引用。 - 通过
IPCThreadState::transact()发起 BC_TRANSACTION,目标句柄 = 0。 - Binder 驱动查找句柄 0 对应的
binder_node,将数据复制到目标进程的 mmap 缓冲区。 - servicemanager 从接收到的
Parcel提取服务名与 Binder 引用,将引用转为内部句柄。 - servicemanager 添加条目到
svclist链表,并发送 BR_TRANSACTION_COMPLETE 回服务进程。
4.6 系统架构流程图
下图描绘了从 servicemanager 初始化到服务注册完成的完整体系结构与数据流向:
4.7 风险点与落地步骤
主要风险:
- 句柄泄露:服务进程异常退出后,
binder_node未及时释放,累积导致句柄池耗尽。 - 空引用风险:查询未被注册的服务,返回 NULL 句柄,若调用方未判空会导致空指针访问。
落地步骤:
- 在 servicemanager 启动的 .rc 文件中配置
onrestart触发器,重启时清理残留句柄。 - 在 Binder 驱动层增加
binder_node引用计数与超时回收机制。 - 要求在
getService()调用后增加 Binder 引用判空分支,避免直接使用无效句柄。
验收标准:连续注册 5000 次 / 查询 10000 次后,内核
/proc/binder/stats 中 node 数量稳定在合理区间;模拟服务进程崩溃后,句柄在 5s 内完成回收;客户端对未注册服务调用 getService() 返回 null,不触发崩溃。
4.8 核心结论
ServiceManager 的启动与注册流程定义了 Binder 通信的初始信任锚点与句柄分配规则。内核提供特权句柄 0 与节点管理机制,用户空间的 servicemanager 进程维护名称映射表,二者通过 ioctl 与内存共享缓冲区形成闭环。该架构在 1000 量级服务场景下具有良好的性能与安全边界,但在大规模动态注册场景下需额外关注句柄回收与异常容错。