再学安卓 - binder之驱动函数ioctl

本文分析基于Android14(aosp分支android-14.0.0_r28)(内核分支common-android14-6.1)

前言

binder_ioctl是Binder驱动中最核心的函数,没有之一,它负责通信两端IPC数据的收发以及Binder参数的设置。在IPC通信过程中,进程调用此函数执行相关命令,达成传输的目的。因此,我们单独用一篇文章的篇幅来分析它。本文内容较多,大致分为基础结构体C端流程S端流程三部分,建议分段阅读。

函数签名如下: static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

  • filp:文件描述符指针,即binder设备文件/dev/binder
  • cmd:ioctl命令
  • arg:数据指针,指向命令对应的参数,该指针是用户空间地址

以下是我们之前遇到过的一些命令(未列出的用到时再查)

ioctl命令 描述
BINDER_WRITE_READ 读写IPC数据
BINDER_SET_MAX_THREADS 设置Binder线程池线程最大个数
BINDER_SET_CONTEXT_MGR_EXT 设置Binder管理者
BINDER_VERSION 获取Binder版本信息
BINDER_ENABLE_ONEWAY_SPAM_DETECTION 开关oneway滥用检测

基础结构体

在binder_ioctl调用过程中,会涉及到一些关键的结构体,内容较多,先大致过一下,在分析代码时对照着看效果更好,同时可以对照后面的关系图,理清这些结构体之间的关系。

binder_proc(Binder进程)

c 复制代码
// common/drivers/android/binder_internal.h

struct binder_proc {
	// 哈希链表节点,该节点链接到全局进程哈希表中(binder_procs)
	struct hlist_node proc_node;
	// 红黑树根节点,管理该进程的所有Binder线程(binder_thread),按线程PID排序
	struct rb_root threads;
	// 红黑树根节点,管理该进程发布的所有Binder节点(binder_node),按地址排序
	struct rb_root nodes;
	// 红黑树根节点,按句柄desc(uint32_t)管理Binder引用(binder_ref),以desc排序
	struct rb_root refs_by_desc;
	// 红黑树根节点,按Binder节点指针(binder_node *)管理Binder引用,以Binder节点地址排序
	struct rb_root refs_by_node;
	// 双向链表头,存储等待事务完成的线程,当线程因事务未完成而阻塞时,会被加入此列表
	struct list_head waiting_threads;
	// 进程PID,唯一标识该进程
	int pid;
	// 指向进程的任务结构体,提供内核级任务信息的查询
	struct task_struct *tsk;
	// 进程的安全凭证,记录进程的UID、GID等信息,用于Binder的权限检查
	const struct cred *cred;
	// 哈希链表节点,用于将该进程链接到延迟工作队列,管理延迟任务,比如:清理死亡进程的资源
	struct hlist_node deferred_work_node;
	// 延迟工作标志,指示是否有延迟工作(如资源清理)需要处理
	int deferred_work;
	// 记录该进程中未完成的事务数量,用于事务状态跟踪
	int outstanding_txns;
	// 标志位,指示该进程是否已经死亡,死亡进程需要触发资源清理和死亡通知
	bool is_dead;
	// 标志位,指示该进程是否被冻结,在系统挂起等场景下,Binder需要暂停相关事务处理
	bool is_frozen;
	// 标志位,指示是否有同步接收的事务,管理同步事务状态
	bool sync_recv;
	// 标志位,指示是否有异步接收的事务,管理异步事务状态
	bool async_recv;
	// 等待队列,线程可以通过此队列等待进程冻结或解冻完成
	wait_queue_head_t freeze_wait;
	// 双向链表头,存储该进程中待处理的事务或命令
	struct list_head todo;
	// 统计信息结构体,记录 Binder 活动数据,用于调试和性能分析
	struct binder_stats stats;
	// 双向链表头,存储已传递的死亡通知,管理服务端死亡通知
	struct list_head delivered_death;
	// 该进程Binder线程池中线程的最大数目
	int max_threads;
	// 该进程请求驱动创建线程的数目,当该线程启动成功,此数值-1
	int requested_threads;
	// 该进程请求驱动已创建成功的线程数目,当该线程启动成功,此数值+1
	int requested_threads_started;
	// 临时引用计数,用于调试或内部资源管理
	int tmp_ref;
	// 该进程的默认优先级,设置Binder线程调度优先级
	struct binder_priority default_priority;
	// debugfs条目,用于调试时查看Binder状态
	struct dentry *debugfs_entry;
	// Binder数据缓冲区描述符,管理mmap申请到的整个缓冲区
	struct binder_alloc alloc;
	// Binder上下文,包含全局信息如设备文件描述符
	struct binder_context *context;
	// 内部自旋锁,保护Binder内部数据结构的并发访问
	spinlock_t inner_lock;
	// 外部自旋锁,保护Binder外部数据结构
	spinlock_t outer_lock;
	// binderfs 条目,支持用户空间挂载和管理Binder设备
	struct dentry *binderfs_entry;
	// 标志位,指示是否启用单向调用(Oneway)滥用检测
	bool oneway_spam_detection_enabled;
};

binder_thread(Binder线程)

c 复制代码
// common/drivers/android/binder_internal.h

struct binder_thread {
	// 指向该线程所属的binder_proc结构体
	// 提供了访问进程级资源(如文件描述符、线程集合等)的途径
	struct binder_proc *proc;
	// 红黑树节点,用于将该线程插入到proc->threads红黑树中,按线程ID排序
	struct rb_node rb_node;
	// 双向链表节点,当线程完成自身todo队列里的工作项时,通过此节点加入到proc->waiting_threads列表中
	struct list_head waiting_thread_node;
	// 本线程ID(PID),也是用户空间中的TID
	int pid;
	// 标识线程的looper状态,比如:是否在循环等待事务,仅由本线程修改
	int looper;
	// 标志位,用于通知该线程退出等待状态并返回用户空间处理,可由其他线程写入
	bool looper_need_return;
	// 指向该线程当前处理的事务栈顶
	// Binder支持事务嵌套,此字段管理事务的调用栈,确保正确追踪事务层次
	struct binder_transaction *transaction_stack;
	// 双向链表头,存储该线程待处理的事务或命令(binder_work节点)
	// 线程从此列表中获取任务并依次处理,是线程工作队列的核心
	struct list_head todo;
	// 标志位,指示todo队列中是否有需要马上处理的工作项
	bool process_todo;
	// 在事务处理失败时记录的错误码,用于反馈给调用方
	struct binder_error return_error;
	// 在处理事务回复时记录的错误码,用于通知服务端
	struct binder_error reply_error;
	// 扩展错误信息,提供更详细的错误状态描述,便于调试和错误分析
	struct binder_extended_error ee;
	// 等待队列,线程在等待事务或资源时,通过此队列进入睡眠状态,等待被唤醒
	// 队列中的节点为struct wait_queue_entry
	wait_queue_head_t wait;
	// 统计信息结构体,记录该线程的Binder活动数据(如事务次数、错误数)
	struct binder_stats stats;
	// 临时引用计数
	atomic_t tmp_ref;
	// 标志位,标记该线程是否死亡,以便触发资源清理操作
	bool is_dead;
	// 指向本线程的内核描述符,提供对本线程内核级信息(如调度、状态)的访问
	struct task_struct *task;
	// 自旋锁,用于保护线程优先级相关的并发访问
	spinlock_t prio_lock;
	// 下一个事务的优先级,影响线程的调度优先级,确保高优先级事务优先执行
	struct binder_priority prio_next;
	// 指示线程优先级是否被调整,用于管理线程的调度策略。
	enum binder_prio_state prio_state;
};

binder_node(Binder接口)

c 复制代码
// common/drivers/android/binder_internal.h

// 代表一个Binder接口(Server端)
struct binder_node {
	// 调试ID,用于在日志和调试中唯一标识该binder_node
	int debug_id;
	// 自旋锁,用于保护该binder_node的并发访问
	spinlock_t lock;
	// 工作项,表示该节点需要处理的工作任务
	struct binder_work work;
	union {
		// 红黑树节点,用于将该binder_node插入到proc->nodes红黑树中,按地址排序
		struct rb_node rb_node;
		// 哈希链表节点,用于在节点失效或死亡时加入到全局死节点列表,以便清理
		struct hlist_node dead_node;
	};
	// 指向该binder_node所属的binder_proc结构体,标识该节点所在的进程
	struct binder_proc *proc;
	// 哈希链表头,存储所有引用该binder_node的binder_ref结构体
	struct hlist_head refs;
	// 内部强引用计数,由Binder驱动内部维护,用于确保驱动自身对节点的引用,防止资源被意外释放
	int internal_strong_refs;
	// 记录当前进程中对该binder_node的弱引用数量,弱引用不会阻止对象被销毁
	int local_weak_refs;
	// 记录当前进程中对该binder_node的强引用数量,强引用会影响对象的生命周期
	int local_strong_refs;
	// 临时引用计数,用于调试或暂存引用计数,通常在资源管理过程中临时使用
	int tmp_refs;
	// 弱引用指针,指向用户空间中Binder对象的地址
	binder_uintptr_t ptr;
	// 指向用户空间中Binder对象的地址,与上一条指向相同
	binder_uintptr_t cookie;
	struct {
		/*
		 * 以下位字段均受proc->inner_lock锁保护
		 */
		// 指示当前是否存在强引用
		u8 has_strong_ref:1;
		// 指示是否有待处理的强引用请求
		u8 pending_strong_ref:1;
		// 指示当前是否存在弱引用
		u8 has_weak_ref:1;
		// 指示是否有待处理的弱引用请求
		u8 pending_weak_ref:1;
	};
	struct {
		/*
		 * 事务和调度配置结构体,控制事务处理时的调度和行为
		 * 初始化后保持不变
		 */
		// 调度策略(如 SCHED_NORMAL 或 SCHED_FIFO)
		u8 sched_policy:2;
		// 是否继承实时调度优先级
		u8 inherit_rt:1;
		// 是否允许传递文件描述符
		u8 accept_fds:1;
		// 事务是否使用安全上下文
		u8 txn_security_ctx:1;
		// 最小优先级,设置事务的最低优先级
		u8 min_priority;
	};
	// 当前是否有待处理的异步事务
	bool has_async_transaction;
	// 双向链表头,存储待处理的异步事务,线程会从中提取任务执行
	struct list_head async_todo;
};

binder_ref(Binder引用)

c 复制代码
// common/drivers/android/binder_internal.h

// 代表一个Binder代理(Client端)
struct binder_ref {
	/* Lookups needed: */
	/*   node + proc => ref (transaction) */
	/*   desc + proc => ref (transaction, inc/dec ref) */
	/*   node => refs + procs (proc exit) */

	// 包含句柄 (desc)、强弱引用计数等核心信息
	// data.desc与用户空间中BpBinder.mHandle相等
	struct binder_ref_data data;
	// 红黑树节点,用于将本引用插入到binder_proc->refs_by_desc红黑树中,便于通过句柄data.desc快速查找
	struct rb_node rb_node_desc;
	// 红黑树节点,用于将本引用插入到binder_proc->refs_by_node红黑树中,便于通过binder_node快速查找
	struct rb_node rb_node_node;
	// 哈希链表节点,用于将本引用加入到binder_node->refs哈希链表中
	struct hlist_node node_entry;
	// 指向所属进程的binder_proc,标识本引用所在的进程
	struct binder_proc *proc;
	// 指向本引用对应的远程服务对象binder_node
	struct binder_node *node;
	// 当引用的binder_node死亡时,Binder驱动通过此字段通知引用方
	struct binder_ref_death *death;
	// 当引用的binder_node被冻结时 (例如系统挂起或进程冻结),Binder驱动通过此字段通知引用方
	struct binder_ref_freeze *freeze;
};
c 复制代码
// common/drivers/android/binder_internal.h

struct binder_ref_data {
	// 调试ID
	int debug_id;
	// 远程Binder接口句柄,与用户空间中BpBinder.mHandle相等
	uint32_t desc;
	// 强引用计数
	int strong;
	// 弱引用计数
	int weak;
};

binder_transaction(Binder事务)

c 复制代码
// common/drivers/android/binder_internal.h

struct binder_transaction {
	// 调试ID,用于在日志和调试中唯一标识该binder_transaction
	int debug_id;
	// 用于将事务链接到处理线程的todo链表中(binder_thread.todo)
	// 与binder_transaction关联的binder_work的type为BINDER_WORK_TRANSACTION或BINDER_WORK_TRANSACTION_COMPLETE
	struct binder_work work;
	// 指向发起此事务的Binder线程,用于关联上下文和可能的回复路径
	struct binder_thread *from;
	// 发起事务的进程PID
	pid_t from_pid;
	// 发起事务的线程ID
	pid_t from_tid;
	// 发起方父事务指针(针对嵌套事务)
	// 若当前事务是嵌套的(如一个事务处理中触发另一个事务),指向父事务
	struct binder_transaction *from_parent;
	// 事务的目标进程,用于关联到正确的接收方进程
	struct binder_proc *to_proc;
	// 事务的目标线程,若指定则直接投递到该线程的待处理队列
	struct binder_thread *to_thread;
	// 目标端父事务指针(针对嵌套事务)
	// 在接收方处理嵌套事务时,关联其父事务上下文
	struct binder_transaction *to_parent;
	// 标记事务是否为同步调用(1表示需要回复,0表示单向调用)
	unsigned need_reply:1;
	// 指向存储实际传输数据的缓冲区,包含Parcel序列化数据及Binder对象偏移
	struct binder_buffer *buffer;
	// 用户定义的操作指令(比如:AIDL生成文件中的方法编号)
	unsigned int    code;
	// 控制事务行为的标志(比如:TF_ONE_WAY表示单向调用)
	unsigned int    flags;
	// 记录发送线程的调度优先级,用于接收方线程优先级继承
	struct binder_priority priority;
	// 接收方线程处理事务前保存的原优先级,处理完成后恢复
	struct binder_priority saved_priority;
	// 标记是否已设置优先级,防止多次修改接收方线程优先级
	bool set_priority_called;
	// 标记是否为嵌套事务,表示此事务是否在另一个事务的处理过程中被触发
	bool is_nested;
	// 发送方的有效用户ID,用于安全审核,验证发送方权限
	kuid_t  sender_euid;
	// 事务开始时间戳,记录事务创建时间,用于性能分析和超时监控
	ktime_t start_time;
	// 文件描述符修复链表,存储需跨进程调整的文件描述符映射信息(如binder_fd_array_object)
	struct list_head fd_fixups;
	// 安全上下文指针,SELinux或其他安全模块的上下文信息,用于权限检查
	binder_uintptr_t security_ctx;
	// 自旋锁,保护from,to_proc,to_thread字段的并发访问
	spinlock_t lock;
	// 预留字段,供Android厂商扩展自定义数据(如芯片特性优化)
	ANDROID_VENDOR_DATA(1);
};

binder_work(Binder工作项)

c 复制代码
// common/drivers/android/binder_internal.h

struct binder_work {
	// 链表节点,添加到binder_thread.todo链表中等待处理
	struct list_head entry;

	// 工作项类型枚举
	enum binder_work_type {
		// 表示与一个Binder事务关联(如跨进程调用请求或回复)
		BINDER_WORK_TRANSACTION = 1,
		// 事务完成通知,用于异步事务的ACK确认
		BINDER_WORK_TRANSACTION_COMPLETE,
		// 单向事务(oneway)滥用通知
		BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT,
		// 错误返回通知,当事务处理失败时,通过此类型传递错误码(如 -EFAULT)
		BINDER_WORK_RETURN_ERROR,
		// 节点相关工作项,用于处理与binder_node相关的任务(如节点引用计数变更、节点销毁等)
		BINDER_WORK_NODE,
		// 节点死亡通知,当服务端进程终止时,通知注册了死亡回调的客户端
		BINDER_WORK_DEAD_BINDER,
		// 死亡通知并清理资源,在发送死亡通知后,同时清除相关资源(如死亡回调记录)
		BINDER_WORK_DEAD_BINDER_AND_CLEAR,
		// 清除死亡通知,当客户端主动取消死亡回调注册时使用
		BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
	} type;
};

flat_binder_object(传输中的Binder)

c 复制代码
// common/include/uapi/linux/android/binder.h

struct flat_binder_object {
	// hdr.type存放对象类型,便于Binder驱动识别对象类型
	// hdr.type常见取值:
	// BINDER_TYPE_BINDER表示本地Binder对象,BINDER_TYPE_HANDLE表示远程Binder句柄
	struct binder_object_header	hdr;
	// 标志位,控制对象的传输行为和特性,如优先级或是否接受文件描述符
	// FLAT_BINDER_FLAG_PRIORITY:指定事务的调度优先级
	// FLAT_BINDER_FLAG_ACCEPTS_FDS:指示是否允许传递文件描述符
	__u32				flags;

	/* 8 bytes of data. */
	union {
		// 指向本地Binder对象的指针,在本地进程中有效
		binder_uintptr_t	binder;
		// 远程进程中的Binder对象句柄,用于跨进程引用
		// 与binder_ref.data.desc值相等
		__u32			handle;
	};

	// 指向用户空间中Binder对象的指针,仅在本地进程中有效
	binder_uintptr_t	cookie;
};

binder_transaction_data(Binder事务数据)

c 复制代码
// common/include/uapi/linux/android/binder.h
// bionic/libc/kernel/uapi/linux/android/binder.h
// 内核和AOSP中都有一份binder_transaction_data的定义,二者相同

struct binder_transaction_data {
    union {
        __u32 handle;  // 远程对象的句柄(无符号32位整型),即Server端Binder接口的编号
        binder_uintptr_t ptr;  // 本地对象指针,指向Binder接口对象
    } target;
    binder_uintptr_t cookie;  // 附加数据,由调用者自己定义
    // 接口方法编码(32位无符号整型),分为应用方法编码和特殊编码
    // 应用方法编码:介于FIRST_CALL_TRANSACTION(0x00000001)和LAST_CALL_TRANSACTION(0x00ffffff)
    // 特殊编码:大于LAST_CALL_TRANSACTION,由系统定义,具体参见IBinder.java
    __u32 code;
    __u32 flags;  // 定义事务行为,最常见的值是FLAG_ONEWAY(异步单向请求)
    pid_t sender_pid;  // 发起本次事务的进程ID(PID)
    uid_t sender_euid;  // 发起本次事务进程的有效用户ID(EUID)
    binder_size_t data_size;  // 本次事务发送的数据缓冲区大小(64位无符号整型),单位字节
    // binder_object偏移量数组总大小,单位字节
    binder_size_t offsets_size;
    union {
        struct {
	        // 指向事务数据缓冲区的指针
            binder_uintptr_t buffer;
            // 指向存放binder_object偏移量数组的指针
            binder_uintptr_t offsets;
        } ptr;
        // 若是小数据(8字节以内),则使用buf传输,不额外分配内存
        __u8 buf[8];
    } data;
};

binder_object是一个联合体,它可以代表flat_binder_object、binder_fd_object、binder_buffer_object、binder_fd_array_object。其中最常见的就是flat_binder_object,代表一个Binder接口在通信传输过程中的形态。
ptr.offsets和offsets_size是专门为了从数据缓冲区提取binder_object结构数据而设立的。ptr.offsets指向一个数组,这个数组的每一个元素都是基于数据缓冲区起始地址(ptr.buffer)的一个偏移量。通过偏移量,就能算出每一个binder_object结构体的起始地址,然后按照binder_object结构体的长度,完整读取出binder_object存储的数据。而offsets_size记录了数组总大小,驱动通过offsets + offsets_size就能将整个数组拷贝到内核空间。

binder_transaction_data组装好之后,会和通信码拼接,一起写入到IPCThreadState.mOut中,最终结构如下图所示:

binder_write_read(Binder数据读写信息)

c 复制代码
// common/include/uapi/linux/android/binder.h
// bionic/libc/kernel/uapi/linux/android/binder.h

struct binder_write_read {
	// 告诉驱动需要从write_buffer中读取多少字节的数据
	binder_size_t       write_size;
	// 由驱动填充的值,表示实际处理了多少字节来自write_buffer的数据
	binder_size_t       write_consumed;
	// 指向需要驱动读取的用户空间地址指针
	binder_uintptr_t    write_buffer;
	// 提供给驱动写入的缓冲区大小(单位:字节)
	binder_size_t       read_size;
	// 由驱动填充的值,表示实际写入了多少字节到read_buffer
	binder_size_t       read_consumed;
	// 提供给驱动写入的缓冲区用户空间地址指针
	binder_uintptr_t    read_buffer;
};

binder_write_read的字段非常简单,它只关心数据在哪里,有多少,消费了多少,至于数据是什么,它并不关心。

结构体关系图

主要结构体之间的关系如下:

结构体本身是没有Server和Client之分的,图中之所以将它们用虚线框圈出来且看起来字段不一样,是因为当它们作为Server或Client角色时,所用到的字段不一样。实际上在通信过程中,同一个进程往往既要扮演Server角色,也要扮演Client角色。

通信模型

异步通信:

同步通信:

代表编号②的工作项(binder_work)是在发起方数据全部写入缓冲区后,唤醒Server端Binder线程之前,就放入了发起方的Binder线程todo队列中,但此工作项不会立即执行,它的执行时机是BR_REPLY来临时即发起方线程被再次唤醒时。由于②号工作项先入队,因此它先于⑥号工作项执行。

我们以同步通信为例,讲解整个通信过程。异步通信的过程差别不太大,若遇到有差异的地方会顺带提一下。

分析链路分为两条:

  • Client发起请求和接收回复:① BC_TRANSACTION、② BR_TRANSACTION_COMPLETE、⑥ BR_REPLY
  • Server接收请求和发起回复:③ BR_TRANSACTION、④ BC_REPLY、⑤ BR_TRANSACTION_COMPLETE

为什么按照这两条链路分析?因为这样是按照代码执行的逻辑一步步推进,更符合思维直觉,不需要读者在两端的线程之间跳转,阅读时的上下文切换相对少一些。

Client发起请求和接收回复

进入读写命令流程

1. 查找或创建Client本地线程

经过系统调用后,来到驱动函数binder_ioctl。

c 复制代码
// common/drivers/android/binder.c

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
	... ...
	// 在当前进程的binder_proc.threads红黑树中,按照当前线程的pid查找
	// 若没有,则新建binder_thread用来代表本线程
	thread = binder_get_thread(proc);
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	// 根据命令做相应操作
	switch (cmd) {
		case BINDER_WRITE_READ:
			ret = binder_ioctl_write_read(filp, cmd, arg, thread);
			if (ret)
				goto err;
			break;
		... ...
	}
	... ...
}

2. 解析binder_write_read

c 复制代码
// common/drivers/android/binder.c

static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread) {
	int ret = 0;
	struct binder_proc *proc = filp->private_data;
	... ...
	// __user宏用于标记一个指针指向的是用户空间地址
	// 也就是说,当内核代码需要访问该数据时,不能直接读写,需要使用copy_from_user/copy_to_user等函数
	// 实际上arg就指向binder_write_read
	void __user *ubuf = (void __user *)arg;
	struct binder_write_read bwr;
	... ...
	// 从用户空间拷贝binder_write_read到内核中
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
	... ...
	// 驱动根据用户进程写入的数据进行处理
	if (bwr.write_size > 0) {
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		trace_binder_write_done(ret);
		// 处理异常,驱动Reply数据量清零,回传更新后的bwr到用户空间
		if (ret < 0) {
			bwr.read_consumed = 0;
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
				ret = -EFAULT;
			goto out;
		}
	}
	... ...
out:
	return ret;
}

3. 进入binder_thread_write

c 复制代码
// common/drivers/android/binder.c

static int binder_thread_write(struct binder_proc *proc,
			struct binder_thread *thread,
			binder_uintptr_t binder_buffer, size_t size,
			binder_size_t *consumed) {
	uint32_t cmd;
	struct binder_context *context = proc->context;
	// 用户缓冲区起始地址
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	// 当前处理位置
	void __user *ptr = buffer + *consumed;
	// 用户缓冲区结束地址
	void __user *end = buffer + size;

	// 逐条处理命令,直到缓冲区结束或线程发生错误
	while (ptr < end && thread->return_error.cmd == BR_OK) {
		int ret;

		// 从用户空间安全读取4字节的命令码(比如BC_TRANSACTION)
		// get_user是一个宏,用于从用户空间复制一个基础类型的数据到内核空间,与copy_from_user类似
		if (get_user(cmd, (uint32_t __user *)ptr))
			return -EFAULT;
		// 指针后移
		ptr += sizeof(uint32_t);
		trace_binder_command(cmd);
		// 更新命令统计数据
		if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
			atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]);
			atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]);
			atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]);
		}
		// 根据命令,执行操作,当前我们讨论的是发送请求,因此看BC_TRANSACTION分支
		switch (cmd) {
		... ...
		case BC_TRANSACTION:
		case BC_REPLY: {
			struct binder_transaction_data tr;
			// 从用户空间拷贝binder_transaction_data数据到内核
			if (copy_from_user(&tr, ptr, sizeof(tr)))
				return -EFAULT;
			// 指针后移
			ptr += sizeof(tr);
			// 调用核心事务处理函数
			binder_transaction(proc, thread, &tr,
					   cmd == BC_REPLY, 0);
			break;
		}
		... ...
		}
		// 设置已读取数据量
		*consumed = ptr - buffer;
	}
	return 0;
}

开启事务

binder_transaction函数用于发起Binder事务,一共有800+行,主要完成以下工作:

  1. binder_node和binder_proc查找,即Server端进程和节点
  2. binder_buffer用户空间映射到物理页,即Server端进程用户空间与物理内存的映射
  3. 复制数据到物理页,即Client端进程用户空间数据通过内核空间复制到物理页
  4. 添加binder_work工作项并唤醒工作线程

代码分支和嵌套非常多,极容易迷路,因此我写了一份"路书"(Code Map),对照着看代码就比较清晰了。

1. binder_node和binder_proc查找

c 复制代码
// common/drivers/android/binder.c

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size) {
	//初始化一大堆变量
	... ...
	struct binder_proc *target_proc = NULL;
	struct binder_node *target_node = NULL;
	... ...
	// 若是BC_REPLY命令
	if (reply) {
		... ...
	} else { // 若不是BC_REPLY命令,则执行此分支
		// target.handle等于binder_ref_data.desc,即远程Binder接口句柄
		// 若target.handle不等于0,则表示当前需要通信的对端Binder接口是普通Binder
		if (tr->target.handle) {
			struct binder_ref *ref;
			binder_proc_lock(proc);
			// 通过proc->refs_by_desc红黑树,查找desc与target.handle相等的节点(binder_ref)
			// 注意:proc为client进程的binder_proc
			ref = binder_get_ref_olocked(proc, tr->target.handle,
						     true);
			if (ref) {
				// 增加引用计数,同时给target_node和target_proc赋值,它们就是通信的目标端
				target_node = binder_get_node_refs_for_txn(
						ref->node, &target_proc,
						&return_error);
			} else {
				binder_user_error("%d:%d got transaction to invalid handle, %u\n",
						  proc->pid, thread->pid, tr->target.handle);
				return_error = BR_FAILED_REPLY;
			}
			binder_proc_unlock(proc);
		} else { // 若target.handle等于0,则表示当前需要跟ServiceManager通信
			... ...
			// 这个分支的操作和上面基本一致,只是binder_node直接从conext中得到
			// 因为ServiceManager启动的时候已经将自己注册好了
			target_node = context->binder_context_mgr_node;
			... ...
		}
		// 接下来做一些安全检查,然后尝试从binder_transaction栈中找一个合适的Binder线程作为target_thread
		// 属于支线流程,我们先跳过
		... ...
	}
	... ...
}

2. binder_buffer用户空间映射到物理页

c 复制代码
// common/drivers/android/binder.c

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size) {
	struct binder_transaction *t;
	struct binder_work *tcomplete;
	... ...
	// 申请一个binder_transaction和bidner_work的内存空间
	t = kzalloc(sizeof(*t), GFP_KERNEL);
	tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
	// 后续是一连串初始化binder_transaction各个字段的操作
	// 其中在t->from的字段里放了一个binder_thread,这个线程将用来接收Server端的回复
	... ...
	if (!reply && !(tr->flags & TF_ONE_WAY))
		t->from = thread;
	else
		t->from = NULL;
	... ...
	// 创建binder_buffer,然后申请一个物理页,通过vm_insert_page函数将二者关联映射
	// 完成了Server端用户虚拟空间和实际物理页的映射
	t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
		tr->offsets_size, extra_buffers_size,
		!reply && (t->flags & TF_ONE_WAY));
	// 初始化t->buffer字段
	... ...
}
c 复制代码
// common/drivers/android/binder_alloc.c

struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
					   size_t data_size,
					   size_t offsets_size,
					   size_t extra_buffers_size,
					   int is_async) {
	struct binder_buffer *buffer, *next;
	size_t size;
	int ret;
	... ...
	// 对data_size、offsets_size和extra_buffers_size进行对齐计算,并检查溢出,确保缓冲区大小合法
	size = sanitized_size(data_size, offsets_size, extra_buffers_size);
	... ...

	// 为binder_buffer结构体分配内存
	next = kzalloc(sizeof(*next), GFP_KERNEL);
	... ...

	binder_alloc_lock(alloc);
	// 从alloc->free_buffers红黑树中找到比size稍大一点的buffer,并且将剩余大小分割出来
	// 将buffer从free_buffers移除,添加到allocated_buffers中
	buffer = binder_alloc_new_buf_locked(alloc, next, size, is_async);
	// 初始化buffer的其他字段
	... ...
	binder_alloc_unlock(alloc);

	// binder_install_buffer_pages函数主要完成以下2件事情
	// 1. 调用alloc_page函数: 分配一个物理页面
	// 2. 调用vm_insert_page函数:将分配的物理页面映射到用户空间的指定地址
	ret = binder_install_buffer_pages(alloc, buffer, size);
	... ...
	return buffer;
}

3. 复制数据到物理页

c 复制代码
// common/drivers/android/binder.c

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size) {
	... ...
	// 将Client用户空间的binder_object偏移量数组复制到内核空间
	if (binder_alloc_copy_user_to_buffer(
				&target_proc->alloc,
				t->buffer,
				ALIGN(tr->data_size, sizeof(void *)),
				(const void __user *)
					(uintptr_t)tr->data.ptr.offsets,
				tr->offsets_size)) {
		// 异常处理
		... ...
	}
	... ...
	// 依次取出偏移量,然后获取binder_object并做相应处理
	off_start_offset = ALIGN(tr->data_size, sizeof(void *));
	buffer_offset = off_start_offset;
	off_end_offset = off_start_offset + tr->offsets_size;
	... ...
	for (buffer_offset = off_start_offset; buffer_offset < off_end_offset;
	     buffer_offset += sizeof(binder_size_t)) {
		struct binder_object_header *hdr;
		size_t object_size;
		struct binder_object object;
		binder_size_t object_offset;
		binder_size_t copy_size;

		// 将binder_object偏移量数组中的偏移量从物理页中拷贝到内核虚拟地址&object_offset中
		if (binder_alloc_copy_from_buffer(&target_proc->alloc,
						  &object_offset,
						  t->buffer,
						  buffer_offset,
						  sizeof(object_offset))) {
			// 异常处理
			... ...
		}

		// 计算得出binder_object在client用户空间的地址
		// 调用binder_alloc_copy_user_to_buffer拷贝到物理页中
		copy_size = object_offset - user_offset;
		if (copy_size && (user_offset > object_offset ||
				object_offset > tr->data_size ||
				binder_alloc_copy_user_to_buffer(
					&target_proc->alloc,
					t->buffer, user_offset,
					user_buffer + user_offset,
					copy_size))) {
			// 异常处理
			... ...
		}
		// 从Client用户空间拷贝binder_object数据到object中
		object_size = binder_get_object(target_proc, user_buffer,
				t->buffer, object_offset, &object);
		... ...
		// 增加偏移量,为下次循环做准备
		user_offset = object_offset + object_size;
		// 判断binder_object类型
		hdr = &object.hdr;
		switch (hdr->type) {
			case BINDER_TYPE_BINDER:
			case BINDER_TYPE_WEAK_BINDER: {
				struct flat_binder_object *fp;
				// 根据hdr指针,获得flat_binder_object指针(通过container_of宏)
				fp = to_flat_binder_object(hdr);
				// 查找或新建binder_node和binder_ref,并建立二者的关联
				// 并修改flat_binder_object.hdr.type
				ret = binder_translate_binder(fp, t, thread);

				if (ret < 0 ||
					// 将flat_binder_object存入buffer对应的物理页中
				    binder_alloc_copy_to_buffer(&target_proc->alloc,
								t->buffer,
								object_offset,
								fp, sizeof(*fp))) {
					// 若出错则进行异常处理
					... ...
				}
			} break;
			... ...
		}
		// 将除了binder_object之外的数据,从用户空间拷贝到t->buffer对应的物理页中
		if (binder_alloc_copy_user_to_buffer(
					&target_proc->alloc,
					t->buffer, user_offset,
					user_buffer + user_offset,
					tr->data_size - user_offset)) {
			// 若出错则进行异常处理
			... ...
		}
    }
}

以上从用户空间复制数据到物理页的操作全部完成,其中很多指针操作以及偏移量的计算,过于细节,就不复述了。

不过其中binder_translate_binder函数值得看看,当binder_object的type为BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER时,表明binder_object是一个Binder实体,将它传输到对端使用的话,需要转化为Binder引用(引用句柄Handle)。而这个转化的步骤就是在binder_translate_binder函数中进行的。还记得吗?第9篇时我们在Parcel::unflattenBinder函数中的疑问,这里得到了完美解答。当时我们只是直接抛出了答案,阅读源码后,我们知道了整件事的来龙去脉。阅读源码的一大乐趣就是这样,不断提出疑问,不断解答疑问。从中获得的正向反馈不亚于完成某个项目功能。

c 复制代码
// common/drivers/android/binder.c

static int binder_translate_binder(struct flat_binder_object *fp,
				   struct binder_transaction *t,
				   struct binder_thread *thread) {
	struct binder_node *node;
	struct binder_proc *proc = thread->proc;
	struct binder_proc *target_proc = t->to_proc;
	struct binder_ref_data rdata;
	int ret = 0;

	// 从当前进程的binder_proc->nodes中查找与fp->binder对应的binder_node
	// 若没有则新建
	node = binder_get_node(proc, fp->binder);
	if (!node) {
		node = binder_new_node(proc, fp);
		if (!node)
			return -ENOMEM;
	}
	... ...
	// 在目标进程的target_proc.refs_by_node中查找binder_node对应的binder_ref
	// 若没有则新建
	ret = binder_inc_ref_for_node(target_proc, node,
			fp->hdr.type == BINDER_TYPE_BINDER,
			&thread->todo, &rdata);
	if (ret)
		goto done;
	// 修改falt_binder_object的值,将其变成Binder句柄(Handle)
	if (fp->hdr.type == BINDER_TYPE_BINDER)
		fp->hdr.type = BINDER_TYPE_HANDLE;
	else
		fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
	fp->binder = 0;
	fp->handle = rdata.desc;
	fp->cookie = 0;
	... ...
	return ret;
}

4. 添加binder_work工作项并唤醒工作线程

c 复制代码
// common/drivers/android/binder.c

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size) {
	struct binder_transaction *t;
	struct binder_work *tcomplete;
	... ...
	if (t->buffer->oneway_spam_suspect)
		tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;
	else
		tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
	// 将t->work的类型设置为BINDER_WORK_TRANSACTION
	t->work.type = BINDER_WORK_TRANSACTION;

	if (reply) { // 若是BC_REPLY
		... ...
	} else if (!(t->flags & TF_ONE_WAY)) { // 若是BC_TRANSACTION且不是oneway异步请求,需要BR_REPLY
		binder_inner_proc_lock(proc);
		// tcomplete代表BR_TRANSACTION_COMPLETE类型的工作项
		// 这里将它加入到client(事务发起方)的工作线程的todo队列中,但会延迟执行
		binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);
		t->need_reply = 1;
		t->from_parent = thread->transaction_stack;
		thread->transaction_stack = t;
		binder_inner_proc_unlock(proc);
		// 将t->work工作项插入目标进程的某个todo队列中并唤醒线程
		return_error = binder_proc_transaction(t,
				target_proc, target_thread);
		... ...
	} else { // 若是BC_TRANSACTION且是oneway异步请求,不需要BR_REPLY
		... ...
		//将tcomplete工作项插入到client(事务发起方)工作线程的todo队列中
		binder_enqueue_thread_work(thread, tcomplete);
		//将t->work工作项插入目标进程的某个todo队列中并唤醒线程
		return_error = binder_proc_transaction(t, target_proc, NULL);
		... ...
	}
	... ...
	return;
}

上面提到最终t->work工作项会被插入到某个todo队列中,那么到底是什么队列呢?binder_proc_transaction函数给出了答案。

c 复制代码
// common/drivers/android/binder.c

static int binder_proc_transaction(struct binder_transaction *t,
				    struct binder_proc *proc,
				    struct binder_thread *thread) {
	struct binder_node *node = t->buffer->target_node;
	bool oneway = !!(t->flags & TF_ONE_WAY);
	bool pending_async = false;
	struct binder_transaction *t_outdated = NULL;
	bool skip = false;
	bool enqueue_task = true;
	... ...
	// 若是异步请求
	if (oneway) {
		if (node->has_async_transaction)
			// 若binder_node正在处理异步事务,将当下标记为异步等待
			pending_async = true;
		else
			// 若binder_node没在处理异步事务,将当前node标记为处理异步任务中
			node->has_async_transaction = true;
	}
	... ...
	//若没有传入指定的目标线程,且目标binder_node没在处理异步事务,且没有设置跳过标记
	if (!thread && !pending_async && !skip)
		// 从目标进程的proc->waiting_threads链表中取第一个线程
		// 若链表为空,则返回NULL
		thread = binder_select_thread_ilocked(proc);
	... ..

	if (thread) { // 在proc->waiting_threads链表中找到了目标线程
		// 设置事务优先级
		binder_transaction_priority(thread, t, node);
		// 将t->work插入到目标线程的todo队列中
		binder_enqueue_thread_work_ilocked(thread, &t->work);
	} else if (!pending_async) { // 没有找到合适的目标线程,且binder_node没在处理异步事务
		... ...
		// 将t->work插入到目标进程的todo队列中
		binder_enqueue_work_ilocked(&t->work, &proc->todo);
	} else { // 没有找到合适的目标线程,且binder_node正在处理异步事务
		... ...
		// 将t->work加入到binder_node的async_todo队列中
		binder_enqueue_work_ilocked(&t->work, &node->async_todo);
	}

	... ...
	// binder_node没在处理异步事务
	if (!pending_async)
		// 唤醒目标线程
		binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);
	... ...
	return 0;
}

最后看看线程是怎么唤醒的。

c 复制代码
// common/drivers/android/binder.c

static void binder_wakeup_thread_ilocked(struct binder_proc *proc,
					 struct binder_thread *thread,
					 bool sync) {
	assert_spin_locked(&proc->inner_lock);

	// 若thread不为空,那么通过内核的wait_queue_head_t等待队列机制唤醒工作线程
	if (thread) {
		trace_android_vh_binder_wakeup_ilocked(thread->task, sync, proc);
		if (sync)
			wake_up_interruptible_sync(&thread->wait);
		else
			wake_up_interruptible(&thread->wait);
		return;
	}
	
	// 走到这一步,依然有可能没有找到合适的工作线程,一般为以下两种情况:
	// 1. 所有工作线程都在忙着处理Binder事务,针对这种情况,只能等待线程空闲
	// 2. 有些线程在epoll轮询时被阻塞在等待队列中,但他们没有被添加到waiting_threads中
	// 针对这种情况,只能唤醒所有不是在处理Binder事务的阻塞线程
	binder_wakeup_poll_threads_ilocked(proc, sync);
}

static void binder_wakeup_poll_threads_ilocked(struct binder_proc *proc,
					       bool sync) {
	struct rb_node *n;
	struct binder_thread *thread;
	
	for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) {
		thread = rb_entry(n, struct binder_thread, rb_node);
		if (thread->looper & BINDER_LOOPER_STATE_POLL &&
		    binder_available_for_proc_work_ilocked(thread)) {
			... ...
			if (sync)
				wake_up_interruptible_sync(&thread->wait);
			else
				wake_up_interruptible(&thread->wait);
		}
	}
}

到这里,请求发起方的数据和指定已经经过驱动写入对端的内存空间中,并且驱动已唤醒对端的Binder线程,请求即将被处理。但我们先不跳到对端去,我们继续发起方的流程。

进入休眠

binder_transaction函数结束后,层层返回,又回到binder_ioctl_write_read函数中,继续执行binder_thread_read函数。

c 复制代码
// common/drivers/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      binder_uintptr_t binder_buffer, size_t size,
			      binder_size_t *consumed, int non_block) {
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;

	int ret = 0;
	bool nothing_to_do = false;
	bool force_spawn = false;
	int wait_for_proc_work;

	if (*consumed == 0) {
		// 先往bwr.read_buffer中写入通信码BR_NOOP
		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
	}

retry:
	binder_inner_proc_lock(proc);
	// 若binder_thread的事务栈为空且todo队列也为空,那么wait_for_proc_work为true
	// 说明binder_thread的工作项全部完成了
	wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
	binder_inner_proc_unlock(proc);
	// 标记thread的looper状态为waiting
	thread->looper |= BINDER_LOOPER_STATE_WAITING;
	... ...
	// 是否以非阻塞的方式打开驱动文件
	// ProcessState::open_driver函数中,打开时标记设置为O_RDWR|O_CLOEXEC,因此这里non_block为false
	if (non_block) {
		if (!binder_has_work(thread, wait_for_proc_work))
			ret = -EAGAIN;
	} else {
		// 当前线程或将进入休眠,等待唤醒
		ret = binder_wait_for_work(thread, wait_for_proc_work);
	}
	... ...
}

我们先停一下,进入binder_wait_for_work看看。这个函数决定了当前线程是否主动让出CPU,进入休眠。

c 复制代码
// common/drivers/android/binder.c

static int binder_wait_for_work(struct binder_thread *thread,
				bool do_proc_work) {
	//DEFINE_WAIT宏创建一个名为wait的wait_queue_entry(内核等待队列机制中的项)
	DEFINE_WAIT(wait);
	struct binder_proc *proc = thread->proc;
	int ret = 0;

	binder_inner_proc_lock(proc);
	for (;;) {
		// 将wait(wait_queue_entry)加入binder_thread的等待队列中
		prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE|TASK_FREEZABLE);
		// 确认binder_thread是否有可以做的工作项,若有,则立即跳出循环,wait出队
		if (binder_has_work_ilocked(thread, do_proc_work))
			break;
		// 加入到proc->waiting_threads等待队列中
		if (do_proc_work)
			list_add(&thread->waiting_thread_node,
				 &proc->waiting_threads);
		trace_android_vh_binder_wait_for_work(do_proc_work, thread, proc);
		binder_inner_proc_unlock(proc);
		// 让出CPU,当前线程进入休眠。当再次被唤醒时,程序将从下一行开始执行
		schedule();
		// 恢复执行,再次加锁
		binder_inner_proc_lock(proc);
		// 从proc->waiting_threads中将binder_thread删除
		list_del_init(&thread->waiting_thread_node);
		// 唤醒之后检查一下当前是否有信号需要处理,若有,则将系统调用返回值设置为-EINTR
		// 正如第9篇中我们讲到的IPCThreadState::talkWithDriver中调用ioctl的while循环重复的条件是err == -EINTR
		// 也就是说之前系统调用被打断了,重新调用ioctl
		if (signal_pending(current)) {
			ret = -EINTR;
			break;
		}
	}
	finish_wait(&thread->wait, &wait);
	binder_inner_proc_unlock(proc);

	return ret;
}

通常情况下,Client端发出请求后,就会在schedule()那行停下来,等Server端完成请求后,驱动再唤醒Client侧。这与我们日常开发APP时在上层的体验一致,调用同步Binder接口(非oneway)若Server端迟迟不回复,那么Client侧会卡死。因此,我们通常不会在主线程调用同步Binder接口。

接收回复

当Client侧线程被唤醒后,此时binder_thread的todo队列中,已经有了2个binder_work,依次为BINDER_WORK_TRANSACTION_COMPLETE(BR_TRANSACTION_COMPLETE)BINDER_WORK_TRANSACTION(BR_REPLY)。那么后续流程如下:

处理BINDER_WORK_TRANSACTION_COMPLETE

c 复制代码
// common/drivers/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      binder_uintptr_t binder_buffer, size_t size,
			      binder_size_t *consumed, int non_block) {
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;

	... ...
	// 解除线程等待标志位
	thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

	if (ret)
		return ret;

	while (1) {
		uint32_t cmd;
		struct binder_transaction_data_secctx tr;
		struct binder_transaction_data *trd = &tr.transaction_data;
		struct binder_work *w = NULL;
		struct list_head *list = NULL;
		struct binder_transaction *t = NULL;
		struct binder_thread *t_from;
		size_t trsize = sizeof(*trd);

		binder_inner_proc_lock(proc);
		... ...
		// 将todo队列赋给list
		if (!binder_worklist_empty_ilocked(&thread->todo))
			// binder_thread的todo队列中有新的工作项
			list = &thread->todo;
		else if (!binder_worklist_empty_ilocked(&proc->todo) &&
			   wait_for_proc_work)
			// binder_proc的todo队列中有新的工作项
			list = &proc->todo;
		else {
no_work:
			binder_inner_proc_unlock(proc);
			// 缓冲区中只有4字节,即函数开头写入的通信码BR_NOOP,那么没有需要处理的,跳到retry继续执行
			if (ptr - buffer == 4 && !thread->looper_need_return)
				goto retry;
			break;
		}

skip:
		// 传入的参数大小不正确
		if (end - ptr < sizeof(tr) + 4) {
			binder_inner_proc_unlock(proc);
			break;
		}
		trace_android_vh_binder_thread_read(&list, proc, thread);
		// 取出一个工作项
		w = binder_dequeue_work_head_ilocked(list);
		if (binder_worklist_empty_ilocked(&thread->todo))
			// 如果binder_thread的todo队列空了,标记一下
			thread->process_todo = false;

		switch (w->type) {
			case BINDER_WORK_TRANSACTION_COMPLETE:
			case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT: {
				// 将binder_work工作项的类型,转换为通信码
				if (proc->oneway_spam_detection_enabled &&
					   w->type == BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT)
					cmd = BR_ONEWAY_SPAM_SUSPECT;
				else
					cmd = BR_TRANSACTION_COMPLETE;
				binder_inner_proc_unlock(proc);
				// 释放binder_work占用的内核空间
				kfree(w);
				binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
				// 通信码写入用户空间
				if (put_user(cmd, (uint32_t __user *)ptr))
					return -EFAULT;
				ptr += sizeof(uint32_t);
				// 更新统计数据,打印调试日志
				... ...
			} break;
			... ...
		}
		// 以上流程中t未被赋值,因此为NULL,那么返回循环开始,继续执行
		if (!t)
			continue;
		... ...
	}
}

再次回到while开头,这次取出的是 BINDER_WORK_TRANSACTION

c 复制代码
// common/drivers/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      binder_uintptr_t binder_buffer, size_t size,
			      binder_size_t *consumed, int non_block) {
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;

	... ...

	while (1) {
		struct binder_work *w = NULL;
		... ...
		w = binder_dequeue_work_head_ilocked(list);
		... ...
		switch (w->type) {
			case BINDER_WORK_TRANSACTION: {
				binder_inner_proc_unlock(proc);
				// container_of宏根据结构体(binder_transaction)中的字段(work)和字段值(w)反向找到结构体指针
				t = container_of(w, struct binder_transaction, work);
			} break;
			... ...
		}
		// 上面的代码通过w的值,找到binder_transaction并赋值给了t
		// 因此,这次不会返回循环开始,而是继续向下执行
		if (!t)
			continue;

		// 此时的t->buffer是Server端发起BC_REPLY时创建的,target_node不会被赋值,所以为NULL
		// 因此,我们只看else分支
		if (t->buffer->target_node) {
			... ...
		} else {
			// 将本地指针置为0,通信码设置为BR_REPLY
			trd->target.ptr = 0;
			trd->cookie = 0;
			cmd = BR_REPLY;
		}
		// 设置binder_transaction_data中剩余的字段
		trd->code = t->code;
		trd->flags = t->flags;
		trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid);

		// 从binder_transaction中取出用来接收Server端回复的线程
		// 可以看看binder_transaction函数的分析,我们之前提到过
		t_from = binder_get_txn_from(t);
		if (t_from) {
			struct task_struct *sender = t_from->proc->tsk;
			// 找到发送方的PID
			trd->sender_pid =
				task_tgid_nr_ns(sender,
						task_active_pid_ns(current));
			trace_android_vh_sync_txn_recvd(thread->task, t_from->task);
		} else {
			trd->sender_pid = 0;
		}
		... ...
		// 将需要Client端读取的数据区指针赋给trd变量
		trd->data_size = t->buffer->data_size;
		trd->offsets_size = t->buffer->offsets_size;
		trd->data.ptr.buffer = (uintptr_t)t->buffer->user_data;
		trd->data.ptr.offsets = trd->data.ptr.buffer +
					ALIGN(t->buffer->data_size,
					    sizeof(void *));
		... ...
		// 写入通信码,当前语境下是BR_REPLY
		if (put_user(cmd, (uint32_t __user *)ptr)) {
			// 写入失败的异常处理
			... ...
		}
		
		ptr += sizeof(uint32_t);
		// 将binder_transaction_data(需要client读取的)拷贝至用户空间
		if (copy_to_user(ptr, &tr, trsize)) {
			// 写入失败的异常处理
			... ...
		}
		ptr += trsize;
		... ...
		// 一次完整的binder_transaction结束,后续代码都是扫尾工作
		// 减少binder_thread引用计数
		if (t_from)
			binder_thread_dec_tmpref(t_from);
		//标记binder_buffer为允许释放
		t->buffer->allow_user_free = 1;
		if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) {
			... ...
		} else {
			// 释放binder_transaction占用空间及相关资源
			binder_free_transaction(t);
		}
		break;
	}
done:
	// 更新驱动向数据区写入了多少数据
	*consumed = ptr - buffer;
	... ...
	return 0;
}

至此,Client端的binder_thread_read 全部结束,回到binder_ioctl_write_read 函数中,将更新后的binder_write_read拷贝到用户空间,层层向上返回,最终系统调用ioctl将调用结果状态返回到用户空间,整个驱动的操作就结束了。回到IPCThreadState::waitForResponse后,依次读出 BR_NOOPBR_TRANSACTION_COMPLETEBR_REPLY 进行上层处理,这些操作跟驱动无关,也比较简单,就不再赘述了。

Server接收请求和发起回复

Binde线程启动即休眠

通过前面的探索我们已经知道Binder线程在启动后,会按照以下调用链进入内核态: IPCThreadState::getAndExecuteCommand | ---> IPCThreadState::talkWithDriver | ---> ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)

此时bwr中write_size为0,read_size为mIn.dataCapacity()(即256byte)。因此在binder_ioctl_write_read函数中,不会调用binder_thread_write,直接进入binder_thread_read。

c 复制代码
// common/drivers/android/binder.c

static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread) {
	... ...
	// write_size为0,因此跳过此分支
	if (bwr.write_size > 0) {
		... ...
	}
	if (bwr.read_size > 0) {
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		... ...
	}
	... ...
out:
	return ret;
}

Binder线程启动后并无需要处理的工作项,那么它会在binder_wait_for_work函数中进入休眠。若有疑问,请回看上一小节关于binder_wait_for_work函数的分析。

Binder线程唤醒

由前面的分析得知,binder_proc_transaction函数中会将一个binder_work(BINDER_WORK_TRANSACTION类型)投入到目标Binder线程的todo队列中,那么当它被唤醒后,将跳出binder_wait_for_work函数,继续后面的代码。

c 复制代码
// common/drivers/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      binder_uintptr_t binder_buffer, size_t size,
			      binder_size_t *consumed, int non_block) {
	... ...
	if (non_block) {
		if (!binder_has_work(thread, wait_for_proc_work))
			ret = -EAGAIN;
	} else {
		ret = binder_wait_for_work(thread, wait_for_proc_work);
	}
	// Binder线程被唤醒后,从这里继续执行
	thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

	if (ret)
		return ret;

	while (1) {
		uint32_t cmd;
		struct binder_transaction_data_secctx tr;
		struct binder_transaction_data *trd = &tr.transaction_data;
		struct binder_work *w = NULL;
		struct list_head *list = NULL;
		struct binder_transaction *t = NULL;
		struct binder_thread *t_from;
		size_t trsize = sizeof(*trd);

		... ...
		// 取出binder_work(BINDER_WORK_TRANSACTION类型)
		w = binder_dequeue_work_head_ilocked(list);
		if (binder_worklist_empty_ilocked(&thread->todo))
			thread->process_todo = false;
		switch (w->type) {
			case BINDER_WORK_TRANSACTION: {
				// 通过binder_work,找到binder_transaction
				t = container_of(w, struct binder_transaction, work);
			} break;
		}
		... ...
		// target_node为通过handle值找到的目标节点
		if (t->buffer->target_node) {
			// 给binder_transaction_data赋值
			struct binder_node *target_node = t->buffer->target_node;
			trd->target.ptr = target_node->ptr;
			trd->cookie =  target_node->cookie;
			binder_transaction_priority(thread, t, target_node);
			// 设置通信码
			cmd = BR_TRANSACTION;
		} else {
			... ...
		}
		// 后续就是给binder_transaction_data剩余字段赋值,和上一小节的内容重复,不再赘述
		... ...
		t_from = binder_get_txn_from(t);
		... ...
		// 写入通信码,当前语境下是BR_TRANSACTION
		if (put_user(cmd, (uint32_t __user *)ptr)) {
			// 写入失败的异常处理
			... ...
		}
		ptr += sizeof(uint32_t);
		// 拷贝binder_transaction_data到用户空间
		if (copy_to_user(ptr, &tr, trsize)) {
			// 拷贝失败的异常处理
			... ...
		}
		ptr += trsize;
		... ...
		// 当前语境下cmd为BR_TRANSACTION且为同步通信(非Oneway)
		if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) {
			binder_inner_proc_lock(thread->proc);
			// 将Binder线程的事务栈顶元素赋给t->to_parent
			t->to_parent = thread->transaction_stack;
			// 将当前Binder线程保存到t->to_thread
			t->to_thread = thread;
			// 将当前事务保存到当前Binder线程的事务栈顶
			thread->transaction_stack = t;
			binder_inner_proc_unlock(thread->proc);
		} else {
			... ...
		}
		break;
	}
	... ...
}

至此,Server端的binder_thread_read 结束,回到binder_ioctl_write_read函数中,将更新后的binder_write_read拷贝到用户空间,层层向上返回,最终系统调用ioctl将调用结果状态返回到用户空间。

用户空间将调用Server端具体的实现函数,调用链如下: IPCThreadState::executeCommand | ---> BBinder::transact | ---> JavaBBinder::onTransact | ---> env->CallBooleanMethod(... gBinderOffsets.mExecTransact ...) | ---> Binder.java::execTransact | ---> Binder.java::execTransactInternal | ---> Binder.java::onTransact

Binder线程发起回复

Java层的Binder::onTransact实现函数完成操作后,IPCThreadState将调用sendReply进入回复阶段。

c 复制代码
// frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags) {
    status_t err;
    status_t statusBuffer;
	// 写入通信码BC_REPLY
    err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer);
    if (err < NO_ERROR) return err;

    return waitForResponse(nullptr, nullptr);
}

此后调用流程和Client端发起请求时一样,ioctl -> binder_ioctl -> binder_ioctl_write_read -> binder_thread_write -> binder_transaction。

c 复制代码
// common/drivers/android/binder.c

static int binder_thread_write(struct binder_proc *proc,
			struct binder_thread *thread,
			binder_uintptr_t binder_buffer, size_t size,
			binder_size_t *consumed) {
	... ...
	while (ptr < end && thread->return_error.cmd == BR_OK) {
		int ret;
		if (get_user(cmd, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
		... ...
		switch (cmd) {
			... ...
			case BC_REPLY: {
				struct binder_transaction_data tr;
				if (copy_from_user(&tr, ptr, sizeof(tr)))
					return -EFAULT;
				ptr += sizeof(tr);
				binder_transaction(proc, thread, &tr,
						   cmd == BC_REPLY, 0);
				break;
			}
			... ...
		}
		*consumed = ptr - buffer;
	}
	return 0;
}

流程再次来到binder_transaction函数。

c 复制代码
// common/drivers/android/binder.c

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size) {
	... ...
	// 分析client发起请求时,我们看的是else分支,但此时reply为true,所以我们看if分支
	if (reply) {
		binder_inner_proc_lock(proc);
		// 取出在Binder线程被唤醒时保存的事务栈顶元素
		in_reply_to = thread->transaction_stack;
		// 检查对应的事务是否为空,若空,则异常处理
		if (in_reply_to == NULL) {
			... ...
		}
		// 检查取出事务栈顶的事务对应的Binder线程是否为当前线程,若否,则异常处理
		if (in_reply_to->to_thread != thread) {
			... ...
		}
		thread->transaction_stack = in_reply_to->to_parent;
		binder_inner_proc_unlock(proc);
		// 取出client端线程,并赋值给target_thread
		target_thread = binder_get_txn_from_and_acq_inner(in_reply_to);
		... ...
		// 找到client的进程
		target_proc = target_thread->proc;
		target_proc->tmp_ref++;
		binder_inner_proc_unlock(target_thread->proc);
		trace_android_vh_binder_reply(target_proc, proc, thread, tr);
	} else {
		... ...
	}
	... ...
	// 映射内存并拷贝数据,这里和Client发起请求时一样,只是这次是Server端将数据拷贝到Client端的用户空间中
	... ...
	if (t->buffer->oneway_spam_suspect)
		tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;
	else
		tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
	t->work.type = BINDER_WORK_TRANSACTION;

	if (reply) {
		// 将tcomplete工作项放入Binder线程的todo队列中
		binder_enqueue_thread_work(thread, tcomplete);
		... ...
		// 检查一下client端线程是否还活着
		if (target_thread->is_dead) {
			// 异常处理
			... ...
		}
		... ...
		// client端线程的事务栈元素出栈
		binder_pop_transaction_ilocked(target_thread, in_reply_to);
		// 将t->work工作项放入client端线程的todo队列中
		binder_enqueue_thread_work_ilocked(target_thread, &t->work);
		... ...
		// 唤醒client端线程
		wake_up_interruptible_sync(&target_thread->wait);
		... ...
		// 释放binder_transaction占用空间及相关资源
		binder_free_transaction(in_reply_to);
	} else if (!(t->flags & TF_ONE_WAY)) {
		... ...
	} else {
		... ...
	}
	... ...
	return;
}

至此,唤醒client端线程后,Binder线程基本完成了本次任务,当然,最后还有一个tcomplete工作项在todo队列中,完成它之后,Binder线程再次进入binder_thread_read函数中并进入休眠,等待有缘人再次唤醒。

总结

本篇我们梳理了一次同步通信在内核中的整个流程。还有很多ioctl的控制命令我们并没有覆盖,不过剩余的代码都不会太复杂,有需要再查阅。最后奉上一张流程图。

相关推荐
CV资深专家4 小时前
在 Android 框架中,接口的可见性规则
android
daifgFuture8 小时前
Android 3D球形水平圆形旋转,旋转动态更换图片
android·3d
二流小码农9 小时前
鸿蒙开发:loading动画的几种实现方式
android·ios·harmonyos
爱吃西红柿!10 小时前
fastadmin fildList 动态下拉框默认选中
android·前端·javascript
悠哉清闲10 小时前
工厂模式与多态结合
android·java
大耳猫11 小时前
Android SharedFlow 详解
android·kotlin·sharedflow
火柴就是我12 小时前
升级 Android Studio 后报错 Error loading build artifacts from redirect.txt
android
androidwork13 小时前
掌握 MotionLayout:交互动画开发
android·kotlin·交互
GoGeekBaird13 小时前
69天探索操作系统-第68天:从用户到内核:实现动态系统调用处理以构建健壮的操作系统
后端·操作系统
奔跑吧 android13 小时前
【android bluetooth 协议分析 14】【HFP详解 1】【案例一: 手机侧显示来电,但车机侧没有显示来电: 讲解AT+CLCC命令】
android·hfp·aosp13·telecom·ag·hf·headsetclient