本文设计源码及逻辑较多,已尽量画图辅助,完全理解可能要耗一定精力,建议收藏。
应用程序中执行 getService() 需与 ServiceManager 通过 binder 跨进程通信,此过程中会贯穿 Framework、Natve 层以及 Linux 内核驱动。
binder 驱动的整体分层如上图,下面先来宏观的了解下 getService() 在整个 Android 系统中的调用栈,ServiceManager 本身的获取:
与 ServiceManager 进行 IPC 通信:
「本文将主要分析此过程中 binder 驱动具体承担了哪些工作」,也就是上图中 IPCThreadState 与 binder 驱动的 ioctl 调用。
binder 驱动中做的工作可以总结为以下几步:
- 准备数据,根据命令分发给具体的方法去处理
- 找到目标进程的相关信息
- 将数据一次拷贝到目标进程所映射的物理内存块
- 记录待处理的任务,唤醒目标线程
- 调用线程进入休眠
- 目标进程直接拿到数据进行处理,处理完后唤醒调用线程
- 调用线程返回处理结果
在源码中实际会执行到的函数主要包括:
- binder_ioctl()
- binder_get_thread()
- binder_ioctl_write_read()
- binder_thread_write()
- binder_transaction()
- binder_thread_read()
下面按照这些 binder 驱动中的函数,以工作步骤为脉络,深入分析驱动中的源码执行逻辑,彻底搞定 binder 驱动!
1.binder_ioctl()
在 IPCThreadState 中通过系统调用 ioctl 陷入系统内核,调用到 binder_ioctl() 方法:
ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)
binder_ioctl() 方法中会根据 BINDER_WRITE_READ、BINDER_SET_MAX_THREADS 等不同 cmd 转调到不同的方法去执行,这里我们只关注 BINDER_WRITE_READ,代码如下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
int ret;
//拿到调用进程在 binder_open() 中记录的 binder_proc
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
binder_lock(__func__);
//获取调用线程 binder_thread
thread = binder_get_thread(proc);
switch (cmd) {
case BINDER_WRITE_READ:
//处理 binder 数据读写,binder IPC 通信的核心逻辑
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
...
}
之前文章介绍过 binder_open() 方法, binder_open() 方法主要做了两个工作:
- 创建及初始化每个进程独有一份的、用来存放 binder 相关数据的 binder_proc 结构体
- 「将 binder_proc 记录起来,方便后续使用」。
正是通过 file 的 private_data 来记录的:
static int binder_open(struct inode *nodp, struct file *filp){
...
filp->private_data = proc;
...
}
拿到调用进程后,进一步通过 binder_get_thread() 方法拿到调用线程,然后就交给 binder_ioctl_write_read() 方法去执行具体的 binder 数据读写了。
可见 binder_ioctl() 方法本身的逻辑非常简单,将数据 arg 透传了出去。
下面分别来看 binder_get_thread()、binder_ioctl_write_read() 这两个方法。
2.binder_get_thread()
static struct binder_thread *binder_get_thread(
struct binder_proc *proc){
struct binder_thread *thread = NULL;
struct rb_node *parent = NULL;
//从 proc 中获取红黑树根节点
struct rb_node **p = &proc->threads.rb_node;
//查找 pid 等于当前线程 id 的thread,该红黑树以 pid 大小为序存放
while (*p) {
parent = *p;
thread = rb_entry(parent, struct binder_thread, rb_node);
//current->pid 是当前调用线程的 id
if (current->pid < thread->pid)
p = &(*p)->rb_left;
else if (current->pid > thread->pid)
p = &(*p)->rb_right;
else
break;
}
if (*p == NULL) {//如果没有找到,则新创建一个
thread = kzalloc(sizeof(*thread), GFP_KERNEL);
if (thread == NULL)
return NULL;
binder_stats_created(BINDER_STAT_THREAD);
thread->proc = proc;
thread->pid = current->pid;
init_waitqueue_head(&thread->wait); //初始化等待队列
INIT_LIST_HEAD(&thread->todo); //初始化待处理队列
//加入到 proc 的 threads 红黑树中
rb_link_node(&thread->rb_node, parent, p);
rb_insert_color(&thread->rb_node, &proc->threads);
thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
thread->return_error = BR_OK;
thread->return_error2 = BR_OK;
}
return thread;
}
binder_thread 是用来描述线程的结构体,binder_get_thread() 方法中逻辑也很简单,首先从调用进程 proc 中查找当前线程是否已被记录,如果找到就直接返回,否则新建一个返回,并记录到 proc 中。
也就是说所有调用 binder_ioctl() 的线程,都会被记录起来。