Post

Binder 全景解析:从入门到理解 Android IPC 核心机制

本文结合一个从 AOSP 源码中提取的 Binder 用户态实现项目,由浅入深地讲解 Android Binder IPC 的核心原理。所有代码均可在 Android 模拟器上实际运行。

本文结合一个从 AOSP 源码中提取的 Binder 用户态实现项目,由浅入深地讲解 Android Binder IPC 的核心原理。所有代码均可在 Android 模拟器上实际运行。

1. 为什么是 Binder?

Android 是一个多进程系统——每个 App 运行在独立的进程中,系统服务(如 ActivityManagerService、PackageManagerService)也各自独立。进程之间要通信,就需要 IPC(Inter-Process Communication)机制。

Linux 已有的 IPC 方案

机制特点问题
管道 (Pipe)单向,父子进程间不适合任意进程间通信
信号 (Signal)异步通知只能传递信号编号,无法传递复杂数据
共享内存 (Shared Memory)速度最快,零拷贝需要自己处理同步,没有安全机制
Socket通用,支持网络开销大,两次数据拷贝
消息队列 (Message Queue)可传递结构化数据两次拷贝,无法传递文件描述符

Binder 的优势

Binder 是 Android 专门设计的 IPC 机制,它解决了上述方案的痛点:

mindmap
  root((Binder 的三大优势))
    只需一次拷贝
      传统 IPC
        用户空间 → 内核 → 用户空间(2次)
      Binder
        用户空间 → 内核/接收方共享区(1次)
    内置安全机制
      驱动自动附加调用方 UID/PID
      调用身份无法伪造
    C/S 架构 + 面向对象
      天然支持 Client-Server 模型
      远程对象调用像本地方法

一次拷贝的秘密:mmap

传统 IPC 需要两次拷贝:发送方 → 内核缓冲区 → 接收方。Binder 通过 mmap() 让接收方的用户空间和内核空间共享同一块物理内存,数据从发送方拷贝到内核后,接收方可以直接读取,省去了第二次拷贝。

graph LR  
    subgraph "传统 IPC(2次拷贝)"  
        A1[发送方用户空间] -->|copy_from_user| B1[内核缓冲区]  
        B1 -->|copy_to_user| C1[接收方用户空间]  
    end  
    subgraph "Binder(1次拷贝)"  
        A2[发送方用户空间] -->|copy_from_user| B2[内核缓冲区]  
        B2 -.->|mmap 共享| C2[接收方用户空间]  
    end

在我们的项目代码中,binder_open() 就完成了这个 mmap 映射:

// binder.c - binder_open()  
struct binder_state *binder_open(const char* driver, size_t mapsize)  
{  
    struct binder_state *bs;

    bs = malloc(sizeof(*bs));
    // 1. 打开 Binder 驱动设备文件  
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);  
    // 2. 检查驱动版本  
    ioctl(bs->fd, BINDER_VERSION, &vers);  
    // 3. 关键!mmap 建立共享内存映射  
    //    PROT_READ: 用户空间只读(写入由驱动完成)  
    //    MAP_PRIVATE: 私有映射  
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

    return bs;
}  

注意 mmap 的参数:PROT_READ 表示用户空间只能读,写入操作由内核驱动完成。这就是为什么数据”自动”出现在接收方内存中的原因。


2. Binder 架构全景

Binder 通信涉及四个核心角色:

graph TB  
    subgraph "用户空间"  
        C[Client 进程<br/>如:App]  
        S[Server 进程<br/>如:SystemServer]  
        SM[ServiceManager<br/>服务注册中心]  
    end  
    subgraph "内核空间"  
        D[Binder 驱动<br/>/dev/binder]  
    end  
    C -->|"① 查询服务"| SM  
    SM -->|"② 返回 handle"| C  
    S -->|"③ 注册服务"| SM  
    C -->|"④ 发起调用"| D  
    D -->|"⑤ 转发请求"| S  
    S -->|"⑥ 返回结果"| D  
    D -->|"⑦ 返回给调用方"| C  

四大角色

角色职责对应项目文件
Binder 驱动内核模块,负责跨进程数据传输、线程管理、引用计数Linux 内核 /dev/binder
ServiceManager服务注册中心,handle 固定为 0service_manager.c
Server提供服务的进程,注册服务并等待请求test_server.c
Client使用服务的进程,查找服务并发起调用test_client.c

一个形象的比喻:

  • Binder 驱动 = 电话交换机(负责接通电话)
  • ServiceManager = 114 查号台(告诉你号码)
  • Server = 商家(在查号台登记电话号码,等待来电)
  • Client = 顾客(先查号,再打电话)

3. Binder 驱动:一切的基础

Binder 驱动是一个 Linux 字符设备(/dev/binder),它不对应任何硬件,而是一个纯软件实现的内核模块。所有 Binder 通信都通过 ioctl() 系统调用与驱动交互。

3.1 驱动提供的核心 ioctl 命令

ioctl 命令用途使用场景
BINDER_VERSION获取驱动协议版本binder_open() 时检查兼容性
BINDER_SET_CONTEXT_MGR注册为 context managerServiceManager 启动时
BINDER_WRITE_READ读写数据(最核心的命令)所有通信都通过它

3.2 BINDER_WRITE_READ:通信的核心

几乎所有 Binder 操作最终都归结为一个 ioctl(fd, BINDER_WRITE_READ, &bwr) 调用。binder_write_read 结构体同时携带”要写入的数据”和”要读取的缓冲区”:

// Linux 内核头文件中的定义  
struct binder_write_read {  
    binder_size_t write_size;      // 要写入的数据大小  
    binder_size_t write_consumed;  // 驱动实际消费了多少  
    binder_uintptr_t write_buffer; // 写入数据的指针  
  
    binder_size_t read_size;       // 读取缓冲区大小  
    binder_size_t read_consumed;   // 驱动实际写入了多少  
    binder_uintptr_t read_buffer;  // 读取缓冲区的指针  
};  

写入缓冲区中的数据以 BC_*(Binder Command)命令开头,读取缓冲区中返回的数据以 BR_*(Binder Return)命令开头:

sequenceDiagram  
    participant U as 用户空间进程  
    participant K as Binder 驱动(内核)  
  
    U->>K: ioctl(BINDER_WRITE_READ)<br/>write_buffer: [BC_TRANSACTION, data...]
    Note over K: 驱动处理命令<br/>将数据转发给目标进程  
    K->>U: read_buffer: [BR_TRANSACTION_COMPLETE]  
    Note over U: 继续等待回复...  
    U->>K: ioctl(BINDER_WRITE_READ)<br/>write_size=0, 只读取  
    K->>U: read_buffer: [BR_REPLY, reply_data...]

3.3 BC/BR 命令协议

BC_*(用户空间 → 驱动):

命令含义
BC_TRANSACTION发起一次事务(Client → Server)
BC_REPLY回复一次事务(Server → Client)
BC_ENTER_LOOPER通知驱动:当前线程进入循环,可以接收请求
BC_ACQUIRE / BC_RELEASE增加/减少 Binder 引用计数
BC_FREE_BUFFER释放驱动分配的数据缓冲区
BC_REQUEST_DEATH_NOTIFICATION注册死亡通知

BR_*(驱动 → 用户空间):

命令含义
BR_TRANSACTION收到一个事务请求(Server 端收到)
BR_REPLY收到事务回复(Client 端收到)
BR_TRANSACTION_COMPLETE事务已被驱动接收
BR_DEAD_BINDER对端进程已死亡
BR_FAILED_REPLY事务失败
BR_NOOP空操作

在项目代码中,binder_parse() 函数就是解析这些 BR_* 命令的核心逻辑:

// binder.c - binder_parse()(简化版)  
int binder_parse(struct binder_state *bs, struct binder_io *bio,  
                 uintptr_t ptr, size_t size, binder_handler func)
{  
    uintptr_t end = ptr + size;  

    while (ptr < end) {
        uint32_t cmd = *(uint32_t *)ptr;  // 读取命令码
        ptr += sizeof(uint32_t);  

        switch (cmd) {
        case BR_NOOP:
            break;

        case BR_TRANSACTION_COMPLETE:
            break;

        case BR_TRANSACTION: {  // Server 端收到请求
            struct binder_transaction_data_secctx txn;

            memcpy(&txn.transaction_data, (void *)ptr, sizeof(...));

            if (func) {
                struct binder_io msg, reply;

                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, &txn.transaction_data);
                // 调用业务处理函数  
                res = func(bs, &txn, &msg, &reply);  
                // 发送回复  
                binder_send_reply(bs, &reply, ...);
            }
            break;
        }

        case BR_REPLY: {  // Client 端收到回复
            struct binder_transaction_data *txn = (void *)ptr;

            if (bio) {
                bio_init_from_txn(bio, txn);  // 将回复数据填入 bio
            }
            break;
        }

        case BR_DEAD_BINDER: {  // 对端死亡通知
            struct binder_death *death = ...;

            death->func(bs, death->ptr);
            break;
        }

        case BR_FAILED_REPLY:
        case BR_DEAD_REPLY:
            return -1;  // 通信失败
        }
    }

    return r;
}  

这个函数是整个用户态 Binder 库的”大脑”——所有从驱动返回的数据都经过它解析和分发。


4. ServiceManager:服务的”电话簿”

ServiceManager 是 Android 系统中第一个启动的 Binder 服务,它的 handle 固定为 0。所有其他服务都需要先向它注册,Client 也需要通过它来查找服务。

4.1 ServiceManager 的启动流程

flowchart TD  
    A[main] --> B["binder_open(driver, BINDER_VM_SIZE)"]
    B --> C{"打开成功?"}  
    C -->|否| D[退出]  
    C -->|是| E["binder_become_context_manager(bs)"]  
    E --> F{"注册成功?"}  
    F -->|否| G["打印错误,退出"]  
    F -->|是| H["binder_loop(bs, svcmgr_handler)"]  
    H --> I[无限循环等待请求]  
    I --> J{"收到 BR_TRANSACTION"}  
    J --> K["svcmgr_handler() 处理请求"]  
    K --> L{"请求类型?"}  
    L -->|SVC_MGR_ADD_SERVICE| M["do_add_service()<br/>注册服务"]  
    L -->|SVC_MGR_CHECK_SERVICE| N["do_find_service()<br/>查找服务"]  
    L -->|SVC_MGR_LIST_SERVICES| O["返回服务列表"]  
    M --> I  
    N --> I  
    O --> I

对应项目代码 service_manager.c 的 main 函数:

// service_manager.c - main()  
int main(int argc, char **argv)  
{  
    struct binder_state *bs;
    char *driver = (argc > 1) ? argv[1] : "/dev/binder";

    // 1. 打开 Binder 驱动  
    bs = binder_open(driver, BINDER_VM_SIZE);  
    // 2. 注册成为 context manager(handle = 0)  
    if (binder_become_context_manager(bs)) {
        fprintf(stdout, "cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    // 3. 进入循环,等待并处理请求  
    binder_loop(bs, svcmgr_handler);
    return 0;
}  

4.2 服务注册:do_add_service()

当 Server 进程调用 svcmgr_publish() 注册服务时,ServiceManager 的 svcmgr_handler 会收到 SVC_MGR_ADD_SERVICE 请求,然后调用 do_add_service() 将服务信息保存到链表中:

// service_manager.c - 服务信息结构  
struct svcinfo {  
    struct svcinfo *next;         // 链表指针
    uint32_t handle;         // Binder handle(驱动分配的引用)  
    struct binder_death death;
    int allow_isolated;
    uint32_t dumpsys_priority;
    size_t len;              // 服务名长度
    uint16_t name[0];        // 服务名(变长数组)  
};  
  
// 全局服务链表  
struct svcinfo *svclist = NULL;  

服务注册的核心逻辑:

// service_manager.c - do_add_service()(简化)  
int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len,  
                   uint32_t handle, uid_t uid, int allow_isolated,
                   uint32_t dumpsys_priority, pid_t spid)
{  
    struct svcinfo *si;  

    // 1. 检查服务名长度  
    if (len > 127) {
        return -1;
    }

    // 2. 检查是否已存在同名服务  
    si = find_svc(s, len);
    if (si) {
        if (si->handle) {
            // 释放旧的 handle
            svcinfo_death(bs, si);
        }
        si->handle = handle;  // 更新 handle
    } else {
        // 3. 创建新的服务记录
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';  
        // 4. 插入链表头部  
        si->next = svclist;
        svclist = si;
    }

    // 5. 增加 handle 的引用计数 & 注册死亡通知  
    binder_acquire(bs, handle);
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}  

4.3 服务查找:do_find_service()

Client 通过 SVC_MGR_CHECK_SERVICE 查找服务时,ServiceManager 遍历链表找到对应的 handle 返回:

// service_manager.c - do_find_service()  
uint32_t do_find_service(const uint16_t *s, size_t len,  
                         uid_t uid, pid_t spid, const char *sid)
{  
    struct svcinfo *si = find_svc(s, len);  

    if (!si || !si->handle) {
        return 0;  // 服务不存在
    }  

    // 权限检查(demo 中已禁用 SELinux)  
    if (!si->allow_isolated) {
        // ... 检查调用方权限
    }  

    return si->handle;  // 返回服务的 handle
}  

什么是 handle? handle 是 Binder 驱动为每个 Binder 引用分配的整数标识。Client 拿到 handle 后,就可以通过 binder_call(bs, &msg, &reply, handle, code) 向对应的 Server 发起调用。handle 类似于文件描述符——它是进程本地的,不同进程中同一个服务的 handle 值可能不同。


5. 一次完整的 Binder 通信

现在让我们用项目中的 test_server.c 和 test_client.c 来追踪一次完整的 Binder IPC 调用。

5.1 全景流程图

sequenceDiagram  
    participant SM as ServiceManager<br/>(handle=0)
    participant D as Binder 驱动<br/>(/dev/binder)  
    participant S as Server<br/>(test_server)
    participant C as Client<br/>(test_client)  
    Note over SM: 启动,注册为 context manager
    SM->>D: binder_become_context_manager()
    SM->>D: binder_loop() 等待请求  
  
    Note over S: 启动,注册 "hello" 服务  
    S->>D: binder_open()
    S->>D: binder_call(target=0, code=ADD_SERVICE)<br/>携带服务名 "hello"
    D->>SM: BR_TRANSACTION (ADD_SERVICE)
    SM->>SM: do_add_service("hello", handle)
    SM->>D: BC_REPLY (status=0)
    D->>S: BR_REPLY (成功)  
    S->>D: binder_loop() 等待请求  
  
    Note over C: 启动,查找并调用服务  
    C->>D: binder_open()
    C->>D: binder_call(target=0, code=CHECK_SERVICE)<br/>查找 "hello"
    D->>SM: BR_TRANSACTION (CHECK_SERVICE)
    SM->>SM: do_find_service("hello")
    SM->>D: BC_REPLY (handle=1)
    D->>C: BR_REPLY (handle=1)  
    Note over C: 拿到 handle,发起业务调用  
    C->>D: binder_call(target=1, code=666)<br/>SAY_HELLO
    D->>S: BR_TRANSACTION (code=666)
    S->>S: test_handler() → say_hello()
    S->>D: BC_REPLY ("Flavio")
    D->>C: BR_REPLY ("Flavio")
    Note over C: 打印 "reply from server Flavio"

5.2 Server 端:注册服务并等待

// test_server.c - main()  
int main(int argc, char **argv)  
{  
    struct binder_state *bs;
    uint32_t svcmgr = BINDER_SERVICE_MANAGER;  // handle = 0
    const char *driver = (argc > 1) ? argv[1] : "/dev/binder";

    // 1. 打开 Binder 驱动  
    bs = binder_open(driver, BINDER_VM_SIZE);  
    // 2. 向 ServiceManager 注册 "hello" 服务  
    //    target=0 表示发给 ServiceManager
    //    (void*)123 是一个 cookie,用于标识本地对象
    ret = svcmgr_publish(bs, BINDER_SERVICE_MANAGER, "hello", (void *) 123);  
    // 3. 进入循环,等待 Client 的请求  
    binder_loop(bs, test_handler);
}  

svcmgr_publish() 的内部实现展示了如何构造一次 Binder 调用:

// test_server.c - svcmgr_publish()  
int svcmgr_publish(struct binder_state *bs, uint32_t target,  
                   const char *name, void *ptr)
{  
    unsigned iodata[512 / 4];
    struct binder_io msg, reply;

    // 1. 初始化消息缓冲区  
    bio_init(&msg, iodata, sizeof(iodata), 4);  
    // 2. 按协议填充数据  
    bio_put_uint32(&msg, 0);                // strict mode header
    bio_put_string16_x(&msg, SVC_MGR_NAME); // 接口名
    bio_put_string16_x(&msg, name);         // 服务名 "hello"
    bio_put_obj(&msg, ptr);                 // Binder 对象(本地引用)
  
    // 3. 发起 Binder 调用  
    //    target=0 → ServiceManager
    //    code=SVC_MGR_ADD_SERVICE → 注册服务
    if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)) {
        return -1;
    }

    status = bio_get_uint32(&reply);  // 获取返回状态  
    binder_done(bs, &msg, &reply);    // 释放缓冲区
    return status;
}  

Server 收到请求后的处理逻辑:

// test_server.c - test_handler()  
int test_handler(struct binder_state *bs,  
                 struct binder_transaction_data_secctx *txn_secctx,
                 struct binder_io *msg,
                 struct binder_io *reply)
{  
    struct binder_transaction_data *txn = &txn_secctx->transaction_data;  

    fprintf(stdout, "[server] handler code=%d on tid=%d\n", txn->code, (int)gettid());  

    switch (txn->code) {
    case TEST_SERVER_SAY_HELLO:  // code = 666
        ret = say_hello();       // 执行业务逻辑
        bio_put_string16_x(reply, ret);
        break;

    case TEST_SERVER_SLOW_CALL:  // code = 667,sleep 5s 模拟慢处理
        ret = slow_call();
        bio_put_string16_x(reply, ret);
        break;

    case TEST_SERVER_LARGE_REPLY:  // code = 668,构造大回复数据
        // 填充大量数据,模拟 TransactionTooLargeException
        for (i = 0; i < 20; i++) {
            bio_put_string16_x(reply, "PADDING_DATA_...");
        }
        break;

    default:
        return -1;
    }

    bio_put_uint32(reply, 0);  // 状态码  
    return 0;
}  

5.3 Client 端:查找服务并调用

// test_client.c - main()  
int main(int argc, char **argv)  
{  
    struct binder_state *bs;
    uint32_t svcmgr = BINDER_SERVICE_MANAGER;
    const char *driver = (argc > 1) ? argv[1] : "/dev/binder";
    const char *mode = (argc > 2) ? argv[2] : "hello";

    // 1. 打开 Binder 驱动(使用与 AOSP 一致的 mmap 大小)  
    bs = binder_open(driver, BINDER_VM_SIZE);  
    // 2. 从 ServiceManager 查找 "hello" 服务  
    handle = svcmgr_lookup(bs, svcmgr, "hello");  
    // 3. 根据模式执行不同测试  
    if (strcmp(mode, "hello") == 0) {
        say_hello(bs, handle);  // 正常调用
    } else if (strcmp(mode, "flood") == 0) {
        test_flood(driver, handle, 20);  // 并发慢请求,模拟 Binder 风暴
    } else if (strcmp(mode, "large") == 0) {
        test_large(bs, handle);  // 超大数据,模拟 TransactionTooLargeException
    }
}  

svcmgr_lookup() 查找服务的过程:

// test_client.c - svcmgr_lookup()  
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target,  
                       const char *name)
{  
    unsigned iodata[512 / 4];
    struct binder_io msg, reply;

    bio_init(&msg, iodata, sizeof(iodata), 4);
    bio_put_uint32(&msg, 0);                // strict mode header
    bio_put_string16_x(&msg, SVC_MGR_NAME); // 接口名
    bio_put_string16_x(&msg, name);           // 要查找的服务名
  
    // 向 ServiceManager (target=0) 发起查询  
    binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE);  
    // 从回复中提取 handle
    handle = bio_get_ref(&reply);  
    if (handle) {
        binder_acquire(bs, handle);  // 增加引用计数
    }
  
    binder_done(bs, &msg, &reply);
    return handle;
}  

5.4 binder_call() 的内部实现

binder_call() 是发起 Binder 调用的核心函数,它将用户数据打包成 BC_TRANSACTION 命令发给驱动,然后等待 BR_REPLY:

// binder.c - binder_call()  
int binder_call(struct binder_state *bs,  
                struct binder_io *msg, struct binder_io *reply,
                uint32_t target, uint32_t code)
{  
    struct binder_write_read bwr;
    struct {
        uint32_t cmd;
        struct binder_transaction_data txn;
    } __attribute__((packed)) writebuf;

    // 1. 构造 BC_TRANSACTION 命令  
    writebuf.cmd = BC_TRANSACTION;
    writebuf.txn.target.handle = target;  // 目标 handle
    writebuf.txn.code = code;             // 调用码
    writebuf.txn.flags = 0;
    writebuf.txn.data_size = msg->data - msg->data0;
    writebuf.txn.offsets_size = ((char *)msg->offs) - ((char *)msg->offs0);
    writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;
    writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;

    bwr.write_size = sizeof(writebuf);
    bwr.write_buffer = (uintptr_t)&writebuf;

    // 2. 循环 ioctl 直到收到回复  
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_buffer = (uintptr_t)readbuf;

        // 发送命令 + 等待回复  
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);  
        // 解析驱动返回的数据  
        res = binder_parse(bs, reply, (uintptr_t)readbuf, bwr.read_consumed, 0);
        if (res == 0) {
            return 0;  // 收到 BR_REPLY,成功
        }
        if (res < 0) {
            goto fail;  // 出错
        }
        // res > 0: 还没收到回复,继续循环  
    }
}  

注意 binder_call 中的 for(;;) 循环——第一次 ioctl 可能只收到 BR_TRANSACTION_COMPLETE(表示驱动已接收),需要再次 ioctl 才能收到 BR_REPLY。


6. 数据序列化:binder_io 与 Parcel

跨进程通信不能直接传递指针,所有数据都需要序列化成字节流。在 C 层,项目使用 binder_io 结构体来完成这个工作;在 Android Framework 的 Java/C++ 层,对应的是 Parcel 类。

6.1 binder_io 结构

// binder.h  
struct binder_io {  
    char *data;            // 当前读写位置  
    binder_size_t *offs;   // 当前 offset 位置(用于 Binder 对象)  
    size_t data_avail;     // 剩余可用数据空间  
    size_t offs_avail;     // 剩余可用 offset 空间  
  
    char *data0;           // 数据缓冲区起始地址  
    binder_size_t *offs0;  // offset 缓冲区起始地址  
    uint32_t flags;        // 状态标志  
    uint32_t unused;
};  

6.2 数据布局

binder_io 的内存布局很巧妙——数据从前往后写,offset 从后往前写,共享同一块缓冲区:

flowchart TB
    subgraph BUF["iodata 缓冲区(示意)"]
        direction LR
        D0["data0"] --> U32["uint32"] --> S1["string16"] --> S2["string16"] --> OBJ["obj"]
        OBJ --> D["data<br/>当前写指针"]
        D --> GAP["空闲区"]
        GAP --> O["offs<br/>当前写指针"]
        O --> F3["off3"] --> F2["off2"] --> F1["off1"] --> F0["off0 / offs0"]
    end

    W1["data 区从左向右增长"] -.-> D
    W2["offset 区从右向左逼近 data 区"] -.-> O

初始化代码:

// binder.c - bio_init()  
void bio_init(struct binder_io *bio, void *data,  
              size_t maxdata, size_t maxoffs)
{  
    size_t n = maxoffs * sizeof(size_t);  // offset 区域大小  
  
    // offset 数组放在缓冲区末尾  
    bio->data = bio->data0 = (char *)data + n;
    bio->offs = bio->offs0 = data;
    bio->data_avail = maxdata - n;
    bio->offs_avail = maxoffs;
    bio->flags = 0;
}  

6.3 写入操作

// 写入 uint32
void bio_put_uint32(struct binder_io *bio, uint32_t n)
{
    uint32_t *ptr = bio_alloc(bio, sizeof(n));

    if (ptr) {
        *ptr = n;
    }
}  
  
// 写入字符串(UTF-16 编码,与 Android Java 层兼容)  
void bio_put_string16_x(struct binder_io *bio, const char *_str)
{  
    // 将 ASCII 字符串转为 UTF-16 写入  
    bio_put_uint32(bio, len);
    // 逐字符扩展为 uint16_t
}  
  
// 写入 Binder 对象(flat_binder_object)  
void bio_put_obj(struct binder_io *bio, void *ptr)
{  
    struct flat_binder_object *obj;

    obj = bio_alloc_obj(bio);  // 同时在 data 和 offs 中分配空间
    obj->hdr.type = BINDER_TYPE_BINDER;  // 本地 Binder 对象  
    obj->binder = (uintptr_t)ptr;
    obj->cookie = (uintptr_t)ptr;
}  

6.4 与 Android Parcel 的对应关系

binder_io (C 层)Parcel (Java/C++ 层)说明
bio_put_uint32()Parcel.writeInt()写入 32 位整数
bio_put_string16_x()Parcel.writeString()写入字符串
bio_put_obj()Parcel.writeStrongBinder()写入 Binder 对象引用
bio_get_uint32()Parcel.readInt()读取 32 位整数
bio_get_string16()Parcel.readString()读取字符串
bio_get_ref()Parcel.readStrongBinder()读取 Binder 引用

在 AOSP 中,Parcel 类(frameworks/native/libs/binder/Parcel.cpp)是 binder_io 的 C++ 升级版,功能更丰富,支持更多数据类型,但核心原理完全一致。


7. Java 层:AIDL 的 Stub/Proxy 模式

在 Android 开发中,我们通常通过 AIDL(Android Interface Definition Language)来定义跨进程接口。编译器会自动生成 Stub(Server 端)和 Proxy(Client 端)代码。本项目的 Java 层手动实现了这个模式,帮助你理解 AIDL 背后到底发生了什么。

7.1 整体架构

graph TB  
    subgraph "Client 进程"  
        CA[ClientMain.java] --> CP[HelloServiceProxy]
        CP -->|"实现"| IF1[IHelloService]  
        CP -->|"调用 native"| BN1[BinderNative.call]
    end  
    subgraph "JNI 层"  
        BN1 --> JNI[binder_jni.c]
        JNI --> BC[binder.c<br/>binder_call]
        BC2[binder.c<br/>binder_loop] --> JNI2[binder_jni.c]
        JNI2 --> BN2[BinderNative.loop]
    end  
    subgraph "Server 进程"  
        BN2 --> CS[HelloServiceStub]
        CS -->|"实现"| IF2[IHelloService]  
        CS --> SA[ServerMain.java]
    end  
    BC <-->|"ioctl"| DR[Binder 驱动]  
    BC2 <-->|"ioctl"| DR

7.2 接口定义

// IHelloService.java — 模拟 AIDL 生成的接口  
public interface IHelloService {
    int TRANSACTION_SAY_HELLO   = 666;  // 正常调用
    int TRANSACTION_SLOW_CALL   = 667;  // 慢处理(模拟线程池耗尽)  
    int TRANSACTION_LARGE_REPLY = 668;  // 大数据回复(模拟 TransactionTooLargeException)  
  
    String sayHello();  
    String slowCall();  
    String largeReply();  
}  

在真实 Android 开发中,你只需要写一个 .aidl 文件:

// IHelloService.aidl  
interface IHelloService {  
    String sayHello();
}  

编译器会自动生成 Stub 和 Proxy 类。我们的项目手动实现了这两个类。

7.3 Server 端:Stub

Stub 是 Server 端的基类,负责:

  1. 向 ServiceManager 注册服务
  2. 进入 Binder 循环
  3. 收到请求时,根据 transaction code 分发到具体方法
// HelloServiceStub.java(简化)  
public abstract class HelloServiceStub implements IHelloService {  
    // 注册服务并进入循环(阻塞)  
    public void registerAndServe(String driver, String serviceName) {  
        long bs = BinderNative.open(driver);  
  
        // 1. 向 ServiceManager 注册  
        BinderNative.publish(bs, serviceName, TRANSACTION_SAY_HELLO);  
  
        // 2. 进入 Binder 循环,收到请求时回调 onTransaction
        //    多线程模型下,不同请求可能在不同 Binder 线程上执行
        BinderNative.loop(bs, (code, data) -> {  
            switch (code) {  
                case TRANSACTION_SAY_HELLO:
                    return sayHello();
                case TRANSACTION_SLOW_CALL:
                    return slowCall();
                case TRANSACTION_LARGE_REPLY:
                    return largeReply();
                default:
                    return null;
            }
        });
    }
}  

Server 的入口:

// ServerMain.java  
public class ServerMain {
    public static void main(String[] args) {  
        String driver = (args.length > 0) ? args[0] : "/dev/binder";  
  
        HelloServiceStub stub = new HelloServiceStub() {
            @Override  
            public String sayHello() {  
                return "Hello from Java Server!";
            }  

            @Override  
            public String slowCall() {  
                // sleep 5s 模拟慢处理,占住 Binder 线程  
                Thread.sleep(5000);  
                return "slow_done";
            }  

            @Override  
            public String largeReply() {  
                // 构造大字符串,模拟 TransactionTooLargeException
                StringBuilder sb = new StringBuilder();  
                for (int i = 0; i < 5000; i++) {
                    sb.append("PADDING_DATA_");
                }
                return sb.toString();  
            }
        };  
        stub.registerAndServe(driver, "hello");
    }
}  

7.4 Client 端:Proxy

Proxy 是 Client 端的代理类,它把本地方法调用转换为 Binder IPC 调用:

// HelloServiceProxy.java  
public class HelloServiceProxy implements IHelloService {
    private final long bsPtr;
    private final int handle;

    public HelloServiceProxy(long bsPtr, String serviceName) {  
        this.bsPtr = bsPtr;  
        this.handle = BinderNative.lookup(bsPtr, serviceName);  
    }  

    @Override  
    public String sayHello() {  
        return BinderNative.call(bsPtr, handle, TRANSACTION_SAY_HELLO, null);
    }  

    @Override  
    public String slowCall() {  
        // 调用远程 slowCall,Server 端会 sleep 5s
        return BinderNative.call(bsPtr, handle, TRANSACTION_SLOW_CALL, null);
    }  

    @Override  
    public String largeReply() {  
        // 请求 Server 返回大数据  
        return BinderNative.call(bsPtr, handle, TRANSACTION_LARGE_REPLY, null);
    }
}  

Client 的入口:

// ClientMain.java  
public class ClientMain {
    public static void main(String[] args) {  
        String driver = (args.length > 0) ? args[0] : "/dev/binder";  
        String mode = (args.length > 1) ? args[1] : "hello";  
  
        HelloServiceProxy proxy = new HelloServiceProxy(driver, "hello");  
        switch (mode) {
            case "hello":
                // 正常调用
                String reply = proxy.sayHello();
                System.out.println("Reply: " + reply);
                break;
            case "flood":
                // 并发 N 个 slowCall,模拟 Binder 风暴
                testFlood(proxy, 20);
                break;
            case "large":
                // 请求大数据回复,模拟 TransactionTooLargeException
                testLarge(proxy);
                break;
        }
    }
}  

7.5 Stub/Proxy 模式的本质

graph LR  
    subgraph "Client 进程"  
        A[业务代码] -->|"proxy.sayHello()"| B[Proxy]  
        B -->|"序列化参数<br/>binder_call(handle, 666)"| C[Binder 驱动]  
    end  
    subgraph "Server 进程"  
        C -->|"反序列化参数<br/>BR_TRANSACTION(666)"| D[Stub]  
        D -->|"stub.sayHello()"| E[业务实现]  
        E -->|"返回结果"| D  
        D -->|"序列化结果<br/>BC_REPLY"| C  
    end  
    C -->|"反序列化结果<br/>BR_REPLY"| B  
    B -->|"return 结果"| A  

这就是 Binder 的”透明代理”魔法:

  • Client 调用 proxy.sayHello(),感觉像在调用本地方法
  • Proxy 内部将方法名(transaction code)和参数序列化,通过 Binder 驱动发送
  • Stub 收到请求后反序列化,调用真正的业务实现
  • 结果原路返回

整个过程对 Client 的业务代码完全透明——这就是为什么 Android 的跨进程调用用起来和本地调用几乎一样。


8. Binder 的资源限制与常见异常

理解了 Binder 通信原理后,我们来看实际开发中最容易踩的坑。Binder 并非无限制的通道,驱动层有明确的资源约束,超出限制就会抛出异常。

8.1 两个关键限制

在 AOSP 的 ProcessState 初始化时,有两个硬编码的限制。我们的项目在 binder.h 中定义了与 AOSP 一致的常量:

// binder.h — 与 AOSP ProcessState 一致的资源限制  
#define BINDER_VM_SIZE              ((1 * 1024 * 1024) - (4096 * 2))  /* 1MB - 8KB ≈ 1016KB */  
#define DEFAULT_MAX_BINDER_THREADS  15  /* +1 主线程 = 最多 16 个并发 Binder 线程 */
限制默认值含义
BINDER_VM_SIZE1MB - 8KB ≈ 1016KB每个进程 Binder mmap 的内存上限
DEFAULT_MAX_BINDER_THREADS15(+1主线程=16)最大并发 Binder 线程数

在 binder_open() 中,这两个限制都被应用:

// binder.c - binder_open()  
struct binder_state *binder_open(const char* driver, size_t mapsize)  
{  
    // ... open + version check ...  
    // 1. mmap 建立共享内存映射,mapsize 决定了内存上限  
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);  
    // 2. 设置 Binder 驱动允许的最大线程数,与 AOSP ProcessState 行为一致  
    uint32_t maxthreads = DEFAULT_MAX_BINDER_THREADS;
    ioctl(bs->fd, BINDER_SET_MAX_THREADS, &maxthreads);  
    return bs;
}  

调用方使用 BINDER_VM_SIZE 作为 mmap 大小,与真实 Android 系统一致:

// test_server.c / test_client.c / service_manager.c  
bs = binder_open(driver, BINDER_VM_SIZE);  // 1MB - 8KB,与 AOSP 一致  

这意味着一个进程中所有正在进行的 Binder 事务共享这 ~1MB 内存。同时驱动最多允许 16 个并发 Binder 线程(15 + 1 主线程)。

8.2 FAILED_TRANSACTION:不只是”数据太大”

当 Binder 驱动内存不足时,会返回 FAILED_TRANSACTION。但这里有个关键的误区:这个限制是累积的,不是单次的。

sequenceDiagram  
    participant T1 as 线程1  
    participant T2 as 线程2  
    participant T3 as 线程3  
    participant T4 as 线程4  
    participant D as Binder 驱动<br/>(内存池 ~1MB)  
    T1->>D: 传输 300KB ✅  
    Note over D: 已用 300KB
    T2->>D: 传输 300KB ✅  
    Note over D: 已用 600KB
    T3->>D: 传输 300KB ✅  
    Note over D: 已用 900KB
    T4->>D: 传输 150KB ❌  
    Note over D: 900+150 > 1016KB<br/>FAILED_TRANSACTION!
    Note over T4: 即使只传了 150KB<br/>也会失败!  

驱动内存是在 Server 端处理完请求、调用 BC_FREE_BUFFER 后才释放的。如果 Server 处理慢,多个并发请求的内存会持续累积。

为了提前发现问题,我们在 binder_call() 中加入了数据大小检查,模拟 AOSP 中 200KB 的判定阈值:

// binder.c - binder_call() 中的大小检查  
size_t data_size = msg->data - msg->data0;  
if (data_size > 200 * 1024) {  
    fprintf(stderr,
            "binder: WARNING: large transaction (%zu bytes), "
            "risk of TransactionTooLargeException\n",
            data_size);
}  

在我们的项目中,binder_send_reply() 和 binder_free_buffer() 就是释放内存的关键路径:

// binder.c - binder_send_reply()  
void binder_send_reply(struct binder_state *bs,  
                       struct binder_io *reply,
                       binder_uintptr_t buffer_to_free,
                       int status)
{  
    struct {
        uint32_t cmd_free;
        binder_uintptr_t buffer;
        uint32_t cmd_reply;
        struct binder_transaction_data txn;
    } __attribute__((packed)) data;

    // 先释放接收缓冲区,再发送回复  
    data.cmd_free = BC_FREE_BUFFER;  // ← 释放驱动内存
    data.buffer = buffer_to_free;
    data.cmd_reply = BC_REPLY;       // ← 发送回复
    // ...
    binder_write(bs, &data, sizeof(data));
}  

8.3 异常类型判定逻辑

AOSP 中 FAILED_TRANSACTION 会根据 Parcel 大小分成两种异常:

if (parcelSize > 200KB)  
    → TransactionTooLargeException   // "你这次传的数据太大了"  
else  
    → DeadObjectException            // "不是你的问题,可能是对端死了或内存被别人占满"  
flowchart TD  
    A[Binder 调用失败<br/>status = FAILED_TRANSACTION] --> B{"本次 Parcel 大小 > 200KB?"}  
    B -->|是| C["抛出 TransactionTooLargeException<br/>原因:单次数据过大"]  
    B -->|否| D["抛出 DeadObjectException<br/>原因:累积内存超限或对端死亡"]  
    C --> E["解决:减小传输数据<br/>拆分大对象、压缩图片等"]  
    D --> F["解决:排查并发、<br/>提高 Server 响应速度"]  

8.4 Binder 风暴(Binder Storm)

当大量 Binder 请求同时涌入时,会出现”Binder 风暴”:

  • 所有 Binder 线程被占满(默认 16 个)
  • 新请求在 waitForResponse 中阻塞
  • 如果发生在主线程 → ANR

结合我们项目代码理解:binder_call() 中的 for(;;) 循环就是在等待回复,如果 Server 端处理慢,Client 就会一直阻塞在这里:

// binder.c - binder_call()  
for (;;) {  
    // 这里会阻塞!直到驱动返回数据  
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    res = binder_parse(bs, reply, ...);
    if (res == 0) {
        return 0;  // 收到回复才返回
    }
}  

多线程 Binder 线程池

在 AOSP 中,收到 BR_SPAWN_LOOPER 时会创建新线程加入线程池。我们的项目实现了同样的机制:

// binder.c - binder_parse() 中的 BR_SPAWN_LOOPER 处理  
case BR_SPAWN_LOOPER: {  
    struct binder_thread_desc *desc = malloc(sizeof(*desc));

    desc->bs = bs;
    desc->func = func;
    pthread_create(&tid, NULL, binder_thread_routine, desc);
    break;
}  

新线程入口函数发送 BC_REGISTER_LOOPER 注册自己,然后进入与主线程相同的 ioctl 循环:

// binder.c - binder_thread_routine()  
static void *binder_thread_routine(void *arg)
{  
    // 向驱动注册:我是一个新的 looper 线程  
    readbuf[0] = BC_REGISTER_LOOPER;  // 区别于主线程的 BC_ENTER_LOOPER
    binder_write(bs, readbuf, sizeof(uint32_t));  

    for (;;) {
        ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
    }
}  

注意 BC_ENTER_LOOPER(主线程)和 BC_REGISTER_LOOPER(新线程)的区别——驱动用这个来区分”主动进入循环”和”被动创建的线程”。

实战验证:压力测试

项目提供了压力测试来验证这些限制:

# 并发 20 个慢请求(每个 sleep 5s),超过 Server 的 16 线程上限  
./run.sh stress  
# 或手动指定并发数  
./test_client /dev/binderfs/mybinder flood 20  
# 发送超大数据,触发 200KB 警告  
./test_client /dev/binderfs/mybinder large

预期观察到的现象:

  • flood 测试:前 16 个请求被 Server 的 16 个 Binder 线程处理(可以看到不同的 tid),后续请求阻塞等待,总耗时 > 5s
  • large 测试:binder_call() 打印 WARNING: large transaction 日志,如果超过 mmap 大小则返回 FAILED_TRANSACTION

8.5 实际开发中的应对策略

问题解决方案
单次数据过大减小 Intent/Bundle 数据量;大数据用 ContentProvider 或文件共享
累积内存超限Server 端提高处理速度,尽快释放 BC_FREE_BUFFER
Binder 风暴控制并发调用频率;非关键调用使用 FLAG_ONEWAY(异步)
主线程 ANR避免在主线程发起耗时 Binder 调用;使用异步回调
对端死亡注册 DeathRecipient,收到通知后重连

在我们的项目中,binder_link_to_death() 就是注册死亡通知的实现:

// binder.c  
void binder_link_to_death(struct binder_state *bs, uint32_t target,  
                           struct binder_death *death)
{  
    struct {
        uint32_t cmd;
        struct binder_handle_cookie payload;
    } __attribute__((packed)) data;

    data.cmd = BC_REQUEST_DEATH_NOTIFICATION;
    data.payload.handle = target;
    data.payload.cookie = (uintptr_t)death;
    binder_write(bs, &data, sizeof(data));
}  

当对端进程死亡时,驱动会发送 BR_DEAD_BINDER,binder_parse() 中会回调 death 函数:

// binder.c - binder_parse()  
case BR_DEAD_BINDER: {  
    struct binder_death *death =
        (struct binder_death *)(uintptr_t) * (binder_uintptr_t *)ptr;

    death->func(bs, death->ptr);  // 执行死亡回调
    break;
}  

ServiceManager 就是用这个机制来清理已死亡服务的注册信息的。


9. Binder 通信监控实战

知道了 Binder 会出什么问题,下一步就是如何发现问题。线上环境中 Binder 异常的堆栈往往不能直接指明根因,我们需要一套监控方案来捕获通信细节。

9.1 监控的切入点

回顾我们项目中的调用链:

Client 业务代码  
  → binder_call(bs, &msg, &reply, handle, code)  // 用户态库
    → ioctl(bs->fd, BINDER_WRITE_READ, &bwr)     // 系统调用
      → Binder 驱动处理  

在 AOSP 的 C++ 层,对应的调用链是:

Java 层 Proxy.transact()
  → android_os_BinderProxy_transact()            // JNI
    → BpBinder::transact(code, data, reply, flags)  // ← 最佳 Hook 点
      → IPCThreadState::transact()
        → IPCThreadState::writeTransactionData()
        → IPCThreadState::waitForResponse()

BpBinder::transact 是最佳的监控切入点,因为它同时持有:

  • code:调用的方法编号
  • data(Parcel):传输的数据,可以获取大小和接口名
  • flags:是否为 oneway 调用

9.2 四个监控目标

结合项目代码理解每个目标的原理:

目标 1:监控通信行为(频率 + 堆栈)

在我们的项目中,每次 binder_call() 就是一次 Binder 通信。通过 Hook BpBinder::transact,可以统计调用频率、记录堆栈:

// 概念示意:Hook 后的代理函数  
int32_t bpbinder_transact_hook(void *bpbinder,  
                               uint32_t code, const void *data,
                               void *reply, uint32_t flags)
{  
    // 记录调用时间、频率  
    log_binder_call(code, flags);  
    // 调用原函数  
    int32_t result = original_transact(bpbinder, code, data, reply, flags);  
    return result;
}  

目标 2:监控 Parcel 大小(防止 FAILED_TRANSACTION)

在我们的项目中,binder_io 的数据大小通过指针差计算:

// binder.c - binder_call() 中  
writebuf.txn.data_size = msg->data - msg->data0;  // ← 这就是 Parcel 大小  

在 AOSP 中,对应的是 Parcel::dataSize()。通过 dlsym 找到这个符号就能获取大小:

// 监控示意  
size_t parcel_size = parcel_dataSize(data);  // 通过符号调用  
if (parcel_size > WARNING_THRESHOLD) {  // 比如 500KB
    log_warning("Large Binder parcel: %zu bytes", parcel_size);
    capture_stacktrace();
}  

目标 3:获取调用的服务类名

Binder 通信中通过 InterfaceToken 标识服务接口。在我们的项目中,Client 发送数据时会写入接口名:

// test_client.c - svcmgr_lookup()  
bio_put_string16_x(&msg, SVC_MGR_NAME);  // "android.os.IServiceManager"  

在 AOSP 中,Parcel::writeInterfaceToken() 做同样的事。Hook 这个函数,就能建立 Parcel 指针 → 接口名的映射:

// 概念示意  
std::map<void*, std::string> parcel_interface_map;  
  
int32_t writeInterfaceToken_hook(void *parcel, const char16_t *interface,  
                                 size_t len)
{  
    // 记录:这个 Parcel 对应哪个接口  
    parcel_interface_map[parcel] = utf16_to_utf8(interface, len);  
    return original_writeInterfaceToken(parcel, interface, len);
}  

目标 4:解析具体方法名

在我们的项目中,code = 666 对应 TEST_SERVER_SAY_HELLO。在 AIDL 生成的代码中,每个方法都有一个 TRANSACTION_xxx 常量:

// AIDL 生成的 Stub 类中  
public static final int TRANSACTION_sayHello = 666;

通过反射读取 InterfaceName$Stub 类的静态常量,就能把 code 映射回方法名:

// 监控代码示意  
public static String resolveMethodName(String interfaceToken, int code) {  
    try {
        Class<?> stubClass = Class.forName(interfaceToken + "$Stub");
        for (Field field : stubClass.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())
                && field.getType() == int.class
                && field.getName().startsWith("TRANSACTION_")) {
                field.setAccessible(true);
                if ((int) field.get(null) == code) {
                    // TRANSACTION_sayHello → sayHello
                    return field.getName().substring("TRANSACTION_".length());
                }
            }
        }
    } catch (Exception e) {
        /* 类不存在或被混淆 */
    }
    return "unknown(code=" + code + ")";
}  

9.3 监控架构总览

flowchart LR  
    subgraph "Native Hook 层"  
        H1["Hook BpBinder::transact<br/>获取 code, Parcel, flags"]
        H2["Hook Parcel::writeInterfaceToken<br/>获取接口名"]  
        H3["Hook Parcel::dataSize<br/>获取数据大小"]  
    end  
    subgraph "数据聚合层"  
        M["Parcel 指针 → 接口名 Map"]
        H2 --> M
        H1 --> A["聚合:接口名 + 方法名 + 数据大小 + 频率"]  
        H3 --> A
        M --> A
    end  
    subgraph "告警 & 上报"  
        A --> W1["Parcel > 500KB → 告警"]  
        A --> W2["同一接口 QPS > 阈值 → 告警"]  
        A --> W3["定期上报统计数据"]  
    end

9.4 从项目代码理解监控原理

我们的项目虽然是 C 层的简化实现,但监控的核心思路完全一致。以 binder_call() 为例,如果我们要在项目中加入简单的监控,只需要在调用前后加入统计:

// 在 binder_call() 中加入监控(示意)  
int binder_call(struct binder_state *bs,  
                struct binder_io *msg, struct binder_io *reply,
                uint32_t target, uint32_t code)
{  
    // 监控:记录数据大小  
    size_t data_size = msg->data - msg->data0;

    fprintf(stderr, "[monitor] target=%d code=%d size=%zu\n",
            target, code, data_size);  
    if (data_size > 500 * 1024) {
        fprintf(stderr, "[monitor] WARNING: large parcel!\n");
    }
    // 原有逻辑...  
    writebuf.cmd = BC_TRANSACTION;
    // ...
}  

实际生产环境中,用 PLT/GOT Hook 或 Inline Hook(如 bhook、shadowhook)来实现,不需要修改源码,对 libbinder.so 中的符号进行运行时拦截即可。


10. 动手实战:运行 Demo 项目

10.1 环境准备

# 需要的工具  
# - Android SDK(含 platform-tools、build-tools)  
# - Android NDK 25.x  
# - JDK 8+  
# - CMake 3.10+  
  
# 设置环境变量  
export ANDROID_HOME=~/android/sdk  
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393  

10.2 创建模拟器

# 必须使用 google_apis 镜像(非 playstore),因为需要 root 权限  
sdkmanager "system-images;android-35;google_apis;arm64-v8a"
avdmanager create avd -n binder_test \
    -k "system-images;android-35;google_apis;arm64-v8a" \
    -d pixel_6

10.3 编译

cd android
./build.sh  

编译过程:

  1. NDK 交叉编译 C 原生二进制(service_manager, test_server, test_client, binder_ctl)
  2. NDK 编译 JNI 共享库(libbinder_jni.so)
  3. javac 编译 Java 源码
  4. d8 转换为 Android DEX 格式

10.4 运行

# 启动模拟器  
$ANDROID_HOME/emulator/emulator -avd binder_test &  
adb wait-for-device  
# C 原生测试  
./run.sh native  
# Java JNI 测试  
./run.sh java  
# 压力测试(并发慢请求 + 超大数据)  
./run.sh stress        # C 原生压力测试  
./run.sh stress-java   # Java 压力测试  

10.5 手动分终端调试

如果想分别观察每个进程的输出,可以打开三个终端:

REMOTE=/data/local/tmp/binder  
BDEV=/dev/binderfs/mybinder  
  
# 先获取 root 并创建自定义 binder 设备  
adb root
adb shell $REMOTE/binder_ctl mybinder  
# 终端 1:启动 ServiceManager
adb shell $REMOTE/service_manager $BDEV  
# 终端 2:启动 Server
adb shell $REMOTE/test_server $BDEV  
# 终端 3:启动 Client(支持 hello / flood / large 模式)  
adb shell $REMOTE/test_client $BDEV hello
adb shell $REMOTE/test_client $BDEV flood 20   # 20 个并发慢请求  
adb shell $REMOTE/test_client $BDEV large       # 超大数据测试  

预期输出(hello 模式):

# ServiceManager 终端  
result: 0  
binder: opened /dev/binderfs/mybinder, mmap size=1040384, max threads=15  
service manager start loop  
  
# Server 终端  
binder: opened /dev/binderfs/mybinder, mmap size=1040384, max threads=15  
[server] handler code=666 on tid=xxxx  
[server] say_hello on tid=xxxx  
  
# Client 终端  
=== Binder Test Client ===  
driver: /dev/binderfs/mybinder, mode: hello  
found service 'hello' handle=1  
[client] reply from server: Flavio    ← 成功收到 Server 的回复!  

预期输出(flood 模式,20 个并发):

=== FLOOD TEST: 20 concurrent SLOW_CALL requests ===  
Server has max 16 binder threads.  
Each SLOW_CALL sleeps 5s, holding driver memory.  
  
[flood-0] calling SLOW_CALL on tid=xxxx...  
[flood-1] calling SLOW_CALL on tid=xxxx...  
...  
# 前 16 个请求被 Server 的 16 个 Binder 线程处理(不同 tid)  
# 后 4 个请求阻塞等待,直到前面的请求完成释放线程  
[flood-0] reply: slow_done  
...  
=== FLOOD TEST done ===  

为什么要创建自定义 binder 设备? 系统自带的 ServiceManager 已经占用了 /dev/binder,我们通过 binder_ctl 在 binderfs 中创建独立的设备 /dev/binderfs/mybinder,这样我们的三个进程可以在自己的”小世界”里通信,不会干扰系统。


11. 总结与进阶

11.2 本项目代码与 AOSP 的对应关系

本项目文件AOSP 对应位置说明
binder.c/hframeworks/native/cmds/servicemanager/binder.c用户态 Binder 库(C 版本)
service_manager.cframeworks/native/cmds/servicemanager/service_manager.c旧版 ServiceManager(Android 10 前)
test_server.c / test_client.cframeworks/native/cmds/servicemanager/bctest.c测试工具
Java Stub/ProxyAIDL 编译器自动生成out/target/.../IXxxService.java
—frameworks/native/libs/binder/C++ 版 Binder 库(BpBinder/BBinder/IPCThreadState)
—frameworks/base/core/java/android/os/Binder.javaJava 层 Binder 基类

注意: Android 11+ 的 ServiceManager 已经用 C++ 重写(ServiceManager.cpp),使用 AIDL 接口而非手写的 C 代码。但底层原理完全一致。

11.3 进阶阅读方向

  1. Binder 驱动源码:drivers/android/binder.c(Linux 内核),理解 binder_thread_write / binder_thread_read 的实现
  2. C++ Binder 库:frameworks/native/libs/binder/,理解 BpBinder(Proxy)、BBinder(Stub)、IPCThreadState(线程级 Binder 状态)
  3. Binder 线程池:Server 端如何管理多个 Binder 线程来并发处理请求(本项目已实现 BR_SPAWN_LOOPER → pthread_create 的多线程模型)
  4. Binder 异常处理:DeadObjectException、TransactionTooLargeException 等
  5. Binder 通信监控:systrace、binder_transaction tracepoint

本文所有代码均来自项目仓库,可在 Android 模拟器上实际编译运行。建议读者在阅读本文的同时,动手运行 Demo,通过修改代码来加深理解。比如:

  • 修改 test_server.c 中 say_hello() 的返回值,观察 Client 的输出变化
  • 运行 ./run.sh stress 观察多线程 Binder 线程池的行为,以及线程池耗尽时的现象
  • 调整 test_client.c 中 flood 的并发数(如 ./test_client $BDEV flood 30),观察超过 16 线程上限后的阻塞行为
  • 在 binder.c 中打开 #define TRACE 1,观察完整的 BC/BR 命令交互日志
  • 尝试注册多个服务,理解 ServiceManager 的服务管理机制