解剖麻雀:Binder 通信的整体架构全景图

37 阅读7分钟

在上一篇中,我们解决了“为什么”的问题。今天,我们要把 Binder 的架构“摊开”,不仅要看清它的分层结构,更要直接面对源码

一、三层架构:从 Java 到 Kernel 的映射

首先,再次确认我们的战场。Binder 的魅力在于它让上层的 Java 代码感觉像是在调用本地方法,而底层却在疯狂地穿越进程边界。

+-------------------------------------------------------+
|                  Application / Framework (Java)       |
|  [MyService.java]  [MyProxy.java]                     |
|         | JNI (android_os_Binder.cpp)                 |
+---------v---------------------------------------------+
|                  Native Framework (C++)               |
|  [ProcessState]   [IPCThreadState]                    |
|  [BBinder]        [BpBinder]                          |
|         | System Call (ioctl /dev/binder)             |
+---------v---------------------------------------------+
|                  Linux Kernel (Driver)                |
|  [binder_proc]    [binder_thread]                     |
|  [binder_node]    [binder_buffer]                     |
+-------------------------------------------------------+

二、核心角色源码大揭秘

为了让你直观感受这些类在源码中的样子,我们将源码中的核心逻辑,简化成了的C++ 风格伪代码。

1. IBinder:通信的“身份证”接口

一切的开始。无论是服务端还是客户端,手里拿的都是 IBinder 指针。它定义了通信的最基本动作:transact

// frameworks/native/libs/binder/include/binder/IBinder.h

class IBinder {
public:
    // 核心方法:发起一次事务
    // code: 命令码 (比如 ADD_BOOK = 1)
    // data: 输入数据包 (Parcel)
    // reply: 输出数据包 (Parcel)
    // flags: 标志位 (比如 FLAG_ONEWAY 异步调用)
    virtual status_t transact(
        uint32_t code, 
        const Parcel& data, 
        Parcel* reply, 
        uint32_t flags = 0) = 0;

    // 获取服务信息 (如调试用的描述符)
    virtual const char16_t* getInterfaceDescriptor() const = 0;
    
    // 检查服务是否存活
    virtual bool pingBinder() = 0;
    
    // ... 其他方法
};

解读

  • 这是一个纯虚类(接口)。
  • 你不需要关心它怎么实现,你只需要知道:只要拿到 IBinder 对象,就能调用 transact 发送数据
  • 具体的实现类有两个分支:BBinder(服务端)和 BpBinder(客户端)。

2. ProcessState:进程的“入场券”

每个进程在进入 Binder 世界前,必须初始化 ProcessState。它负责打开驱动 /dev/binder 并建立内存映射(mmap)。

// frameworks/native/libs/binder/ProcessState.cpp

class ProcessState {
private:
    int mDriverFD;          // /dev/binder 的文件描述符
    void* mVMStart;         // mmap 映射的起始地址
    size_t mMapSize;        // 映射区域大小 (默认 1MB - 8KB)

    // 单例模式,每个进程只有一个实例
    static ProcessState* gProcessState; 

public:
    static sp<ProcessState> self() {
        if (gProcessState != nullptr) {
            return gProcessState;
        }
        // 第一次调用时初始化
        gProcessState = new ProcessState("/dev/binder");
        gProcessState->startDriver(); // 打开驱动
        gProcessState->setupBinderContext(); // 设置 mmap
        return gProcessState;
    }

    void startDriver() {
        mDriverFD = open("/dev/binder", O_RDWR | O_CLOEXEC);
        // ... 错误处理
    }

    void setupBinderContext() {
        // 关键!执行 mmap,将内核缓冲区映射到用户空间
        // 这就是 Binder "一次拷贝" 的物理基础
        mMapSize = DEFAULT_BINDER_SIZE; 
        mVMStart = mmap(nullptr, mMapSize, PROT_READ, MAP_PRIVATE, mDriverFD, 0);
        // ...
    }
    
    // 获取线程状态
    sp<IPCThreadState> getThreadPool();
};

解读

  • 单例:一个进程只需要一次初始化。
  • mmap:注意 setupBinderContext 中的 mmap 调用。这块内存是后续接收数据的“收件箱”。如果没有这一步,进程就收不到来自内核的数据。

3. IPCThreadState:线程的“通讯员”

这是最忙碌的类。每个绑定到 Binder 的线程都有一个 IPCThreadState 实例。它维护着读写缓冲区,并执行真正的 ioctl 系统调用。

// frameworks/native/libs/binder/IPCThreadState.cpp

class IPCThreadState {
private:
    const int mProcess;           // 所属进程 ID (实际上是 driver fd)
    const pthread_t mMyThreadId;  // 当前线程 ID
    
    Parcel mOut;                  // 输出缓冲区 (写给内核的数据)
    Parcel mIn;                   // 输入缓冲区 (从内核读到的数据)

    // 单例 (线程局部存储 TLS)
    static IPCThreadState* self();

public:
    // 核心:执行 transact 请求
    status_t talkWithDriver(bool doReceive = true) {
        binder_write_read_struct bwr;
        
        // 准备数据:如果有输出数据,填入结构体
        if (mOut.dataPosition() > 0) {
            bwr.write_buffer = (uintptr_t)mOut.data();
            bwr.write_size = mOut.dataPosition();
        }
        
        // 准备接收:如果有空间接收,填入结构体
        if (doReceive) {
            bwr.read_buffer = (uintptr_t)mIn.data();
            bwr.read_size = mIn.dataCapacity();
        }

        // 【关键一步】进入内核!
        // 相当于告诉内核:"我有数据要发,同时也想看看有没有新消息"
        ioctl(mProcess, BINDER_WRITE_READ, &bwr);
        
        return NO_ERROR;
    }

    // 封装好的 transact 方法,供 BpBinder 调用
    status_t writeTransactionData(int cmd, uint32_t handle, 
                                  uint32_t code, const Parcel& data) {
        // 将命令和数据打包进 mOut 缓冲区
        mOut.writeInt32(cmd);
        mOut.writeInt32(handle);
        mOut.writeInt32(code);
        data.writeToParcel(&mOut);
        
        // 触发与内核的交互
        return talkWithDriver();
    }
    
    // 处理从内核收到的命令 (如 BC_TRANSACTION)
    status_t executeCommand(int32_t cmd);
};

解读

  • 双缓冲区mOutmIn 是用户态的临时仓库。
  • ioctltalkWithDriver 中的 ioctl 是用户态和内核态切换的关口。一旦调用,线程可能阻塞,直到内核处理完毕或有新数据。

4. BBinder vs BpBinder:服务端与客户端的分野

这是 Binder 多态的精髓。同样的 transact 调用,在不同对象上有完全不同的行为。

A. BBinder (Server 端)

代表本地拥有的服务对象。当内核把请求送过来时,由它来处理。

// frameworks/native/libs/binder/Binder.cpp

class BBinder : public IBinder {
protected:
    // 子类必须实现此方法来处理具体业务
    virtual status_t onTransact(uint32_t code, const Parcel& data, 
                                Parcel* reply, uint32_t flags = 0);

public:
    // 作为 Server,transact 是被调用的入口
    virtual status_t transact(uint32_t code, const Parcel& data, 
                              Parcel* reply, uint32_t flags = 0) override {
        // 1. 权限检查 (Security Check)
        // 2. 解包数据
        // 3. 调用虚函数 onTransact 分发给具体实现
        status_t err = onTransact(code, data, reply, flags);
        
        // 4. 如果需要返回结果且没有异常,标记成功
        if (reply != nullptr && err == NO_ERROR) {
             reply->writeInt32(NO_ERROR); 
        }
        return err;
    }
};

解读BBinder::transact被动的。它等待内核唤醒线程,然后被 IPCThreadState 调用,进而调用 onTransact 执行业务。

B. BpBinder (Client 端)

代表远程服务的代理。当你调用它时,它负责把请求发出去。

// frameworks/native/libs/binder/BpBinder.cpp

class BpBinder : public IBinder {
private:
    uint32_t mHandle; // 内核中对应服务的句柄 (Handle ID)

public:
    // 作为 Client,transact 是发起调用的入口
    virtual status_t transact(uint32_t code, const Parcel& data, 
                              Parcel* reply, uint32_t flags = 0) override {
        // 1. 如果连接断了,先尝试重连
        if (mAlive) { 
            // 2. 委托给 IPCThreadState 去干活
            // 这里会构造 BC_TRANSACTION 命令
            status_t status = IPCThreadState::self()->transact(
                mHandle, code, data, reply, flags);
            
            // 3. 处理死 Binder 通知等特殊情况
            if (status == DEAD_OBJECT || status == FAILED_TRANSACTION) {
                mAlive = false;
            }
            return status;
        }
        return DEAD_OBJECT;
    }
};

解读BpBinder::transact主动的。它拿到数据后,立刻找 IPCThreadState 打包并发送给内核。它持有 mHandle,这就是它在内核中定位目标服务的“门牌号”。


5. ServiceManager:全局注册表

它是一个特殊的 BBinder 子类,运行在 PID=1 的进程中。它的逻辑相对独立,主要负责维护一个哈希表。

// system/core/libcutils/service_management.c (简化逻辑)

class ServiceManager : public BBinder {
private:
    // 存储 map: 服务名 -> IBinder 指针
    std::map<String16, sp<IBinder>> mServiceMap;

public:
    virtual status_t onTransact(uint32_t code, const Parcel& data, 
                                Parcel* reply, uint32_t flags) override {
        switch(code) {
            case ADD_SERVICE: {
                String16 name = data.readString16();
                sp<IBinder> service = data.readStrongBinder();
                mServiceMap[name] = service; // 注册服务
                reply->writeInt32(0); // 成功
                break;
            }
            case GET_SERVICE: {
                String16 name = data.readString16();
                auto it = mServiceMap.find(name);
                if (it != mServiceMap.end()) {
                    reply->writeStrongBinder(it->second); // 返回 Binder 引用
                } else {
                    reply->writeStrongBinder(nullptr); // 没找到
                }
                break;
            }
        }
        return NO_ERROR;
    }
};

解读

  • 它本质上也是一个 Binder 服务。
  • 所有进程想要获取其他服务,第一步都是向这个特定的 Handle(通常是 0)发送 GET_SERVICE 请求。

三、串联起来:一次调用的代码旅行

现在,让我们把上面的伪代码串联起来,看一次完整的 proxy.doSomething() 是如何在代码层面流动的。

场景:Client 调用 Server 的 doSomething (Code = 10),这是一次同步调用

Binder IPC Request-Reply-2026-03-09-081819.png

关键节点解读

  1. 步骤 4 (BpBinder) : 客户端代理,持有 mHandle (句柄),它不知道服务具体在哪,只知道发给内核哪个 ID。
  2. 步骤 5 & 8 (ioctl) : 唯一的“过路费”站点。用户态数据在这里跨越边界。Client 在此挂起Server 在此被唤醒
  3. 步骤 7 (Kernel Copy) : 这里的内存拷贝是 Binder 高效的核心——只拷贝一次(从 Client 用户空间到 Kernel 空间,然后映射给 Server 用户空间,无需再拷一份到 Server 用户空间,而是直接映射访问)。
  4. 步骤 10 (BBinder) : 服务端本体。onTransact 是真正的业务分发入口,AIDL 生成的 BnXXX 类就在此处通过 switch(code) 调用具体方法。
  5. 步骤 12 (Reply) : 返回路径并非自动发生,Server 线程必须显式地再次发起一次 ioctl (携带 BC_REPLY) 来通知内核“我办完了”,内核才会去唤醒 Client。

总结

通过上面的分析,我们可以看到 Binder 设计的精妙之处:

  1. 统一接口:无论远近,统统通过 IBinder::transact 交互。

  2. 职责分离

    • ProcessState内存 (mmap)。
    • IPCThreadState通信 (ioctl)。
    • BpBinder发包
    • BBinder拆包和执行
  3. 透明代理BpBinder 的存在,让客户端完全感觉不到对面是一个远程进程。

下一篇,我们将不再满足于伪代码,而是真正深入到 Linux 内核驱动 (binder.c) 的核心逻辑中,去看看那个神秘的 binder_proc 结构体长什么样,以及内核是如何管理那些成千上万个 binder_thread 的。那才是 Binder 真正的“黑盒”内部。