3、Binder通信机制(上):Binder原理、驱动层实现、ServiceManager启动流程

各位同学,今天我们来啃一块硬骨头——Binder。说实话,Binder 是 Android 里最绕、也最核心的一块。我当年刚入行时,被 Binder 的驱动层代码折磨得够呛。但搞懂了它,你再看整个 Android 系统,就像开了上帝视角。

咱们分三块来讲:先搞清楚 Binder 到底是个什么玩意儿,再深入驱动层看看它怎么干活,最后把 ServiceManager 的启动流程捋一遍。嗯,开始吧。

3.1 Binder 到底是什么?

说白了,Binder 就是 Android 里用来做进程间通信(IPC)的一套机制。你想想看,Android 里每个 App 跑在自己的进程里,但它们要互相调用服务啊,比如你要用系统的剪贴板、通知栏、定位服务,这些都在系统进程里。跨进程通信怎么搞?Binder 就是答案。

我个人习惯把 Binder 理解成「远程方法调用」的 Android 版。客户端进程想调用服务端进程里的某个方法,Binder 帮你把参数打包、跨进程传过去、再解包、调用方法、把结果传回来。整个过程对开发者来说几乎是透明的。

核心要点:Binder 不是普通的 IPC,它结合了驱动层、用户空间库、以及 Java 框架层,是一整套通信体系。它比 Linux 传统的 Socket、管道、共享内存更适合移动端场景——性能好、安全性高、且天然支持面向对象调用。

我记得有一次排查一个系统服务卡死的问题,最后发现是 Binder 线程池耗尽导致的。从那以后,我对 Binder 的线程模型就格外敏感。后面我们会专门讲这个。

3.2 Binder 驱动层实现

Binder 驱动是 Linux 内核里的一个虚拟字符设备驱动,路径是 /dev/binder。它不操作任何物理硬件,纯粹是为了进程间通信而生的。你想想看,它得处理数据拷贝、线程管理、引用计数、死亡通知……活还挺杂的。

3.2.1 驱动核心数据结构

驱动层有几个关键结构体,我列个表给你看:

结构体 作用
binder_proc 代表一个使用 Binder 的进程,每个进程一个
binder_thread 代表进程里的一个线程,用于处理 Binder 请求
binder_node 代表一个 Binder 实体(服务端暴露出来的对象)
binder_ref 代表一个 Binder 引用(客户端持有的句柄)
binder_transaction 代表一次跨进程事务(请求或响应)

这些结构体通过链表和红黑树组织在一起。驱动里最核心的操作就是 binder_ioctl,它处理 BINDER_WRITE_READ 命令,完成数据的收发。

3.2.2 一次 Binder 调用的完整流程

我画了一张图,帮你理解一次 Binder 调用在驱动层是怎么流转的:

客户端进程 调用 transact() 写入 BC_TRANSACTION ioctl Binder 驱动 binder_ioctl() binder_thread_read() binder_transaction() 唤醒 服务端进程 收到 BR_TRANSACTION 执行 onTransact() 数据拷贝:一次就够了 mmap 映射,无需两次拷贝 返回 BR_REPLY 写入 BC_REPLY 返回结果数据 收到 BR_REPLY 解包返回结果 Binder 调用流程概览 客户端通过 ioctl 将请求数据写入驱动 驱动将数据拷贝到服务端进程的映射空间 服务端处理完后,同样通过驱动返回结果 整个过程只发生一次数据拷贝(mmap 映射)

看到没?关键点在于「一次拷贝」。传统 IPC 需要从用户空间拷贝到内核空间,再从内核空间拷贝到目标用户空间,两次。Binder 通过 mmap 映射,只需要一次拷贝。这在移动设备上性能优势很明显。

避坑指南:我曾经遇到过一个问题——Binder 调用返回特别慢,排查了半天发现是服务端线程池太小,请求排队了。Binder 驱动默认每个进程有 16 个工作线程,如果服务端处理不过来,客户端就会阻塞。调大 BINDER_SET_MAX_THREADS 可以缓解,但别调太大,线程切换也有开销。

3.3 ServiceManager 启动流程

ServiceManager 是 Binder 体系里的「黄页」。所有系统服务启动后,都要向 ServiceManager 注册自己。客户端想用某个服务,先问 ServiceManager 要一个 Binder 引用,然后才能调用。

那 ServiceManager 自己是怎么启动的?它是个独立的进程,在 Android 启动早期就被 init 进程拉起来了。我们来看看它的启动流程:

3.3.1 启动入口

ServiceManager 的代码在 frameworks/native/cmds/servicemanager/ 目录下。它的 main 函数长这样:

// service_manager.c
int main(int argc, char** argv) {
    struct binder_state *bs;
    // 1. 打开 Binder 驱动
    bs = binder_open("/dev/binder", 128*1024);
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    // 2. 将自己注册为 Binder 上下文管理器
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager\n");
        return -1;
    }

    // 3. 进入事件循环,等待客户端请求
    binder_loop(bs, svcmgr_handler);

    return 0;
}

代码很简洁,就三步:打开驱动、成为上下文管理器、进入循环。但每一步背后都有讲究。

3.3.2 打开 Binder 驱动

binder_open 做了两件事:

  • 调用 open("/dev/binder", O_RDWR) 打开设备文件
  • 调用 mmap 映射一块内存,用于接收数据

这里 mmap 的大小是 128KB。为什么是 128KB?因为 ServiceManager 只处理注册和查询请求,数据量不大。普通 App 的 Binder 映射大小是 1MB,你可以通过 /proc/<pid>/maps 看到。

3.3.3 成为上下文管理器

binder_become_context_manager 实际上就是向驱动发送一个 BINDER_SET_CONTEXT_MGR 命令。驱动收到后,会把当前进程标记为「上下文管理器」,也就是 ServiceManager。整个系统只有一个这样的进程。

注意:ServiceManager 的句柄永远是 0。所有 Binder 客户端都知道,句柄 0 指向的就是 ServiceManager。这是硬编码在驱动里的。你想想看,如果句柄 0 被别的进程抢占了,整个系统就乱套了。所以驱动会保证只有一个进程能成功注册为上下文管理器。

3.3.4 进入事件循环

binder_loop 是一个死循环,它不断调用 ioctl 读取驱动发来的数据。当有客户端请求时,驱动会唤醒 ServiceManager 的线程,然后调用 svcmgr_handler 回调函数处理请求。

svcmgr_handler 处理的主要命令有:

  • SVC_MGR_GET_SERVICE:查询服务,返回 Binder 引用
  • SVC_MGR_CHECK_SERVICE:同上,但不会阻塞
  • SVC_MGR_ADD_SERVICE:注册服务
  • SVC_MGR_LIST_SERVICES:列出所有已注册的服务

我记得有一次调试一个系统服务启动顺序的问题,发现某个服务在 ServiceManager 还没启动完成就去注册了,结果注册失败。后来在 init.rc 里加了 class_start 的依赖关系才解决。嗯,这种时序问题在系统启动阶段特别常见。

3.4 小结

这一章我们讲了 Binder 的核心原理、驱动层的实现机制,以及 ServiceManager 的启动流程。说白了,Binder 就是一套基于驱动的 IPC 框架,ServiceManager 是它的注册中心。理解这些,你再看 Android 的服务调用,就不会觉得是黑盒了。

下一章我们会深入 Binder 的 Java 层实现,看看 Binder.javaIBinderBinderProxy 这些类是怎么和驱动层配合的。到时候你会看到,Java 层其实只是对驱动层的一层封装,核心逻辑都在驱动里。

一句话总结:Binder 驱动是心脏,ServiceManager 是大脑,客户端和服务端通过驱动这个「血管」通信,而 ServiceManager 告诉它们「谁在哪儿」。


公众号:蓝海资料掘金营,微信deep3321