用 “快递站” 故事读懂 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 这个 "唯一入口" 跟驱动交互,核心区别在于 "是否有公开标识" 和 "创建方式"------ 就像快递站的两种柜子,一个是全社区共用,一个是临时专属。

相关推荐
阿巴斯甜20 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker21 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android