文章内容
视频素材:[Android Binder驱动-从驱动层来分析服务的添加过程]
Android Binder驱动:从驱动层分析服务的添加过程
一、Media 服务注册流程
一个 Media 服务调用 addService 时,用户态只是把服务名和 Binder 对象写进 Parcel,再发起一次 ioctl。驱动随后要解决三个问题:谁有资格接收“添加服务”请求,接收线程睡在哪里,以及带着 Binder 对象的数据怎样跨进程送达。
沿着 binder_ioctl → binder_get_thread → BINDER_SET_CONTEXT_MGR → binder_thread_read → binder_transaction 追下去,这三个问题会落到内核对象和队列操作上。客户端进程把事务交给 Binder 驱动,驱动定位 ServiceManager,转换对象引用,把事务挂入待办队列,最后唤醒 ServiceManager 处理 addService。

二、全局概览
驱动为每个打开 /dev/binder 的进程创建一个 binder_proc,并在 open 阶段放进 file->private_data。它是进程在 Binder 驱动中的身份证,其中 nodes 红黑树保存本进程拥有的 Binder 实体,threads 红黑树保存参与 Binder 通信的线程。
binder_thread 细化到线程级,记录线程状态、todo 工作队列和 wait 等待队列。binder_node 表示服务端拥有的内部 Binder 对象,内嵌 rb_node,能直接挂入 binder_proc->nodes。binder_ref 表示另一个进程对该节点的外部引用:同一份引用既能按节点指针查找,也能按用户态使用的 handle 查找。事务本身由 binder_transaction 描述,并通过 binder_work 接入待处理队列。

Binder 所说的“一次拷贝”,指事务正文只执行一次从发送方用户空间到内核缓冲区的复制。驱动分配的物理页同时映射到内核地址和接收进程的 Binder mmap 区域;写进内核映射后,接收方能从自己的用户态映射读取同一页,不必再做一次内核到用户空间的正文复制。

三、入口:binder_ioctl
用户态对 Binder 驱动的控制都从 ioctl(fd, cmd, arg) 进入:fd 定位已打开的设备文件,cmd 表示命令,arg 指向命令参数。binder_ioctl 从 filp->private_data 取回 binder_proc,再按命令分发。与服务添加关系最密切的命令有三个:BINDER_SET_CONTEXT_MGR 注册上下文管理者,BINDER_WRITE_READ 收发协议命令和事务,BINDER_VERSION 查询协议版本。
// 代码位置 /kernel/common/drivers/android/binder.c 第 5003—5135 行// binder_ioctl: 驱动层所有操作的入口static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ struct binder_proc *proc = filp->private_data; // 根据 cmd 分发到不同处理函数 switch (cmd) { case BINDER_SET_CONTEXT_MGR: ret = binder_ioctl_set_ctx_mgr(filp, arg); break; case BINDER_WRITE_READ: ret = binder_ioctl_write_read(filp, arg, ...); break; case BINDER_VERSION: // 获取 binder 驱动版本号 break; }}四、线程管理:binder_get_thread
Linux 用 task_struct 同时描述进程和线程,Binder 驱动则按当前任务的 pid 建立 binder_thread。查找发生在 proc->threads 红黑树中。树节点嵌在业务结构里,所以源码常用 rb_entry;它建立在 container_of 之上,能由成员地址反推出外层 binder_thread 地址。
首次进入驱动而没有命中时,函数分配并初始化线程对象,建立等待队列和 todo 链表,再插入红黑树。之后同一线程进入 ioctl 就能复用它。
// 代码位置 /kernel/common/drivers/android/binder.c 第 4678—4697 行// binder_get_thread: 查找或创建当前线程的 binder_threadstatic struct binder_thread *binder_get_thread(struct binder_proc *proc){ struct rb_node *parent = NULL; struct binder_thread *thread; // 在 threads 红黑树中查找 while (*p) { parent = *p; thread = rb_entry(parent, struct binder_thread, rb_node); if (current->pid < thread->pid) p = &(*parent)->rb_left; else if (current->pid > thread->pid) p = &(*parent)->rb_right; else return thread; // 找到已有线程 } // 未找到则创建新线程 thread = kzalloc(sizeof(*thread), GFP_KERNEL); thread->proc = proc; thread->pid = current->pid; init_waitqueue_head(&thread->wait); // 初始化等待队列 INIT_LIST_HEAD(&thread->todo); // 初始化工作队列 rb_insert_color(&thread->rb_node, &proc->threads); // 插入红黑树 return thread;}五、注册管理者:BINDER_SET_CONTEXT_MGR
ServiceManager 启动后发送 BINDER_SET_CONTEXT_MGR,请求成为上下文管理者。老版本实现用静态的 context_binder_node 记录这一身份;它只能成功设置一次,第二个进程注册会得到 -EBUSY。现代内核可能把它收进 binder_context,约束仍相同:一个 Binder 上下文只有一个管理者。
// 代码位置 /kernel/common/drivers/android/binder.c 第 4895—4941 行// binder_ioctl_set_ctx_mgr: 注册为上下文管理者static int binder_ioctl_set_ctx_mgr(struct file *filp, void __user *arg){ // 静态变量,只能设置一次 if (context_binder_node != NULL) { // 已经有管理者,返回错误 return -EBUSY; } // 创建 binder_node 作为管理者的标识 context_binder_node = binder_new_node(proc, 0, 0); // ptr 和 cookie 都设为 0,表示这是管理者的特殊节点 context_binder_node->ptr = 0; context_binder_node->cookie = 0; return 0;}驱动创建 binder_node,并把内嵌的 rb_node 挂到 ServiceManager 的 nodes 红黑树。该节点的 ptr 和 cookie 都为 0,类型语义对应 BINDER_TYPE_BINDER。客户端把事务目标写成 handle 0 时,驱动据此找到上下文管理者,不需要客户端预先持有普通引用。
六、循环等待:BC_ENTER_LOOPER
完成注册后,ServiceManager 通过 BINDER_WRITE_READ 写入 BC_ENTER_LOOPER。驱动给当前 binder_thread 设置 looper 标志,表明它可以持续接收事务。此后用户态反复进入读路径,binder_thread_read 检查线程及进程的 todo 队列;没有工作时,线程在 wait_event_interruptible 上可中断睡眠。
等待队列负责“睡在哪里”,工作队列负责“醒来要做什么”。生产事务的一方先把 binder_work 接入 todo,再唤醒对应 wait queue。这个顺序保证线程被唤醒后能观察到工作;条件不满足时,等待宏还会继续睡眠,抵御无效唤醒。
七、核心:binder_transaction


客户端经 BINDER_WRITE_READ 写入 BC_TRANSACTION,驱动最终进入 binder_transaction。对添加服务而言,事务目标是 handle 0,也就是 ServiceManager;Parcel 内还携带待注册服务的 flat_binder_object。
// 代码位置 /bionic/libc/kernel/uapi/linux/binder.h 第 47—56 行// flat_binder_object: 传递 binder 对象的结构体struct flat_binder_object { __u32 type; // 对象类型:BINDER_TYPE_BINDER 或 BINDER_TYPE_HANDLE __u32 flags; // 标志位 union { binder_uintptr_t binder; // 弱引用地址 binder_size_t handle; // handle 值(驱动层分配) }; binder_uintptr_t cookie; // 强引用地址};事务处理可以拆成五个动作。
- 在发送方落实 node。 驱动根据对象偏移扫描 Parcel。遇到
BINDER_TYPE_BINDER,就用binder与cookie在发送方proc->nodes中查找;首次传递时创建binder_node。这里的两个值是用户态地址标识,驱动不会把它们当作可直接解引用的内核指针。 - 在接收方建立 ref。 驱动为 ServiceManager 创建指向该 node 的
binder_ref。引用进入按 node 指针检索和按 handle(源码常称desc)检索的索引,使驱动能双向定位。 - 改写对象表示。 接收方不能使用发送方地址,因此驱动把
BINDER_TYPE_BINDER改成BINDER_TYPE_HANDLE,并写入ref->desc。普通 handle 通常从 1 分配;0 留给上下文管理者。是否恰好连续,还要服从具体版本的分配与复用策略。 - 复制事务数据。
copy_from_user把发送方的 Parcel 数据及 offsets 信息写入驱动为目标进程分配的 buffer。对象转换发生在驱动可访问的目标缓冲区中。 - 排队并唤醒。 事务工作项进入目标线程或目标进程的
todo,驱动唤醒合适的等待线程;发送线程的队列还会收到BINDER_WORK_TRANSACTION_COMPLETE,稍后由读路径返回BR_TRANSACTION_COMPLETE。

// 代码位置 /kernel/common/drivers/android/binder.c 第 3000—3675 行// binder_transaction: 核心事务处理函数static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) { // 1. 在发送方创建 binder_node(首次传递时) if (tr->target.handle == 0) { // 传的是内部对象,需要创建 node struct binder_node *node = binder_new_node(proc, ptr, cookie); // 挂载到 proc->nodes 红黑树 } // 2. 在接收方创建 binder_ref(首次传递时) struct binder_ref *ref = binder_get_ref_for_node(target_proc, node); // 以 PTR 为 key 和以 handle 为 key 分别挂载 // 3. 替换 type 和 handle // type 从 BINDER_TYPE_BINDER 变为 BINDER_TYPE_HANDLE // handle 值从 1 开始累加 t->type = BINDER_TYPE_HANDLE; t->handle = ref->desc; // desc 从 1 递增 // 4. 数据拷贝:将数据写入目标进程的 buffer copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size); // 5. 唤醒目标进程 list_add_tail(&w.entry, &target_thread->todo); // 加入目标 todo 队列 wake_up_interruptible(&target_thread->wait); // 唤醒目标等待队列 // 6. 向本进程添加 TRANSACTION_COMPLETE struct binder_work *work = kzalloc(sizeof(*work), GFP_KERNEL); work->type = BINDER_WORK_TRANSACTION_COMPLETE; list_add_tail(&work->entry, &thread->todo);}
八、一次拷贝的数据路径
把路径连起来看,发送方先在 write buffer 中组织 binder_transaction_data、Parcel 正文和对象偏移表,随后通过 ioctl 交给驱动。驱动校验用户地址和长度,分配属于目标 binder_proc 的 buffer,再用 copy_from_user 完成正文复制。该 buffer 背后的物理页已经映射进 ServiceManager 的 Binder 地址区,驱动返回 BR_TRANSACTION 时只需给出接收方可访问的 buffer 地址和大小。
ServiceManager 被唤醒后,binder_thread_read 从 todo 取出 BINDER_WORK_TRANSACTION,把必要的命令元数据写到读缓冲区。用户态随后直接解析 mmap 区域中的 Parcel。所谓一次拷贝只计算大块事务数据;ioctl 控制结构、返回命令等小数据仍会经过用户与内核边界,也不能把 mmap 理解成两个进程直接共享彼此的任意内存。
九、总结
- ServiceManager 用
BINDER_SET_CONTEXT_MGR建立唯一的特殊 node,取得 handle 0 对应的管理者身份; - 它用
BC_ENTER_LOOPER声明接收能力,并在binder_thread_read的等待队列上休眠; - 客户端事务进入
binder_transaction后,驱动创建或复用 node/ref、把地址形式改成 handle、复制数据、挂入todo并唤醒接收线程。
阅读源码时,可以抓住这条主链:binder_ioctl 找到进程上下文,binder_get_thread 找到线程上下文,设置 context manager 后由 binder_thread_read 保持接收循环,最终由 binder_transaction 完成对象翻译和跨进程投递。不同 Android 与 Linux 版本的函数签名、锁和数据结构会有些变化。
本文由 AgentWorkFlow 生成