衔接上文Binder驱动之设备控制 – 概述&数据结构 。这篇文章将深入Binder驱动实现进程间通信的核心,揭开Binder通信的神秘面纱, 😃
1. binder_ioctl
Binder驱动没有提供read/write接口用于读写操作,所有数据传输、控制都是通过binder_ioctl
进行,因此该部分是Binder通信的核心内容,承载了Binder数据传输部分的主要业务。这一节我们就一起来看看Binder进程间通信驱动层总入口 ---- binder_ioctl
的实现。
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
/*读取命令的大小*/
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
trace_binder_ioctl(cmd, arg);
/* 如果binder_stop_on_user_error < 2 则直接返回0;
* 否则,调用_wait_event_interruptible进入可中断的挂起状态,接着让出处理器,
* 直到被wake_up且条件(binder_stop_on_user_error < 2)为真时才返回
*/
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
goto err_unlocked;
/*获取binder_main_lock锁*/
binder_lock(__func__);
/*在proc->threads红黑树中查找thread,该红黑树以pid为序,具体详见1.1*/
thread = **binder_get_thread**(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
/*根据不同的命令,调用不同的处理函数进行处理,后文将对命令依次分析*/
switch (cmd) {
case BINDER_WRITE_READ:
/*读写命令,数据传输,binder IPC通信的核心逻辑,详见2.1 */
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
case BINDER_SET_MAX_THREADS:
/*设置最大线程数,直接将值设置到proc结构的max_threads域中。*/
if (copy_from_user(&**proc->max_threads**, ubuf, sizeof(proc->max_threads))) {
ret = -EINVAL;
goto err;
}
break;
case BINDER_SET_CONTEXT_MGR:
/*设置Context manager,即将自己设置为ServiceManager,详见2.2 */
ret = binder_ioctl_set_ctx_mgr(filp);
if (ret)
goto err;
break;
case BINDER_THREAD_EXIT:
/*binder线程退出命令,释放相关资源,详见2.3*/
binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
proc->pid, thread->pid);
binder_free_thread(proc, thread);
thread = NULL;
break;
case BINDER_VERSION: {
/*获取binder驱动版本号,在kernel4.4版本中,32位该值为7,64位版本该值为8*/
struct binder_version __user *ver = ubuf;
if (size != sizeof(struct binder_version)) {
ret = -EINVAL;
goto err;
}
/*将版本号信息写入用户态地址空间struct binder_version的protocol_version中*/
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
&ver->protocol_version)) {
ret = -EINVAL;
goto err;
}
break;
}
default:
ret = -EINVAL;
goto err;
}
ret = 0;
err:
if (thread)
thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
binder_unlock(__func__); /*释放binder_main_lock锁*/
wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret && ret != -ERESTARTSYS)
pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
trace_binder_ioctl_done(ret);
return ret;
}
-
_IOC_SIZE读取参数命令的大小。在32位的体系架构中,参数
cmd
由四个域组成:- 读写属性域(
direction: read/write
), 区分是读取命令,还是写入命令。bit30~bit31,占2bit。可用宏_IOC_DIR
读取。 - 数据大小域(
size : size of argument
), 表示ioctl
中arg
变量所指内存区域占用内存大小。bit16~bit29,占14bit。可用宏_IOC_SIZE
读取 。 - 魔数域 (
type:usually related to the major number
),又叫“幻数”区,用来区分不同的设备驱动,当传入的值与自身的值不一致时则不进行进一步处理,是用于防止误使用的一种状态标识位。一般用字母A~Z
或者a~z
表示。bit8~bit15,占8bit。可用宏_IOC_TYPE
读取。 - 序号数或者基数(
command
):用于区分各种命令。bit0~bit7,占8bit。可用宏_IOC_NR
读取。
- 读写属性域(
-
binder_stop_on_user_error
, 该变量是一个全局静态变量, 它的值通过模块参数stop_on_user_error
控制,当系统出现问题时,可以通过将该值设置为一个大于或等于2的值,来暂停binder,来进行debug。模块参数的设置可以在模块插入时以参数传递的形式设定,如insmod xxx.ko arg=xxxx
形式;如果权限设置允许的话,也可以通过sysfs来动态设置(如echo 3 > /sys/module/binder/parameters/stop_no_user_error
)。相关代码如下:
static int binder_stop_on_user_error;
/*定义模块参数`stop_on_user_error`的设置函数*/
static int binder_set_stop_on_user_error(const char *val,
struct kernel_param *kp)
{
int ret;
ret = param_set_int(val, kp);/*将sysfs中/sys/module/binder/parameters/stop_on_user_error读入binder_stop_on_user_error*/
if (binder_stop_on_user_error < 2)
wake_up(&binder_user_error_wait);
return ret;
}
module_param_call(stop_on_user_error/*模块参数名字,所在路径为:/sys/module/binder/parameters/stop_on_user_error*/,
binder_set_stop_on_user_error /*模块参数`stop_on_user_error`的set函数*/,
param_get_int/*模块参数`stop_on_user_error`的读取函数*/,
&binder_stop_on_user_error/*模块参数对应的变量地址*/,
S_IWUSR | S_IRUGO /*在sysfs中的权限设置*/);
module_param_call
该宏用于定义一个内核模块参数,它的定义为module_param_call(name, set, get, arg, perm)
,其中:name
:内核模块参数的名字,也是在sysfs中显示的名字;set
:是该内核模块参数设定的回调函数,当在插入模式时传递参数或者通过sysfs设定内核模块参数时,该函数会被调用;get
: 是该内核模块参数读取的回调函数;arg
:内核模块参数的地址;perm
:该内核模块参数的权限设置,可以在sysfs中看到。
对于基本的数据类型的读取和设定回调函数,内核已经预先做了定义,一般形式为:param_get_xxx
和param_set_xxx
,xxx
是int
, short
等。可以参考一下这篇博客,Linux内核模块的编写方法和技巧。
- 额外提一下sysfs,它是Linux2.6开始提供的一种虚拟文件系统,设计该文件系统的目的是把原本在procfs关于设备的部分独立出来,以“设备层次结构架构(device tree)”的形式呈现。它可以把设备和驱动程序的信息从内核输出到用户空间,也可以对设备和驱动程序做设置。具体详见sysfs简介。
1.1 查找thread — binder_get_thread
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
struct binder_thread *thread = NULL;
struct rb_node *parent = NULL;
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,不是进程的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;
/*更新thread创建统计计数*/
binder_stats_created(BINDER_STAT_THREAD);
/*初始化相关数据成员*/
thread->proc = proc;
thread->pid = current->pid; /*获取线程id*/
init_waitqueue_head(&thread->wait); /*初始化等待队列*/
INIT_LIST_HEAD(&thread->todo); /*初始化待处理队列*/
rb_link_node(&thread->rb_node, parent, p); /*加入到proc的threads红黑树中*/
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;
}
2. binder_ioctl
命令处理逻辑
2.1 读写命令 – BINDER_WRITE_READ
首先我们来看一下BINDER_WRITE_READ
的定义如下,它是通过调用内核提供的_IOWR
宏来构造。关于ioctl
命令的构造方法,有兴趣可以看看这篇文章, 基本讲清楚了。 —— 构造IOCTL学习心得.
#define BINDER_WRITE_READ _IOWR(‘b’/*type 魔数域*/, 1/*command 序号数*/, struct binder_write_read/*size:用来求数据大小域*/)
如果传入的命令是BINDER_WRITE_READ
,则直接调用binder_ioctl_write_read
进行处理。
2.1.1 命令处理函数binder_ioctl_write_read
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;
/*读取arg的大小,通过2.1节它可知它大小应为 sizeof(struct binder_write_read) */
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto out;
}
/* 从用户态地址读取struct binder_write_read结构体 */
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
binder_debug(BINDER_DEBUG_READ_WRITE,
"%d:%d write %lld at %016llx, read %lld at %016llx\n",
proc->pid, thread->pid,
(u64)bwr.write_size, (u64)bwr.write_buffer,
(u64)bwr.read_size, (u64)bwr.read_buffer);
/* write_size大于0,表示用户进程有数据发送到驱动,则调用binder_thread_write发送数据 详见:2.1.2*/
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);
if (ret < 0) {
/*binder_thread_write中有错误发生,则read_consumed设为0,表示kernel没有数据返回给进程*/
bwr.read_consumed = 0;
/*将bwr返回给用户态调用者,bwr在binder_thread_write中会被修改*/
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
/*read_size大于0, 表示进程用户态地址空间希望有数据返回给它,则调用binder_thread_read进行处理*/
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**);
trace_binder_read_done(ret);
/*读取完后,如果proc->todo链表不为空,则唤醒在proc->wait等待队列上的进程*/
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait);
if (ret < 0) {
/*如果binder_thread_read返回小于0,可能处理一半就中断了,需要将bwr拷贝回进程的用户态地址*/
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
binder_debug(BINDER_DEBUG_READ_WRITE,
"%d:%d wrote %lld of %lld, read return %lld of %lld\n",
proc->pid, thread->pid,
(u64)bwr.write_consumed, (u64)bwr.write_size,
(u64)bwr.read_consumed, (u64)bwr.read_size);
/* 处理成功的情况,也需要将bwr拷贝回进程的用户态地址空间*/
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
out:
return ret;
}
-
binder_ioctl_write_read
整个处理逻辑相对简单,它首先从arg
中读取用户态传进来的struct binder_write_read
结构体,然后根据其成员变量write_size
和read_size
是否大于0,判断本次调用是读操作还是写操作。- 如果
write_size
大于0,则为写操作,调用binder_thread_write
处理发送请求。 - 否则为读操作,调用
binder_thread_read
处理接收请求。
- 如果
-
这里要注意的一点是:在
binder_thread_write
和binder_thread_read
的调用中,有两个参数bwr.write_consumed
和bwr.read_consumed
是**传址*参数,这意味着这两个成员变量是会在被调用函数中修改的。如果在binder_thread_write
处理过程中出错(返回值小于0),则不再处理read_size
大于0的情况。最后不管是成功还是失败,都会将bwr
通过copy_to_user
返回给进程,进程可以通过write_consumed
和read_consumed
字段得知驱动读取和写入多少字节的数据。其实上述三处的copy_to_user
调用,其实可以统一合并到最后一处,然后将out
跳转标签移到最后一处之前,其他两处的copy_to_user
直接移除即可,以减少重复代码。
2.1.2 数据发送 — binder_thread_write
这个函数代码量比较大,我们需要分段来看,从前往后,我们先看函数的开始部分:
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;
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 == BR_OK) {
/*从用户态地址空间bwr的write_buffer中读取一个32位无符号整型到cmd*/
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
/*指针后移4个字节*/
ptr += sizeof(uint32_t);
trace_binder_command(cmd);
/*更新该cmd相关的统计信息*/
if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
binder_stats.bc[_IOC_NR(cmd)]++;
proc->stats.bc[_IOC_NR(cmd)]++;
thread->stats.bc[_IOC_NR(cmd)]++;
}
switch (cmd) {
- 函数开始先确定了写缓冲区中开始(
ptr
)和结束的位置(end
)的位置,接着就开始进入循环,读取命令,更新相关统计信息。然后进入switch
分支根据不同的命令类型处理执行相应的处理。
下面我们就来看看,具体的命令处理流程。
2.1.2.1 增减binder_ref
强弱引用计数的四个命令
/*增加或者减少强(BC_ACQUIRE,BC_RELEASE),弱(BC_INCREFS, BC_DECREFS)引用计数*/
case BC_INCREFS:
case BC_ACQUIRE:
case BC_RELEASE:
case BC_DECREFS: {
uint32_t target;
struct binder_ref *ref;
const char *debug_string;
/*从传入参数的用户态地址中读取想要修改引用计数的struct binder_ref的目标handle*/
if (get_user(target, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
if (target == 0 && binder_context_mgr_node &&
(cmd == BC_INCREFS || cmd == BC_ACQUIRE)) {
/* - 如果是想请求增加ServiceManager的强或弱的binder_ref引用,**binder_get_ref_for_node**会先在proc的refs_by_node红黑树中查找,
* desc域等于target的binder_ref。如果有找到,就返回找到的binfer_buf;如果没有找到,就新创建一个并插入到`proc->ref_by_node`红黑树中。
* 还要为新创建的节点通过`rb_node_desc`域加入到`proc->refs_by_desc`红黑树中。
*
*- `refs_by_desc`红黑树是以`binder_buf`中的desc为序组织的,新创建节点的`desc`的值是该`proc`的`refs_by_desc`红黑树中最小的且还未被使用值,
* 即如果引用的`binder_node`是`binder_context_mgr_node`则是0,其他的就是1开始最小的还没被其他节点使用的值。最后还要将新创建的节点
* 通过其`node_entry`域,链入`binder_context_mgr_node`的`refs`哈希链表中。
*/
ref = binder_get_ref_for_node(proc,
binder_context_mgr_node);
if (ref->desc != target) {
binder_user_error("%d:%d tried to acquire reference to desc 0, got %d instead\n",
proc->pid, thread->pid,
ref->desc);
}
} else
/* 与binder_get_ref_for_node类似,也是在proc->refs_by_node红黑树中查找desc域等于target的binder_ref
* 但是如果没找到,不会创建新的binder_ref节点,而是直接返回NULL
*/
ref = binder_get_ref(proc, target);
if (ref == NULL) {
binder_user_error("%d:%d refcount change on invalid ref %d\n",
proc->pid, thread->pid, target);
break;
}
switch (cmd) {
/* 进一步区分增/减 强弱引用命令 */
case BC_INCREFS:
debug_string = "IncRefs";
binder_inc_ref(ref, 0, NULL );/* 增加弱引用计数(ref->weak--) */
break;
case BC_ACQUIRE:
debug_string = "Acquire";
/* 增加强引用计数(ref->strong++)。如果增加前strong的值为0,则还需要增加其所对应(引用)
* binder_node节点的internal_strong_refs的值
*/
binder_inc_ref(ref, 1, NULL);
break;
case BC_RELEASE:
debug_string = "Release";
/* 减少强引用计数(ref->strong--)。如果减少后strong的值为0,则还需要减少其所对应(引用)
* binder_node节点的internal_strong_refs的值。
* strong减完后,如果发现此时strong和weak都为0,还要删除该binder_ref节点
*/
binder_dec_ref(ref, 1);
break;
case BC_DECREFS:
default:
debug_string = "DecRefs";
/* 减少弱引用计数(ref->weak—)。减完后,如果发现此时strong和weak都为0,还要删除该binder_ref节点*/
binder_dec_ref(ref, 0);
break;
}
binder_debug(BINDER_DEBUG_USER_REFS,
"%d:%d %s ref %d desc %d s %d w %d for node %d\n",
proc->pid, thread->pid, debug_string, ref->debug_id,
ref->desc, ref->strong, ref->weak, ref->node->debug_id);
break;
} /*到这里BC_INCREFS, BC_ACQUIRE, BC_RELEASE, BC_DECREFS四个命令处理结束*/
....../*其他命令的处理*/
- 第一个处理的是以下四个用于增加或者减少client端的驱动层表示
binder_ref
的强弱引用计数的命令BC_INCREFS
: 增加binder_ref的弱引用计数。如果是第一次增加(即,ref->weak == 0),还会去增加对应的binder_node的弱引用计数。它是在用户态进程的BpBinder
的构造函数中,通过调用IPCThreadState
的incWeakHandle
发出。BC_DECREFS
:减少binder_ref的弱引用计数,但不会去减少对应binder_node的弱引用计数,即使ref->weak == 0,这与强引用计数的处理是不同的。在BpBinder
的析构函数中,通过调用IPCThreadState
的decWeakHandle
发出。BC_ACQUIRE
:增加binder_ref的强引用计数。如果是第一次增加(即,ref->strong == 0),还会去增加对应的binder_node的强引用计数。在BpBinder
的onFirstRef
函数中,通过调用IPCThreadState
的incStrongHandle
发出。BC_RELEASE
:减少binder_ref的强引用计数。如果减少后强引用计数为0(即,ref->strong == 0),还会去减少对应的binder_node的强引用计数。在BpBinder
的onLastStrongRef
函数中,通过调用IPCThreadState
的decStrongHandle
发出。
如果减少强或弱引用计数后,发现强弱引用计数都变为0,则会调用binder_delete_ref
删除对应的binder_ref
。
2.1.2.2 BC_INCREFS_DONE
和BC_ACQUIRE_DONE
BC_INCREFS_DONE
和BC_ACQUIRE_DONE
两个命令分别是进程用户态在处理完对应的BR_INCREFS
和BR_ACQUIRE
回复Binder驱动的两个命令。关于后面两个命令BR_INCREFS
和BR_ACQUIRE
分别用于Binder驱动请求进程用户态增加IPCThreadState
中的mProcess
成员的(类型为:ProcessState
)弱引用和强引用计数。
case BC_INCREFS_DONE:
case BC_ACQUIRE_DONE: {
/*说明此时传入的是一个flat_binder_object*/
binder_uintptr_t node_ptr;
binder_uintptr_t cookie;
struct binder_node *node;
/* 从进程用户态地址空间中读取BBinder对象的弱引用计数器成员mRefs的地址
* BBinder继承自IBinder,后者继承自RefBase,mRefs为RefBase的类型为weakref_impl的对象
*/
if (get_user(node_ptr, (binder_uintptr_t __user *)ptr))
return -EFAULT;
ptr += sizeof(binder_uintptr_t);
/*从进程用户态地址空间中读取`BBinder`对象的地址,放到cookie变量中*/
if (get_user(cookie, (binder_uintptr_t __user *)ptr))
return -EFAULT;
ptr += sizeof(binder_uintptr_t);
/*根据之前读取的node_ptr,在proc中的nodes红黑树中查找对应的binder_node*/
node = binder_get_node(proc, node_ptr);
if (node