用 “快递站” 故事读懂 Binder 驱动:公开 / 匿名 Binder 打开全解析

如果把 Android 系统比作一个 "城市",进程就是 "公司",进程间通信(IPC)就是 "公司间寄快递"。但 Android 里的 "快递" 有个规矩 ------公司不能直接串门送件 ,必须通过一个 "官方快递站" 中转,这个快递站就是 Binder 驱动

而 "公开 Binder" 和 "匿名 Binder",就是快递站里两种不同的 "快递柜":

  • 公开 Binder:类似 "社区公共快递柜",有固定编号(比如 0 号柜),全城市都知道它是 "服务管理柜",谁都能查、能用;
  • 匿名 Binder:类似 "临时专属柜",没有公开编号,只有寄件方和收件方知道密码(handle),用完就可能回收。

下面用 "快递站运营日记" 结合源码,带你吃透这两种柜子的 "开户 + 使用" 过程,最后附时序图。

一、先搞懂快递站的 "基础规则"(Binder 核心结构)

在讲流程前,先认识快递站里的 3 个核心 "道具",后面全靠它们:

结构名(源码里的定义) 类比成快递站的东西 作用
binder_proc 公司在快递站的 "账户" 每个进程(公司)打开快递站(open /dev/binder)时,驱动会创建一个 binder_proc,记录这个进程的信息(比如持有的快递柜列表、待处理的快递等)
binder_node 物理快递柜(实体) 真正存 "快递" 的地方,每个 Binder 实体对应一个 binder_node,不管是公开还是匿名 Binder,本质都是这个结构
binder_handle 快递柜的 "取件码" 进程操作 binder_node 时,不能直接用 node 地址(驱动不暴露内核地址),而是用 handle(一个整数,比如 0、123),每个进程的 handle 只对自己有效

二、公开 Binder:全城市都知道的 "0 号公共柜"(以 ServiceManager 为例)

公开 Binder 的核心是 "有固定身份、全进程可查",最典型的就是 ServiceManager(SM) ------ 它是 Android 里的 "服务大管家",所有系统服务(比如 ActivityManager、WindowManager)都要向它注册,其他进程要调用服务,必须先找 SM 要 "服务地址"。

而 SM 能当 "大管家",关键是它占用了 Binder 驱动的 "0 号公开柜"。下面看这个柜子是怎么 "上架" 和 "被使用" 的。

故事线:快递站开业,SM 抢注 "0 号柜"

1. 第一步:快递站开门(驱动初始化)

系统启动时,内核会加载 Binder 驱动(类似 "快递站装修开业"),创建一个设备文件 /dev/binder------ 这是所有进程和快递站打交道的 "唯一入口"(不管是公开还是匿名 Binder,都走这个入口)。

2. 第二步:SM 来开 "账户"(open /dev/binder

SM 是 Android 早期启动的进程(由 init 进程启动),它启动后第一件事就是 "去快递站开账户",对应代码就是调用 open("/dev/binder", O_RDWR)

这时 Binder 驱动会执行 binder_open 函数(源码路径:kernel/drivers/android/binder.c),核心逻辑是:

c 复制代码
static int binder_open(struct inode *inode, struct file *filp) {
    // 1. 为当前进程(SM)创建一个"账户":binder_proc
    struct binder_proc *proc = kzalloc(sizeof(*proc), GFP_KERNEL);
    if (!proc) return -ENOMEM;

    // 2. 初始化账户信息:绑定进程 PID、初始化待处理队列等
    proc->pid = current->tgid; // current 是当前进程的内核结构体
    INIT_LIST_HEAD(&proc->todo); // 待处理的"快递"队列
    init_waitqueue_head(&proc->wait); // 等待队列(没快递时进程休眠)

    // 3. 把"账户"挂到文件描述符上(后续操作都通过这个 fd 找 proc)
    filp->private_data = proc;

    return 0;
}

解读 :SM 调用 open 后,就在快递站有了专属 "账户"(binder_proc),拿到了一个文件描述符(fd),后续所有操作(比如抢 0 号柜)都靠这个 fd 跟快递站沟通。

3. 第三步:SM 抢注 "0 号公开柜"(ioctl(BINDER_SET_CONTEXT_MGR)

有了账户后,SM 要告诉快递站:"我要当大管家,把 0 号柜分给我!",对应代码是调用 ioctl(fd, BINDER_SET_CONTEXT_MGR, 0)

驱动会执行 binder_ioctl 函数(核心处理逻辑),针对 BINDER_SET_CONTEXT_MGR 的分支:

c 复制代码
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    struct binder_proc *proc = filp->private_data; // 拿到 SM 的"账户"
    void __user *ubuf = (void __user *)arg;

    switch (cmd) {
        case BINDER_SET_CONTEXT_MGR: {
            // 1. 检查:只有没被抢注过,才能设置(保证 0 号柜唯一)
            if (binder_context_mgr_node != NULL) {
                return -EBUSY; // 已经有人占了,返回"忙"
            }

            // 2. 为 SM 创建"物理快递柜":binder_node
            struct binder_node *node = binder_node_alloc(proc);
            node->flags = BINDER_NODE_FLAG_CONTEXT_MGR; // 标记为"大管家柜"

            // 3. 全局登记:把这个 node 设为"公开大管家柜"
            binder_context_mgr_node = node;

            // 4. 给 SM 分配"取件码":0(固定!)
            struct binder_handle *handle = binder_handle_alloc(proc, node);
            handle->desc = 0; // desc 就是用户进程看到的 handle

            // 5. 把取件码 0 返回给 SM(用户进程)
            if (copy_to_user(ubuf, &handle->desc, sizeof(handle->desc))) {
                return -EFAULT;
            }
            break;
        }
        // 其他分支...
    }
    return 0;
}

关键结论

  • 0 号 handle 是 公开、唯一 的,只有 SM 能拿到;
  • 全系统的进程都知道:"要找 SM,就用 handle 0"。

4. 第四步:普通 APP 找 SM(获取 0 号 handle)

比如 "小程 APP" 要调用 "电话服务",第一步得先找 SM 问 "电话服务的 handle 是多少"。它不需要抢注,直接拿 0 号 handle 即可:

  1. 小程 APP 调用 open("/dev/binder", O_RDWR),驱动为它创建一个 binder_proc(自己的账户);
  2. 小程调用 ioctl(fd, BINDER_GET_CONTEXT_MGR, &handle)(实际是通过 BINDER_IOCTL_TRANSACT 发送查询请求);
  3. 驱动看到 "要找大管家",直接返回 handle = 0(因为 binder_context_mgr_node 已经存在);
  4. 小程拿到 0 号 handle 后,就可以用它跟 SM 通信了(比如发送 "查询电话服务" 的请求)。

公开 Binder 核心特点

  • 有固定标识:以 SM 为例,用固定 handle 0 作为公开入口;
  • 全进程可见 :任何进程只要打开 /dev/binder,都能获取到这个公开 handle;
  • 用途:用于 "全局服务注册 / 查询"(比如系统服务、第三方 SDK 服务)。

三、匿名 Binder:只有两人知道的 "临时柜"

如果 "小程 APP" 要给 "小李 APP" 发私消息(比如跨进程传递一个 Service 实例),不想用公开的 0 号柜(没必要,也不安全),就需要 "匿名 Binder"------ 类似 "临时专属柜",只有小程和小李知道取件码。

故事线:小程给小李寄 "私货",临时开柜

1. 前提:小程和小李都有快递站账户

小程和小李都已经调用过 open("/dev/binder", O_RDWR),各自有自己的 binder_proc(账户)。

2. 第二步:小程申请 "临时柜"(驱动自动创建匿名 node)

小程要给小李发一个 Binder 对象(比如 MyService),它不需要主动调用 "创建匿名 Binder" 的接口,而是在发送数据时,驱动会自动创建匿名柜:

  1. 小程构造一个 "快递包裹"(flat_binder_object,Binder 通信的核心数据结构),里面指定 type = BINDER_TYPE_BINDER(表示 "这是一个要传递的 Binder 实体");
  2. 小程调用 ioctl(fd, BINDER_IOCTL_TRANSACT, &transact_data),把包裹发给小李(目标进程是小李的 PID);
  3. 驱动在 binder_transaction 函数(处理传递逻辑)中,发现包裹是 BINDER_TYPE_BINDER,开始创建匿名柜:
c 复制代码
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread,
                               struct binder_transaction_data *tr, int reply) {
    // ... 其他逻辑 ...

    // 检查包裹类型:如果是要传递 Binder 实体,创建匿名 node
    if (tr->data_type == BINDER_TYPE_BINDER) {
        struct flat_binder_object *obj = (struct flat_binder_object *)tr->data.ptr.buffer;

        // 1. 为小程(发送方)创建"匿名物理柜":binder_node
        struct binder_node *node = binder_node_alloc(proc);
        node->flags = 0; // 不标记为公开,就是匿名

        // 2. 给小程分配"取件码"(比如 123,只在小程的账户里有效)
        struct binder_handle *sender_handle = binder_handle_alloc(proc, node);
        sender_handle->desc = 123; // 小程的 handle 是 123

        // 3. 给小李(接收方)分配"对应的取件码"(比如 456,只在小李的账户里有效)
        struct binder_handle *receiver_handle = binder_handle_alloc(target_proc, node);
        receiver_handle->desc = 456; // 小李的 handle 是 456

        // 4. 把小李的取件码 456 放进包裹,发给小李
        obj->handle = receiver_handle->desc;
        obj->type = BINDER_TYPE_HANDLE; // 接收方看到的是"handle",不是实体
    }

    // ... 把包裹发给小李的"待处理队列" ...
}

解读

  • 匿名 Binder 没有 "公开名字",是驱动在 "传递实体" 时自动创建的;
  • 小程的 handle(123)和小李的 handle(456)不一样,但指向同一个物理柜(binder_node) ------ 就像同一把锁,小程有钥匙 A,小李有钥匙 B,都能开同一个柜子。

3. 第三步:小李用 "临时柜" 回复小程

小李收到包裹后,里面有 handle 456,它就知道:"这是小程给我开的临时柜,用 456 就能跟小程通信"。

之后小李要给小程发回复时,只需要在 ioctl 中指定 handle = 456,驱动会根据小李的 binder_proc 找到对应的 binder_node,再找到小程的 binder_proc,最终把消息发给小程。

4. 第四步:临时柜的回收

当小程和小李都不再使用这个匿名 Binder(比如进程退出、释放 handle),驱动会检测到 binder_node 没有任何进程引用了,就会自动回收这个 "物理柜"(释放 binder_node 内存)------ 类似 "快递柜超时没人用,自动清空回收"。

匿名 Binder 核心特点

  • 无公开标识:没有固定名字和 handle,靠 "进程间传递" 共享;
  • 隐私性:只有参与通信的进程知道 handle(其他进程拿不到);
  • 用途 :用于 "进程间临时通信"(比如跨进程传递 ServiceActivity 间传递 Binder 对象)。

四、时序图:直观看流程

用 Mermaid 时序图,把上面的故事转化为 "技术步骤",小白能直接对照看。

1. 公开 Binder 时序图(SM 注册 + APP 获取 0 号 handle)

init进程Binder驱动普通APP进程SM进程init进程Binder驱动普通APP进程SM进程启动SM进程open("/dev/binder") // 开账户创建SM的binder_proc返回fd(账户凭证)ioctl(fd, BINDER_SET_CONTEXT_MGR)1. 创建binder_node(公开柜)2. 分配handle=03. 全局登记binder_context_mgr_node返回handle=0open("/dev/binder") // 开自己的账户创建APP的binder_proc返回fdioctl(fd, 查SM的handle)读取binder_context_mgr_node返回handle=0

2. 匿名 Binder 时序图(小程→小李传递匿名 Binder)

小李APP小程APP小李APP(接收方)Binder驱动小程APP(发送方)小李APP小程APP小李APP(接收方)Binder驱动小程APP(发送方)open("/dev/binder")创建小程的binder_proc返回fd1open("/dev/binder")创建小李的binder_proc返回fd2ioctl(fd1, BINDER_IOCTL_TRANSACT, 包裹(type=BINDER_TYPE_BINDER))1. 创建匿名binder_node2. 给小程分配handle=1233. 给小李分配handle=4564. 包裹改type=BINDER_TYPE_HANDLE,填handle=456把包裹放进小李的todo队列返回"发送成功"ioctl(fd2, 读取todo队列)返回包裹(含handle=456)ioctl(fd2, BINDER_IOCTL_TRANSACT, 回复包裹(handle=456))通过handle=456找到binder_node,再找到小程的handle=123把回复放进小程的todo队列返回"回复成功"

五、一句话总结:公开 vs 匿名 Binder

对比维度 公开 Binder(如 SM) 匿名 Binder(如跨进程传递 Service)
标识方式 固定 handle(如 0)+ 公开名字 临时 handle(进程内唯一)+ 传递共享
创建时机 主动调用 BINDER_SET_CONTEXT_MGR 被动:驱动在传递 Binder 实体时自动创建
可见范围 全系统进程可见 仅参与通信的进程可见
核心用途 全局服务注册 / 查询 进程间临时通信
关键代码分支 binder_ioctlBINDER_SET_CONTEXT_MGR binder_transaction → 处理 BINDER_TYPE_BINDER

记住:不管是公开还是匿名 Binder,都是通过 /dev/binder 这个 "唯一入口" 跟驱动交互,核心区别在于 "是否有公开标识" 和 "创建方式"------ 就像快递站的两种柜子,一个是全社区共用,一个是临时专属。

相关推荐
相与还5 小时前
【2D横版游戏开发】godot实现tileMap地图
android·游戏引擎·godot
游戏开发爱好者85 小时前
App 上架平台全解析,iOS 应用发布流程、苹果 App Store 审核步骤
android·ios·小程序·https·uni-app·iphone·webview
2501_916007475 小时前
iOS 上架 App 费用详解 苹果应用发布成本、App Store 上架收费标准、开发者账号与审核实战经验
android·ios·小程序·https·uni-app·iphone·webview
ndzj9814796735 小时前
Android target35适配之窗口边衬区变更
android
用户2018792831675 小时前
匿名Binder的奥秘之“特工潜伏行动”
android
潘潘潘5 小时前
Android JNI中Java&Kotlin与C语言的相互调用
android
用户095 小时前
Kotlin 将会成为跨平台开发的终极选择么?
android·面试·kotlin
Carson带你学Android3 天前
Android PC时代已到来?Chrome OS将和Android合并!
android·google·chrome os
牛蛙点点申请出战3 天前
仿微信语音 WaveView -- Compose 实现
android·前端