SM成为context_manager
ServiceManager 能接收系统服务查询,并不是因为名字叫 servicemanager,而是因为它通过 Binder ioctl 成为某个 Binder 设备 context 的管理者。这个动作建立了一条跨进程约束:客户端把目标 handle 写成 0 时,驱动不从客户端的 refs_by_desc 查普通引用,而是读取该设备 binder_context 中保存的 binder_context_mgr_node。
本文面向已经读过 ServiceManager启动、Binder事务发送 和 Binder远程引用 的读者。前文解释了 main() 为什么调用 becomeContextManager();本文继续向下追踪这个调用如何变成 ioctl、驱动如何做安全和 UID 检查、为什么扩展命令失败会回退,以及 handle 0 在事务目标解析和进程退出时分别发生什么。本文不展开 addService() 的服务名校验和服务表更新,那是下一篇的独立问题。
读完后,读者应能从 ProcessState::becomeContextManager() 找到 ioctl 入口,判断一次注册失败属于“已经有人注册”“UID 不匹配”“SELinux 拒绝”还是“节点分配失败”,并沿 binder_transaction() 解释 handle 0 为什么只能指向当前 Binder device 的 context manager。
1. 角色边界
1.1 三个对象
这里有三个容易混淆的对象。ServiceManager 是用户态的 BBinder 本地对象;binder_node 是驱动为本地 Binder 对象维护的内核节点;binder_context_mgr_node 是每个 Binder context 保存的特殊 node 指针。前两个描述“谁能执行 onTransact”,后一个描述“handle 0 应该路由给谁”。
| 对象 | 所有者 | 读取者 | 生效时机 |
|---|---|---|---|
ServiceManager | servicemanager 进程 | IPCThreadState | setTheContextObject() 后收到本地分发 |
binder_node | Binder 驱动 | 事务路由、引用管理 | binder_new_node() 成功后 |
binder_context_mgr_node | 某个 Binder device 的 binder_context | binder_transaction() | ioctl 注册成功后 |
1.2 设备隔离
源码文件:kernel/common/drivers/android/binder_internal.h
相关类型:binder_context、binder_device
struct binder_context {
struct binder_node *binder_context_mgr_node;
struct mutex context_mgr_node_lock;
kuid_t binder_context_mgr_uid;
const char *name;
};
struct binder_device {
struct hlist_node hlist;
struct miscdevice miscdev;
struct binder_context context;
struct inode *binderfs_inode;
refcount_t ref;
};binder_context 嵌在 binder_device 中,所以 context manager 不是跨所有 Binder 设备的单例。/dev/binder、/dev/hwbinder 等设备各自拥有 context、锁和 UID 记录;本文讨论的 servicemanager 默认打开 /dev/binder。因此,“handle 0 指向 ServiceManager”必须补上设备前提,不能写成所有 Binder 设备都共享一个全局服务管理器。
2. 用户注册
2.1 扩展命令
源码文件:frameworks/native/libs/binder/ProcessState.cpp
相关函数:ProcessState::becomeContextManager()
bool ProcessState::becomeContextManager()
{
std::unique_lock<std::mutex> _l(mLock);
flat_binder_object obj {
.flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
};
int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);
// fallback to original method
if (result != 0) {
android_errorWriteLog(0x534e4554, "121035042");
int unused = 0;
result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused);
}
if (result == -1) {
ALOGE("Binder ioctl to become context manager failed: %s\\n", strerror(errno));
}
return result == 0;
}扩展命令传入一个只设置 flags 的 flat_binder_object。这里的 flags 请求事务带上 security context;它不是把 ServiceManager 地址传给驱动。驱动通过当前打开的 file 找到 binder_proc,再由 binder_new_node(proc, fbo) 建立或复用本地 node。扩展命令失败后,用户态无条件尝试旧命令;因此“扩展命令失败”本身不等于注册失败,只有两次 ioctl 都没有返回 0,main() 才会把它当作 fatal 条件。
2.2 调用方顺序
源码文件:frameworks/native/cmds/servicemanager/main.cpp
相关函数:main()
IPCThreadState::self()->setTheContextObject(manager);
if (!ps->becomeContextManager()) {
LOG(FATAL) << "Could not become context manager";
}setTheContextObject() 和 ioctl 是两个方向的准备。前者把收到的 context-manager transaction 交给本进程的 manager 对象;后者把驱动的全局目标指针设为该进程的 node。前者不改变 handle 0 路由,后者也不替代用户态对象分发。启动顺序把它们连续放置,是因为二者共同构成“驱动找到进程后,线程能找到本地对象”的完整链路。
图中 FD 不是独立的服务对象,而是用户态 ioctl 与驱动 binder_ioctl 之间的边界。binder_context 只有在所有检查和 node 创建成功后才写入特殊指针。
3. 驱动校验
3.1 命令分派
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_ioctl()
case BINDER_SET_CONTEXT_MGR_EXT: {
struct flat_binder_object fbo;
if (copy_from_user(&fbo, ubuf, sizeof(fbo))) {
ret = -EFAULT;
break;
}
ret = binder_ioctl_set_ctx_mgr(filp, &fbo);
break;
}
case BINDER_SET_CONTEXT_MGR:
ret = binder_ioctl_set_ctx_mgr(filp, NULL);
break;扩展命令先复制用户态结构体,原始命令不携带结构体,最终都进入同一个核心函数。filp->private_data 是驱动在打开设备时关联的 binder_proc,因此注册目标由调用者打开的设备决定,而不是由命令参数决定。
3.2 单次占用
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_ioctl_set_ctx_mgr()
static int binder_ioctl_set_ctx_mgr(struct file *filp,
struct flat_binder_object *fbo)
{
struct binder_proc *proc = filp->private_data;
struct binder_context *context = proc->context;
kuid_t curr_euid = current_euid();
guard(mutex)(&context->context_mgr_node_lock);
if (context->binder_context_mgr_node) {
pr_err("BINDER_SET_CONTEXT_MGR already set\\n");
return -EBUSY;
}
...
}锁保护的是“是否已经占用”这一 context 级状态。第一次调用看到空指针,才会继续安全和 UID 检查;第二个并发或后续调用直接返回 -EBUSY。这不是用户态 ServiceManager 服务表的重复名称错误,而是驱动角色本身只能有一个当前 node。
3.3 权限与 UID
ret = security_binder_set_context_mgr(proc->cred);
if (ret < 0)
return ret;
if (uid_valid(context->binder_context_mgr_uid)) {
if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) {
pr_err("BINDER_SET_CONTEXT_MGR bad uid ...\\n");
return -EPERM;
}
} else {
context->binder_context_mgr_uid = curr_euid;
}安全检查先于 UID 记录。首次成功注册把当前有效 UID 写入 context;之后即使特殊 node 已被清理,只要 context 中 UID 仍有效,新的注册者也必须匹配这个 UID。源码没有把 UID 规则表达为“任何 root 都可以替换”,也没有在该函数中直接实现 SELinux 策略;security_binder_set_context_mgr() 把权限判断交给 LSM。
4. 节点保存
4.1 创建节点
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_ioctl_set_ctx_mgr()、binder_new_node()
new_node = binder_new_node(proc, fbo);
if (!new_node)
return -ENOMEM;
binder_node_lock(new_node);
new_node->local_weak_refs++;
new_node->local_strong_refs++;
new_node->has_strong_ref = 1;
new_node->has_weak_ref = 1;
context->binder_context_mgr_node = new_node;
binder_node_unlock(new_node);
binder_put_node(new_node);binder_new_node() 失败时特殊指针保持为空,用户态得到失败返回;成功后,驱动先给 node 建立本地强、弱引用状态,再写入 context。最后的 binder_put_node() 只释放本次临时获取的 node 引用,不会把刚保存的 context 指针立即变成悬空指针,因为 node 的本地引用和 context 所属进程生命周期仍然存在。
4.2 引用描述符
源码文件:kernel/common/drivers/android/binder.c
相关函数:get_ref_desc_olocked()
/* 0 is reserved for the context manager */
offset = (node == proc->context->binder_context_mgr_node) ? 0 : 1;当客户端为某个 node 分配 handle 时,驱动把 context manager node 的搜索起点设为 0;普通 node 从 1 开始。这段代码解释了“handle 0 是保留值”的实现边界:它不是客户端自行约定的 service name,也不是 manager 字符串的别名,而是驱动引用分配对特殊 node 的保留槽位。
5. 事务路由
5.1 普通句柄
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction()
if (tr->target.handle) {
struct binder_ref *ref;
binder_proc_lock(proc);
ref = binder_get_ref_olocked(proc, tr->target.handle, true);
if (ref) {
target_node = binder_get_node_refs_for_txn(
ref->node, &target_proc, &return_error);
} else {
return_error = BR_FAILED_REPLY;
}
binder_proc_unlock(proc);
}非零 handle 必须在发送方自己的引用表中找到 binder_ref。因此普通服务调用依赖先前已经建立的引用,错误 handle 会进入 failed reply,而不会退回 context manager。
5.2 零句柄
else {
mutex_lock(&context->context_mgr_node_lock);
target_node = context->binder_context_mgr_node;
if (target_node)
target_node = binder_get_node_refs_for_txn(
target_node, &target_proc, &return_error);
else
return_error = BR_DEAD_REPLY;
mutex_unlock(&context->context_mgr_node_lock);
}零句柄分支完全绕过发送方的普通 binder_ref 查找,改为锁住当前设备的 context 并读取特殊 node。node 为空时返回 BR_DEAD_REPLY,所以“ServiceManager 没有注册某个服务”和“Binder context manager 已死亡”是两种不同故障:前者发生在 ServiceManager 的服务表逻辑,后者发生在事务目标解析之前。
5.3 自调用保护
if (target_node && target_proc->pid == proc->pid) {
binder_user_error("got transaction to context manager from process owning it\\n");
return_error = BR_FAILED_REPLY;
return_error_param = -EINVAL;
goto err_invalid_target_handle;
}驱动拒绝 context manager 进程自己通过目标 0 发起的事务。这个分支不能推导出“ServiceManager 不能调用任何 Binder 服务”;它只限制目标解析为自身 context-manager node 的这一路径,普通非零 handle 仍按常规引用规则处理。
6. 失败清理
6.1 注册失败
| 分支 | 驱动结果 | binder_context_mgr_node | 用户态后果 |
|---|---|---|---|
| 已有 node | -EBUSY | 保持原值 | becomeContextManager() 返回 false |
| SELinux 拒绝 | LSM 错误码 | 保持空值 | 进入 fatal |
| UID 不匹配 | -EPERM | 保持空值或原值 | 进入 fatal |
| node 分配失败 | -ENOMEM | 保持空值 | 进入 fatal |
| 成功 | 0 | 指向新 node | 继续建立 Looper |
失败路径的共同点是写入特殊指针发生在所有前置检查和 node 创建之后。不会出现“UID 检查失败但 context 已指向半成品 node”的中间状态。
6.2 进程退出
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_deferred_release()
mutex_lock(&context->context_mgr_node_lock);
if (context->binder_context_mgr_node &&
context->binder_context_mgr_node->proc == proc) {
binder_debug(BINDER_DEBUG_DEAD_BINDER,
"%s: %d context_mgr_node gone\\n",
__func__, proc->pid);
context->binder_context_mgr_node = NULL;
}
mutex_unlock(&context->context_mgr_node_lock);进程释放时,驱动按所属 proc 判断是否清空 context manager node。清空后,后续目标 0 的事务会走 BR_DEAD_REPLY,而不是继续访问已经死亡的进程。这里没有把 binder_context_mgr_uid 重置为无效值,因此“node 被清空”与“下一进程可以用任意 UID 接管”不能互相推断。
状态图中的 DeadReply 不是 binder_context_mgr_node 的持久状态,而是 node 为空时某次事务得到的结果;这样画是为了区分“注册状态”与“请求观察到的错误”。
7. 读源码
7.1 命令搜索
读者可以在 AOSP 源码树中运行:
rg -n "becomeContextManager|BINDER_SET_CONTEXT_MGR(_EXT)?" \
frameworks/native/libs/binder/ProcessState.cpp \
kernel/common/include/uapi/linux/android/binder.h \
kernel/common/drivers/android/binder.c
rg -n "binder_ioctl_set_ctx_mgr|binder_context_mgr_node|binder_deferred_release" \
kernel/common/drivers/android/binder.c \
kernel/common/drivers/android/binder_internal.h
rg -n "target.handle|context_mgr_node|BR_DEAD_REPLY" \
kernel/common/drivers/android/binder.c第一组命令应能把用户态调用、UAPI 命令码和驱动分派连起来;第二组定位注册与清理 owner;第三组定位普通 handle 和 0 handle 的分叉。
7.2 判断练习
假设 becomeContextManager() 返回 false,先按返回码和日志区分 -EBUSY、-EPERM、LSM 错误和 -ENOMEM,不要直接把所有失败归结为“Binder 没打开”。假设一个客户端得到 BR_DEAD_REPLY,应检查目标设备的 binder_context_mgr_node 是否已在 binder_deferred_release() 中清空;若 node 仍存在,则问题不在 context manager 进程退出路径,而应继续追踪事务目标和后续交付。
再反向复述这条主线:servicemanager 打开哪个设备,ProcessState 发出哪个 ioctl,驱动把哪个 node 写入哪个 context 字段,客户端的 0 handle 在哪里读取该字段,以及 node 为空时返回什么。能够沿这五个问题定位源码,就不会把 manager 服务名、用户态 context object 和驱动 handle 0 混成同一个状态。
