Binder驱动之进程间通信及设置

衔接上文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), 表示ioctlarg变量所指内存区域占用内存大小。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_xxxparam_set_xxxxxxint, 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_sizeread_size是否大于0,判断本次调用是读操作还是写操作。

    • 如果write_size大于0,则为写操作,调用binder_thread_write处理发送请求。
    • 否则为读操作,调用binder_thread_read处理接收请求。
  • 这里要注意的一点是:在binder_thread_writebinder_thread_read的调用中,有两个参数bwr.write_consumedbwr.read_consumed是**传址*参数,这意味着这两个成员变量是会在被调用函数中修改的。如果在binder_thread_write处理过程中出错(返回值小于0),则不再处理read_size大于0的情况。最后不管是成功还是失败,都会将bwr通过copy_to_user返回给进程,进程可以通过write_consumedread_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构造函数中,通过调用IPCThreadStateincWeakHandle发出。
    • BC_DECREFS:减少binder_ref的弱引用计数,但不会去减少对应binder_node的弱引用计数,即使ref->weak == 0,这与强引用计数的处理是不同的。在BpBinder析构函数中,通过调用IPCThreadStatedecWeakHandle发出。
    • BC_ACQUIRE:增加binder_ref的强引用计数。如果是第一次增加(即,ref->strong == 0),还会去增加对应的binder_node的强引用计数。在BpBinderonFirstRef函数中,通过调用IPCThreadStateincStrongHandle发出。
    • BC_RELEASE:减少binder_ref的强引用计数。如果减少后强引用计数为0(即,ref->strong == 0),还会去减少对应的binder_node的强引用计数。在BpBinderonLastStrongRef函数中,通过调用IPCThreadStatedecStrongHandle发出。
      如果减少强或弱引用计数后,发现强弱引用计数都变为0,则会调用binder_delete_ref删除对应的binder_ref
2.1.2.2 BC_INCREFS_DONEBC_ACQUIRE_DONE
  • BC_INCREFS_DONEBC_ACQUIRE_DONE两个命令分别是进程用户态在处理完对应的BR_INCREFSBR_ACQUIRE回复Binder驱动的两个命令。关于后面两个命令BR_INCREFSBR_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 
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android进程间通信(Inter-process Communication, IPC)是指在不同进程之间进行数据交互和通信的方法。Android提供了多种方式实现进程间通信,以下是常用的几种方式: 1. Binder机制:Binder是一种跨进程通信技术,它基于Linux内核提供的Binder驱动。通过Binder,我们可以将一个Service注册为Binder服务,其他进程可以通过Binder进行远程调用,实现进程间的通信。 2. 文件共享:进程可以通过共享文件的方式实现通信。一个进程将数据写入文件,其他进程读取该文件数据,从而实现进程间的信息传递。 3. Socket通信:可以使用Socket套接字进行进程间通信。一个进程作为服务器,另一个进程作为客户端,通过Socket建立连接进行数据交互。 4. ContentProvider:ContentProviderAndroid中用于实现进程间共享数据的一种组件。通过ContentProvider,一个进程可以提供数据给其他进程进行读写操作。 5. BroadcastReceiver:广播是一种常见的进程间通信方式。一个进程发送广播消息,其他进程通过注册相应的广播接收器来接收并处理广播消息。 6. Messenger:Messenger是一种轻量级的进程间通信方式。通过Messenger,一个进程可以发送消息给另一个进程,实现进程间的通信。 以上是常用的几种Android进程间通信方式,开发者可以根据具体需求选择合适的方式来实现进程间通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值