Skip to content

Binder的C/S模型

沿本地接口短路、远程代理、驱动引用、服务线程和回调反转,解释 Binder Client-Server 角色如何在一次事务中变化。

基于android-17.0.0_r1
AndroidBinderIPCBpBinderBBinder源码阅读

Binder的C/S模型 ​

“Client 调用 Proxy,Server 执行 Stub”是 Binder 的入门口诀,但它隐藏了三个关键事实:

  • Client 和 Server 是一次事务中的角色,不是进程的永久标签;
  • 同一进程既能持有远程 BpBinder,也能发布本地 BBinder;
  • 接口转换可能在本地直接短路,根本不会进入驱动。

本文面向已经读过 Binder调用源码地图 的读者,沿 native libbinder 和 kernel/common 真实源码回答一个问题:一个 IBinder 接口在本地、远程、嵌套回调三种情况下,谁拥有对象、谁选择线程、谁保存事务栈,状态如何闭合。

本文不展开完整 AIDL 代码生成、死亡通知、引用计数专题和线程池上限。读完后,读者应能区分 queryLocalInterface、BpInterface、BnInterface、binder_ref、binder_node、transaction_stack,并解释“服务方法反向回调客户端”为什么仍然遵守同一套 C/S 状态规则。

1. 角色边界 ​

角色归属 ​

一次普通调用可以表示为:

时刻调用方角色被调用方角色关键 owner
接口获取取服务的进程ServiceManagerhandle/ref
事务发送持有 BpBinder 的线程持有 BBinder 的进程binder_transaction
服务回调原服务线程原客户端的 BBinder新 transaction
reply等待 reply 的原线程发送 BC_REPLY 的服务线程transaction_stack

Server 不是“永远只接收请求”。当服务把一个 callback Binder 放进 Parcel 后,客户端也会成为 被调用方,服务进程暂时成为新的 Client。角色由当前 transaction 的 target 和 from 决定。

三个对象 ​

对象所在进程作用
BpBinder远程对象的持有方用 handle 发起 native 事务
BBinder本地对象的拥有方用 ptr/cookie 接收事务
binder_ref/node内核在进程间翻译引用和目标

handle 只在持有它的进程中有意义;node 的 ptr/cookie 只在本地对象所属进程中使用。跨进程传递 Binder 对象时,驱动会把 BINDER_TYPE_BINDER 或 BINDER_TYPE_HANDLE 转换成目标进程可用的 引用形态。

2. 接口转换 ​

asInterface ​

源码文件:frameworks/native/libs/binder/include/binder/IInterface.h

相关宏:DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0()

cpp
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(ITYPE, INAME, BPTYPE) \
    const ::android::String16& ITYPE::getInterfaceDescriptor() const { \
        return ITYPE::descriptor; \
    } \
    ::android::sp<ITYPE> ITYPE::asInterface( \
            const ::android::sp<::android::IBinder>& obj) { \
        ::android::sp<ITYPE> intr; \
        if (obj != nullptr) { \
            intr = ::android::sp<ITYPE>::cast( \
                    obj->queryLocalInterface(ITYPE::descriptor)); \
            if (intr == nullptr) { \
                intr = ::android::sp<BPTYPE>::make(obj); \
            } \
        } \
        return intr; \
    }

asInterface 先调用 queryLocalInterface,再决定是否创建 BpInterface。这个顺序就是 C/S 模型的 第一个分叉:同一进程拿到 BnInterface 时可以返回本地接口;远程对象默认返回空,才构造代理。 所以“调用接口一定经过 Binder 驱动”是错误的。

本地接口 ​

源码文件:frameworks/native/libs/binder/include/binder/IInterface.h

相关模板:BnInterface<INTERFACE>

cpp
template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
        const String16& _descriptor)
{
    if (_descriptor == INTERFACE::descriptor)
        return sp<IInterface>::fromExisting(this);
    return nullptr;
}

template<typename INTERFACE>
IBinder* BnInterface<INTERFACE>::onAsBinder()
{
    return this;
}

本地匹配返回同一个对象的接口视图,调用方后续会直接调用 C++ 虚函数。这里没有 Parcel、handle 或 transaction_stack;如果调试时看到本地调用没有 binder transaction,这是设计行为而不是丢日志。

远程接口 ​

源码文件:frameworks/native/libs/binder/include/binder/IInterface.h

cpp
template<typename INTERFACE>
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
    : BpRefBase(remote)
{
}

template<typename INTERFACE>
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
{
    return remote();
}

远程接口只保存 remote IBinder;生成的 BpFoo 方法通常把参数写入 Parcel,再调用 remote()->transact。 BpInterface 不拥有服务对象,它只拥有一份远程引用的用户态表示。

3. 本地实体 ​

BBinder接口 ​

源码文件:frameworks/native/libs/binder/Binder.cpp

相关函数:IBinder::queryLocalInterface()、BBinder::localBinder()

cpp
sp<IInterface> IBinder::queryLocalInterface(
        const String16& /*descriptor*/)
{
    return nullptr;
}

BBinder* IBinder::localBinder()
{
    return nullptr;
}

BBinder* BBinder::localBinder()
{
    return this;
}

IBinder 的默认 queryLocalInterface 返回空;BnInterface 才按 descriptor 提供本地接口。localBinder 返回 this 是服务端判断“这是本地实体”的依据,BpBinder 则实现 remoteBinder 返回自身。

事务入口 ​

源码文件:frameworks/native/libs/binder/Binder.cpp

相关函数:BBinder::transact()

cpp
status_t BBinder::transact(uint32_t code, const Parcel& data,
        Parcel* reply, uint32_t flags)
{
    data.setDataPosition(0);
    if (reply != nullptr && (flags & FLAG_CLEAR_BUF)) {
        reply->markSensitive();
    }

    status_t err = NO_ERROR;
    switch (code) {
        case PING_TRANSACTION:
            err = pingBinder();
            break;
        case EXTENSION_TRANSACTION:
            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
            err = reply->writeStrongBinder(getExtension());
            break;
        case DEBUG_PID_TRANSACTION:
            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
            err = reply->writeInt32(getDebugPid());
            break;
        default:
            err = onTransact(code, data, reply, flags);
            break;
    }
    if (reply != nullptr)
        reply->setDataPosition(0);
    return err;
}

BBinder 先处理系统事务码,再把用户 code 交给 onTransact。reply 在本地调用路径也会被重置到 起始位置,因此本地和远程接口都可以用同一套 Parcel 读取约定,但它们的线程和内核路径不同。

默认存根 ​

源码文件:frameworks/native/libs/binder/Binder.cpp

相关函数:BBinder::onTransact()

cpp
status_t BBinder::onTransact(uint32_t code, const Parcel& data,
        Parcel* reply, uint32_t /*flags*/)
{
    switch (code) {
        case INTERFACE_TRANSACTION:
            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
            reply->writeString16(getInterfaceDescriptor());
            return NO_ERROR;
        default:
            return UNKNOWN_TRANSACTION;
    }
}

实际 BnInterface 子类会覆写 onTransact,AIDL Stub 还会检查 descriptor、读取参数和调用服务实现。 默认 BBinder 不知道业务方法;未知 code 返回 UNKNOWN_TRANSACTION,这也是代理端区分接口不匹配的重要 失败入口。

4. 远程代理 ​

BpBinder入口 ​

源码文件:frameworks/native/libs/binder/BpBinder.cpp

相关函数:BpBinder::transact()

cpp
status_t BpBinder::transact(uint32_t code, const Parcel& data,
        Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status;
        if (isRpcBinder()) {
            status = rpcSession()->transact(
                    sp<IBinder>::fromExisting(this), code, data, reply, flags);
        } else {
            status = IPCThreadState::self()->transact(
                    binderHandle(), code, data, reply, flags);
        }
        if (status == DEAD_OBJECT)
            mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

内核 Binder 分支把 handle 交给 IPCThreadState;收到 DEAD_OBJECT 后只把当前代理标记为死亡, 不会自动把服务重启后的新对象塞回旧代理。恢复必须由上层重新查找服务或重建接口。

handle意义 ​

源码文件:frameworks/native/libs/binder/ProcessState.cpp

相关函数:getStrongProxyForHandle()

cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    std::function<void()> postTask;
    std::unique_lock<std::mutex> _l(mLock);
    handle_entry* e = lookupHandleLocked(handle);
    if (e != nullptr) {
        IBinder* b = e->binder;
        if (b == nullptr || !e->refs->attemptIncWeak(this)) {
            sp<BpBinder> bp = BpBinder::PrivateAccessor::create(
                    handle, &postTask);
            e->binder = bp.get();
            if (bp)
                e->refs = bp->getWeakRefs();
            result = bp;
        } else {
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }
    _l.unlock();
    if (postTask)
        postTask();
    return result;
}

实际源码还包含 handle 0 的 context manager 特例、引用计数和驱动命令;这里保留核心 owner: ProcessState 按本进程 handle 表缓存 BpBinder。两个线程拿同一 handle 时,可能共享同一个 native 代理对象,但每次 transact 仍由各自 IPCThreadState 和驱动线程状态执行。

5. 引用转换 ​

Binder对象写入 ​

源码文件:frameworks/native/libs/binder/Parcel.cpp

相关函数:Parcel::flattenBinder()

cpp
status_t Parcel::flattenBinder(const sp<IBinder>& binder)
{
    flat_binder_object obj;
    if (binder) {
        if (BBinder* local = binder->localBinder()) {
            obj.hdr.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local);
        } else {
            BpBinder* proxy = binder->remoteBinder();
            obj.hdr.type = BINDER_TYPE_HANDLE;
            obj.handle = proxy->binderHandle();
            obj.cookie = 0;
        }
    } else {
        obj.hdr.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }
    return writeObject(obj, false);
}

写入 Parcel 的 Binder 对象有两种主要形态:本地对象携带 ptr/cookie,远程代理携带 handle。 这不是把 C++ 对象字节复制给对端,而是让驱动在目标进程建立或复用对应的引用。

驱动转换 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_translate_binder()、binder_translate_handle()

c
static int binder_translate_binder(struct flat_binder_object *fp,
        struct binder_transaction *t, struct binder_thread *thread)
{
    struct binder_proc *proc = thread->proc;
    struct binder_proc *target_proc = t->to_proc;
    struct binder_node *node;
    struct binder_ref_data rdata;

    node = binder_get_node(proc, fp->binder);
    if (!node)
        node = binder_new_node(proc, fp);
    if (security_binder_transfer_binder(proc->cred, target_proc->cred))
        return -EPERM;

    binder_inc_ref_for_node(target_proc, node,
            fp->hdr.type == BINDER_TYPE_BINDER,
            &thread->todo, &rdata);
    fp->hdr.type = BINDER_TYPE_HANDLE;
    fp->binder = 0;
    fp->handle = rdata.desc;
    fp->cookie = 0;
    return 0;
}

发送一个本地 Binder 时,驱动在发送方 proc 找到或创建 node,再在目标 proc 增加 ref,并把对象 改写为 handle。反向发送一个 handle 时,binder_translate_handle 会查找源 ref;若目标进程正是 node 所属进程,可以恢复 BINDER_TYPE_BINDER 和 ptr/cookie,否则在目标进程创建新的 ref。

两个引用树 ​

源码文件:kernel/common/drivers/android/binder_internal.h

c
struct binder_ref {
    struct binder_ref_data data;
    struct rb_node rb_node_desc;
    struct rb_node rb_node_node;
    struct hlist_node node_entry;
    struct binder_proc *proc;
    struct binder_node *node;
};

struct binder_proc {
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    struct rb_root nodes;
    struct rb_root threads;
    struct list_head todo;
    struct binder_alloc alloc;
};

refs_by_desc 支持从 handle 查找,refs_by_node 支持从 node 查找;node->refs 还记录哪些进程持有该 节点的引用。这个多重索引解释了为什么 handle 不是全局对象编号,也解释了为什么清理一个进程 时需要同时处理 node、ref 和 todo。

6. 事务栈 ​

目标选择 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()

c
if (reply) {
    in_reply_to = thread->transaction_stack;
    if (in_reply_to == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_empty_call_stack;
    }
    thread->transaction_stack = in_reply_to->to_parent;
    target_thread = binder_get_txn_from_and_acq_inner(in_reply_to);
} else {
    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;
    }
}

新事务按 target handle 找 node 和目标进程;reply 不重新按服务名查找,而是从当前服务线程的 transaction_stack 找回原调用线程。回复栈损坏会生成 BR_FAILED_REPLY 或 BR_DEAD_REPLY。

同步事务 ​

源码文件:kernel/common/drivers/android/binder.c

c
if (!(t->flags & TF_ONE_WAY)) {
    binder_inner_proc_lock(proc);
    binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);
    t->from_parent = thread->transaction_stack;
    thread->transaction_stack = t;
    binder_inner_proc_unlock(proc);

    return_error = binder_proc_transaction(
            t, target_proc, target_thread);
}

同步事务发送前把当前事务压入 from 线程的栈;目标服务线程收到事务后,又把事务压入自己的 transaction_stack。服务发送 BC_REPLY 时,驱动利用这些 parent/from 字段把 reply 放回原等待 线程。oneway 没有同样的业务 reply 栈,因此不能用同步调用的完成条件解释它。

嵌套调用 ​

服务处理客户端请求时,如果服务又同步调用第三个进程,Binder 会尝试依据 transaction_stack 选择关联线程,并记录 from_parent。嵌套调用不是“新开一个固定线程”,而是当前 Binder 线程和 驱动事务栈的状态组合;死锁风险来自锁、线程等待和回调方向,而不是 C/S 名称本身。

7. 回调反转 ​

传入回调 ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

相关测试:BinderLibTest.CallBack()

cpp
TEST_F(BinderLibTest, CallBack)
{
    Parcel data, reply;
    sp<BinderLibTestCallBack> callBack = new BinderLibTestCallBack();
    data.writeStrongBinder(callBack);
    EXPECT_THAT(m_server->transact(
            BINDER_LIB_TEST_NOP_CALL_BACK, data, &reply, TF_ONE_WAY),
            StatusEq(NO_ERROR));
    EXPECT_THAT(callBack->waitEvent(5), StatusEq(NO_ERROR));
    EXPECT_THAT(callBack->getResult(), StatusEq(NO_ERROR));
}

客户端把本地 callback Binder 写入 Parcel,服务收到后取得一个指向客户端 node/ref 的远程接口, 再以 oneway transaction 回调客户端。原来的 Client 在这一笔新事务中变成 Server;原服务线程 变成 Client。

回调时序 ​

测试的关键断言不是“回调函数被调用”,而是 waitEvent 和 getResult 都成功;这说明服务端 确实完成了回调事务,但不能推出回调线程、调度延迟或任何服务的通用顺序。

嵌套验证 ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

相关测试:BinderLibTest.IndirectGetId2()、BinderLibTest.IndirectGetId3()

cpp
TEST_F(BinderLibTest, IndirectGetId3)
{
    Parcel data, reply;
    int32_t serverId[3];

    data.writeInt32(countof(serverId));
    for (size_t i = 0; i < countof(serverId); i++) {
        sp<IBinder> server = addServer(&serverId[i]);
        data.writeStrongBinder(server);
        data.writeInt32(BINDER_LIB_TEST_INDIRECT_TRANSACTION);

        BinderLibTestBundle nested;
        nested.writeInt32(1);
        nested.writeStrongBinder(m_server);
        nested.writeInt32(BINDER_LIB_TEST_GET_ID_TRANSACTION);
        nested.appendTo(&data);
    }

    ASSERT_THAT(m_server->transact(
            BINDER_LIB_TEST_INDIRECT_TRANSACTION, data, &reply),
            StatusEq(NO_ERROR));
}

输入包含三个动态 server Binder,并在每个 bundle 内再次放入 m_server 和下一层事务码;动作是 让服务端继续调用这些 Binder;断言随后逐层读取 id、count 和 bundle 边界。它能证明多层对象 传递和嵌套事务可以闭合,不能证明任意业务锁组合都不会死锁。

8. 失败边界 ​

本地不匹配 ​

queryLocalInterface descriptor 不匹配时返回 null,asInterface 会构造远程代理;如果调用方 误把不同接口当成同一接口,后续可能得到 UNKNOWN_TRANSACTION 或 descriptor 校验失败。

无效引用 ​

驱动在 binder_transaction 中找不到 handle 对应的 ref,或者 node 已死亡,会返回失败命令; 客户端可能看到 BR_FAILED_REPLY、BR_DEAD_REPLY 或 DEAD_OBJECT。此时先查 ref/node 解析,不要 先查服务实现。

代理死亡 ​

BpBinder 在收到 DEAD_OBJECT 后将 mAlive 置为 false。旧代理不会自动绑定新服务;恢复必须由 上层重新执行服务发现并重建接口。

回调失败 ​

回调对象被释放、服务线程退出或 oneway 队列无法继续时,原始同步调用可能已经成功;回调的 失败不是原始 reply 的失败。需要分别记录原请求 transaction 和回调 transaction。

9. 阅读边界 ​

本文证明了本地短路、远程代理、node/ref 转换、同步事务栈和回调反转。没有展开死亡通知、 完整强弱引用释放、AIDL 生成代码的每个参数方向、RPC Binder 分支、线程池上限、SELinux 策略和 Binder allocator 细节。

可继续阅读:

  • Binder调用源码地图:复习跨层同步事务主线;
  • Linux IPC选择边界:比较 Binder 与其他 IPC 的 owner 和清理;
  • 驱动层的 node/ref 专题:继续沿 binder_translate_binder() 和 binder_translate_handle();
  • 事务协议专题:继续拆 BC/BR 命令和 transaction_stack。

最后做一个只读练习:任选一个通过 Parcel 传递的 Binder 对象,分别标出发送进程的 localBinder/ remoteBinder、目标进程的 node/ref、服务线程的 target/cookie 和 reply 的 transaction_stack。 如果某一步只能说“Binder 自动完成”,就回到对应源码的 owner 和锁边界。

10. 角色复述 ​

同一事务中,A 发送请求时是 client,B 执行 onTransact 时是 server;若 B 使用 A 传来的 callback Binder,B 立即成为新的 client,A 的 callback 对象成为新的 server。驱动不保存“永久客户端/服务端”标签,只保存 transaction 的 from/to 和 node/ref 关系;角色随事务方向改变。

这也是本篇与 Binder调用源码地图 的边界:地图回答“跨哪些层”,本文回答“同一对象在不同事务里由谁消费”。具体嵌套栈和死锁条件留给 Binder嵌套调用。