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

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

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

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

scss 复制代码
+-------------------------------------------------------+
|                  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

arduino 复制代码
// 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)。

csharp 复制代码
// frameworks/native/libs/binder/ProcessState.cpp

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

    // 单例模式,每个进程只有一个实例
    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 系统调用。

arduino 复制代码
// 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 端)

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

arduino 复制代码
// 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 端)

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

arduino 复制代码
// 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 的进程中。它的逻辑相对独立,主要负责维护一个哈希表。

arduino 复制代码
// 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),这是一次同步调用

arduino 复制代码
[ Client 进程 ]                                  [ Kernel 空间 ]                                  [ Server 进程 ]
      |                                                |                                                |
      |  1. Java: proxy.doSomething()                  |                                                |
      |     |                                          |                                                |
      |     v                                          |                                                |
      |  2. JNI: android_os_Binder_transact            |                                                |
      |     |                                          |                                                |
      |     v                                          |                                                |
      |  3. C++: BpBinder::transact(code, data)        |                                                |
      |     | (持有 mHandle)                           |                                                |
      |     v                                          |                                                |
      |  4. C++: IPCThreadState::transact              |                                                |
      |     | - 组装 BC_TRANSACTION 命令               |                                                |
      |     | - 写入 mOut 缓冲区                       |                                                |
      |     v                                          |                                                |
      |  5. C++: IPCThreadState::talkWithDriver        |                                                |
      |     | (准备 binder_write_read 结构体)          |                                                |
      |     |                                          |                                                |
      |     +-------------------> 6. ioctl(BINDER_WRITE_READ) ------------------+
      |     | (用户态 -> 内核态)                      |                                                |
      |     | [线程阻塞/睡眠]                         |  7. binder_ioctl 处理                        |
      |     |                                         |     - 解析 BC_TRANSACTION                  |
      |     |                                         |     - 根据 mHandle 查找 binder_node        |
      |     |                                         |     - 内存拷贝 (User -> Kernel Buffer)     |
      |     |                                         |     - 查找 Server 进程的空闲线程           |
      |     |                                         |     - 构造 BR_TRANSACTION                  |
      |     |                                         |     - 唤醒 Server 线程 (wait_queue)        |
      |     |                                         |                        |                   |
      |     |                                         |                        v                   |
      |     |                                         |                8. Server 线程苏醒          |
      |     |                                         |                   |                        |
      |     |                                         |                   v                        |
      |     |                                         | <--------- ioctl 返回 (BR_TRANSACTION) -----+
      |     |                                         |                        |
      |     |                                         |  9. C++: IPCThreadState::executeCommand    |
      |     |                                         |     - 解析 BR_TRANSACTION                  |
      |     |                                         |     - 读取数据到 mIn 缓冲区                |
      |     |                                         |     - 调用 target->transact (BBinder)      |
      |     |                                         |                        |                   |
      |     |                                         |  10. C++: BBinder::transact                |
      |     |                                         |      - 调用 onTransact(code, data, reply)  |
      |     |                                         |                        |                   |
      |     |                                         |  11. C++: MyService::onTransact            |
      |     |                                         |      - [执行业务逻辑 doSomething]          |
      |     |                                         |      - 结果写入 reply Parcel               |
      |     |                                         |                        |                   |
      |     |                                         |  12. C++: IPCThreadState::sendReply        |
      |     |                                         |      - 组装 BC_REPLY 命令                  |
      |     |                                         |      - 写入 mOut 缓冲区                    |
      |     |                                         |      - 再次 ioctl(BINDER_WRITE_READ)       |
      |     |                                         |                        |                   |
      |     |                                         |  13. Kernel: 处理 BC_REPLY                 |
      |     |                                         |      - 找到等待的 Client 线程              |
      |     |                                         |      - 拷贝 reply 数据                     |
      |     |                                         |      - 唤醒 Client 线程                    |
      |     |                                         |                        |                   |
      | <----+ (ioctl 返回 BR_REPLY) -----------------+                        |                   |
      |     |                                         |                        |                   |
      |  14. C++: IPCThreadState::waitForResponse      |                        |                   |
      |      - 解析 BR_REPLY                           |                        |                   |
      |      - 从 mIn 读取 reply 数据                  |                        |                   |
      |      |                                         |                        |                   |
      |      v                                         |                        |                   |
      |  15. C++: BpBinder::transact 返回              |                        |                   |
      |      |                                         |                        |                   |
      |      v                                         |                        |                   |
      |  16. JNI: 返回 Java 层                         |                        |                   |
      |      |                                         |                        |                   |
      |      v                                         |                        |                   |
      |  17. Java: 获得结果,继续执行                  |                        |                   |
      |                                                |                                                |

关键节点解读

  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 真正的"黑盒"内部。

相关推荐
范特西林2 小时前
破冰之旅:为什么 Android 选择了 Binder?
android
奔跑中的蜗牛6663 小时前
一次播放器架构升级:Android 直播间 ANR 下降 60%
android
测试工坊5 小时前
Android 视频播放卡顿检测——帧率之外的第二战场
android
Kapaseker7 小时前
一杯美式深入理解 data class
android·kotlin
鹏多多7 小时前
Flutter使用screenshot进行截屏和截长图以及分享保存的全流程指南
android·前端·flutter
Carson带你学Android7 小时前
OpenClaw移动端要来了?Android官宣AI原生支持App Functions
android
黄林晴7 小时前
Android 删了 XML 预览,现在你必须学 Compose 了
android
三少爷的鞋7 小时前
Android 面试系列 | 内存泄露:从"手动配对"到"架构自愈"
android
恋猫de小郭8 小时前
什么 AI 写 Android 最好用?官方做了一个基准测试排名
android·前端·flutter