Binder死磕到底(一):Linux进程通信和service manager进程

目录

一、Linux基础概念

1、Linux内存管理

2、Linux进程通信

二、Binder驱动

1、Binder驱动初始化

2、Binder驱动打开设备文件

3、Binder驱动内存映射

4、Binder驱动如何在用户态和内核态进行数据传递

5、几个重要数据结构

1)、binder_proc进程对象

2)、binder_node实体对象

3)、binder_ref引用对象

4)、binder_thread线程对象

5)、binder相关命令解析

6、Binder驱动命令处理

三、service manager进程

1、service manager进程的启动

2、打开和映射Binder驱动设备

3、向Binder驱动注册成为上下文管理者

4、循环等待Client进程请求

5、观察者模式

6、服务注册终极奥义

1)、service manager向binder驱动请求服务注册

2)、binder驱动处理BC_ACQUIRE消息

7、服务查询终极奥义


Android系统其实是一个建立在Linux内核上面的桌面系统,它直接沿用了Linux内核,即Android的进程调度,内存管理等机制都从Linux内核上面继承过来。因此在介绍Binder

本篇第一章先介绍Linux的一些基础知识,其中跟Android系统的Binder关系比较大的有Linux内存管理和Linux进程隔离等知识点。

本篇第二章就开始正式介绍为Android系提供进程间通信的专用驱动程序binder,它作为一个很重要的android驱动程序,被实现在linux内核中。

参考:Binder的原理和实现一次拷贝的流程

参考:Binder系列—开篇

一、Linux基础概念

在嵌入式普及的今天,通常采用的ARM架构的芯片,例如手机或者平板等这些设备。这里我们就不讨论ARM架构了,这里先介绍一下这些机器的开机流程。

  • 当开启电源,直流电通过晶振的震荡让整个ARM架构的芯片开始运作,程序寄存器PC(从该寄存器取出值,该值为指向代码段的地址,CPU就将会执行该地址存储的代码)会进入中断向量表(被芯片固定在0X00开头前面的一些地址),通过一系列汇编指令进入到uboot(也可能叫做bootloader或者fastboot)。
  • 当uboot初始化好一些硬件资源后,就将启动Linux的内核,进入linux系统中,这个时候会创建linux的几大重要模块,例如内存管理,进程管理等。
  • 当linux的一切准备完毕,就开始创建第一个守护进程init,init进程作为linux系统的用户进程,同时也是Android系统的第一个进程,它开辟了Android系统的native世界。
  • 最后init进程通过rc文件相继创建了zygote进程,它创建了Android系统的万千成员,创建了JVM虚拟机,创建了Java世界,最终创建了框架层和应用程序APP,启动Luncher程序进入桌面。

从上面的流程很容易发现,Android系统的万千成员都是建立在Linux内核创建的第一个守护进程init基础之上,而init又是运行在linux系统的用户空间,所以它和它的子进程都必须要满足Linux系统的基本规则。下面我们就将结束Linux两个比较重要的概念。

参考:Linux 虚拟地址到物理地址转换

参考:Linux内存映射

参考:Linux内存管理机制

1、Linux内存管理

在一个32位的Linux系统中,一个进程可以使用的地址范围为0x00-0xFFFFFFFF,即在这程序中我们定义一个指针,因为指针在32位的系统中占用4个字节,因此能够通过这个指针访问任何该范围的内存地址单元,然而这些内存地址单元目前并没有对应真实的物理内存,因此又叫做虚拟地址空间。根据上面的理论我们可以知道在32位Linux系统中,每个进程都能够访问4G的线性虚拟地址空间(线性就是连续的意思,即能够访问编号0x00 0x01 0x02 ... 0xfffffffe 0xffffffff这些虚拟地址)。因此在Linux系统中每个进程都有拥有4G内存空间,我们不妨想象一下,如果有多个进程那么岂不是4G内存空间的整数倍。哈哈,当然不是这样的,这里说的4G空间完全是一个虚拟的,其实根本就不存在

从宏观上来看Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。

每个进程都可以使用访问0到4G这段线性虚拟地址,Linux系统为了系统的安全为也把4G的线性虚拟地址划分为两部分:即0x00-0xbfffffff(3G)为用户空间(用户态才能访问),0xc0000000-0xffffffff(3G-4G)为内核空间(内核态才能访问)。同样不管内核空间还是用户空间,它们都是虚拟地址,即“可以寻址”4G,意思是虚拟地址的0-3G对于一个进程的用户态可以访问的,而3-4G是只有进程的内核态可以访问的。并不是说这个进程会用满这些空间。

上面可以知道0-4G的线性虚拟空间被分为了用户空间和内核空间。但他们目前都没有指向真实的物理内存,因此就有了分页技术可以实现将一个虚拟地址对应到一个真实的物理内存设备上的某个地址。即当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成,概括地讲,地址转换需要将虚拟地址分段,使每段虚地址都作为一个索引指向页表,而页表项则指向下一级别的页表或者指向最终的物理页面。内存映射其实也是用的上面的分页技术来实现的。

一般而言一个进程虽然拥有3G虚拟内存访问权限,但是实际需要的内存可能很少。各个进程均拥有3G虚拟内存,那么操作系统是如何做到各进程所使用的实际物理内存不会互相占用呢?实际上,各个进程均有自己的内存映射表。任意一个时刻,在一个CPU上只有一个进程在运行。所以对于此CPU来讲,在这一时刻,整个系统只存在一个4GB的虚拟地址空间,这个虚拟地址空间是面向此进程的。当进程发生切换的时候,虚拟地址空间也随着切换。由此可以看出,每个进程都有自己的虚拟地址空间,只有此进程运行的时候,其虚拟地址空间才被运行它的CPU所知。在其它时刻,其虚拟地址空间对于CPU来说,是不可知的。所以尽管每个进程都可以有4 GB的虚拟地址空间,但在CPU眼中,只有一个虚拟地址空间存在。虚拟地址空间的变化,随着进程切换而变化。

内核空间具有相对独立的特性。即内核的虚拟地址空间范围是自己独有的,不与任何用户进程共享(内核实质也是一个进程)。这样可保证内核空间的安全性。但是由于内核虚拟地址空间较小0xc000000~0xFFFFFFFF 仅有1G大小,Linux将内核地址空间划分为三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,其中3G-(3G+896M)这段空间直接映射了物理内存设备的前面896M实际物理地址,它们之间是简单的线性对应关系。所以不论有多少个进程,它们都共用了物理内存设备的同一块区域。

2、Linux进程通信

Android是基于Linux内核的一款操作系统,因此Android上面的应用程序之间的通信也应该遵循Linux的进程间通信方式,其中进程间通信的方式有很多种,例如最初的管道,信号量,还有后面稍微复杂的共享内存及Socket套接字等方式。

如上图,在Linux系统中如果两个进程之间需要传输数据,那么必定绕不过Linux进程间隔离这道坎。通过第一章我们已经理解到了每个进程都拥有(可寻址,能否存储数据还是另外一个问题)4G大小的内存空间(虚拟地址空间),linux系统的内存管理机制通过上文提到的分页技术,能够将不同进程的相同虚拟地址转换成不同的物理内存设备地址(即进程A和进程B有指针变量指向地址0x80这个地址,通过分页技术将进程A的0x80虚拟地址转换成了物理内存设备的0x010000F1真实物理内存设备地址,将进程B的0x80虚拟地址转换成了物理内存设备的0x020000F2真实物理内存设备地址)。从这个假设的例子可以看出不同进程之间并不能进行内存共享,它们就像平行世界一样永远无法产生交集,这其实就是linux系统进程隔离的原理。

因为进程隔离的限制,进程A无法访问进程B的数据,那这样的系统能拿来干嘛呢?答案当然不是,Linux系统的设计者将进程空间(4G的虚拟地址空间)划分为用户空间和内核空间。这样的划分不仅仅将解决进程隔离的限制,还能很好的保护操作系统以及底层硬件的访问权限,即内核空间是系统内核(操作系统的核心)运行的空间独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限;用户空间是用户程序运行的空间,为了保证安全性,用户进程是不能访问受内核保护的内存区域以及底层硬件的访问权限。为了保证安全性用户空间(0-3G)与内核空间(3-4G)也是相互隔离的,即linux永远不会给用户进程分配一个在3-4G范围内的虚拟地址,而用户进程之间访问3-4G范围内的虚拟地址将抛出异常;内核也不能之间访问0-3G的虚拟地址从第一章已经知道3G-3G+896M区域的虚拟地址是直接映射到物理内存的前896M,而剩余的128M其实是被称为高端内存HIGH_MEM的区域。如下图:

虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。Linux 使用两级保护机制:0级供系统内核使用;3级供用户程序使用。当一个进程执行系统调用而陷入内核代码中执行时,称进程处于内核运行态,此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。即linux通过系统调用的方式能够在内核空间和用户空间自由切换,如果进程A传递数据给进程B,那么就有这样一条路线进行数据传递:进程A通过系统调用从进程A的用户空间切换到内核空间,可以通过copy_from_user函数将用户空间的数据拷贝到内核空间,因为内核空间就只有一个(不同进程的内核空间指向的同一块物理内存设备地址,即内核空间是共享的),所以进程B也可以通过系统调用切换到内核空间,并通过copy_to_user函数将内核空间的数据拷贝到进程B对应的用户空间中,就这样通过内核空间作为中介完成了数据的拷贝其实这就是Linux中比较传统的IPC通信机制,如下图:

二、Binder驱动

跨进程通信是需要内核空间做支持的。传统的IPC机制如管道、Socket都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是Binder并不是Linux系统内核的一部分,那怎么办呢?这就得益于Linux的动态内核可加载模块LKM的机制:模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行(属性linux驱动的朋友就知道这其实就是一个驱动程序)。它在运行时被链接到内核作为内核的一部分运行。这样Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信,这个内核模块就是Binder驱动程序。

参考:Binder机制学习总结之Binder驱动

1、Binder驱动初始化

搞过Linux驱动的同学肯定很熟悉宏module_init和module_exit,当内核某个模块被insmod(插入到系统内核)时候将调用module_init宏指定的函数进行初始化(例如驱动设备的注册),当内核某个模块被rmmod(从系统内核中移除)的时候就会调用module_exit宏指定的函数进行一些类似析构操作。我们来看看这两个宏定义的地方:

//kernel/include/linux/init.h
#ifndef MODULE
#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn
#define pure_initcall(fn)		__define_initcall("0",fn,0)
#define core_initcall(fn)		__define_initcall("1",fn,1)
#define core_initcall_sync(fn)		__define_initcall("1s",fn,1s)
#define postcore_initcall(fn)		__define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)	__define_initcall("2s",fn,2s)
#define arch_initcall(fn)		__define_initcall("3",fn,3)
#define arch_initcall_sync(fn)		__define_initcall("3s",fn,3s)
#define subsys_initcall(fn)		__define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)	__define_initcall("4s",fn,4s)
#define fs_initcall(fn)			__define_initcall("5",fn,5)
#define fs_initcall_sync(fn)		__define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)		__define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)		__define_initcall("6",fn,6)
#define device_initcall_sync(fn)	__define_initcall("6s",fn,6s)
#define late_initcall(fn)		__define_initcall("7",fn,7)
#define late_initcall_sync(fn)		__define_initcall("7s",fn,7s)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) \
	static exitcall_t __exitcall_##fn __exit_call = fn
#define module_init(x)	__initcall(x);
#define module_exit(x)	__exitcall(x);
#else /* MODULE */
#define early_initcall(fn)		module_init(fn)
#define core_initcall(fn)		module_init(fn)
#define postcore_initcall(fn)		module_init(fn)
#define arch_initcall(fn)		module_init(fn)
#define subsys_initcall(fn)		module_init(fn)
#define fs_initcall(fn)			module_init(fn)
#define device_initcall(fn)		module_init(fn)
#define late_initcall(fn)		module_init(fn)
#define security_initcall(fn)		module_init(fn)
#define module_init(initfn)					\
	static inline initcall_t __inittest(void)		\
	{ return initfn; }					\
	int init_module(void) __attribute__((alias(#initfn)));
#define module_exit(exitfn)					\
	static inline exitcall_t __exittest(void)		\
	{ return exitfn; }					\
	void cleanup_module(void) __attribute__((alias(#exitfn)));
#endif

从kernel/include/linux/init.h文件中的定义我们可以发现内核模块加载的宏module_init与宏device_initcall完全等价,google对linux内核代码的修改之一就是binder驱动模块的加入,当然这里使用的是device_initcall来对binder驱动模块进行初始化,如下代码:

//kernel/drivers/staging/android/binder.c
//定义了对设备文件binder系统调用的操作
static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};
//定义设备类型MISC_DYNAMIC_MINOR 设备名为binder
static struct miscdevice binder_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "binder",
	.fops = &binder_fops
};
//模块注册
device_initcall(binder_init);
static int __init binder_init(void)
{
	int ret;
	binder_deferred_workqueue = create_singlethread_workqueue("binder");
	if (!binder_deferred_workqueue) return -ENOMEM;
    //创建binder/proc/目录
	binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
	if (binder_debugfs_dir_entry_root) binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", binder_debugfs_dir_entry_root);
    //杂项设备注册
	ret = misc_register(&binder_miscdev);
    //在上面目录下面创建了五个文件stae stats transactions transaction_log failed_transaction_log
    //可通过这五个文件读取到binder驱动的运行状况,例如命名协议和返回协议请求次数 日子记录信息 正在使用binder进程信息
	if (binder_debugfs_dir_entry_root) {
		debugfs_create_file("state", S_IRUGO, binder_debugfs_dir_entry_root, NULL, &binder_state_fops);
		debugfs_create_file("stats", S_IRUGO, binder_debugfs_dir_entry_root, NULL, &binder_stats_fops);
		debugfs_create_file("transactions", S_IRUGO, binder_debugfs_dir_entry_root, NULL, &binder_transactions_fops);
		debugfs_create_file("transaction_log", S_IRUGO, binder_debugfs_dir_entry_root, &binder_transaction_log, &binder_transaction_log_fops);
		debugfs_create_file("failed_transaction_log", S_IRUGO, binder_debugfs_dir_entry_root, &binder_transaction_log_failed, &binder_transaction_log_fops);
	}
	return ret;
}

2、Binder驱动打开设备文件

用户进程在使用binder进程间通信机制之前,首先要调用函数open打开设备文件/dev/biner来获取binder驱动的文件描述符,然后通过该文件描述符与binder驱动程序交互,继而与其他进程执行binder进程间通信。

由第一节可以看出用户程序执行open("/dev/binder",O_RDWR)打开binder设备文件的时候就会进入内核态(open函数其实就是系统调用),且系统内核跳转到上面驱动程序中并执行上面指定的binder_open函数,该函数如下:

//kernel/drivers/staging/android/binder.c
//定义全局hash队列binder_procs 成员都是binder_proc结构体
static HLIST_HEAD(binder_procs);
//每个使用binder通信的进程open("/dev/binder",O_RDWR)调用到该方法
static int binder_open(struct inode *nodp, struct file *filp)
{
    //proc是binder_proc变量,该结构体被用来描述一个使用binder通信的进程
	struct binder_proc *proc;
    //创建proc变量,用来描述当前进程在内核空间的实例对象,可能是客户端进程也可能是服务端进程
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	if (proc == NULL) return -ENOMEM;
	get_task_struct(current);
	proc->tsk = current;
	INIT_LIST_HEAD(&proc->todo);
	init_waitqueue_head(&proc->wait);
	proc->default_priority = task_nice(current);
	mutex_lock(&binder_lock);
	binder_stats_created(BINDER_STAT_PROC);
    //将当前进程的实例对象proc添加到全局队列binder_procs中
	hlist_add_head(&proc->proc_node, &binder_procs);
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
    //将当前进程的实例对象proc保存在文件描述符filp中 方便后续mmap和ioctl等使用
	filp->private_data = proc;
	mutex_unlock(&binder_lock);
	if (binder_debugfs_dir_entry_proc) {
		char strbuf[11];
        //根据当前进程的pid来生成文件名
        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
        //binder/proc目录下创建一个以当前进程ID为名字的制度文件 并以binder_proc_fops函数未文件内容读取函数
        //binder/proc/<PID>文件可以被用来获取进程<PID>的binder线程池、binder实体对象和引用对象
		proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO, binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
	}
	return 0;
}

这里的逻辑其实也不是很复杂,但是涉及了几个概念:通过binder方式来进行通信的用户进程(不限于客户端进程还是服务端进程),都必须先通过打开/dev/binder驱动设备文件的方式进入内核空间,并在内核空间创建一个binder_proc结构体实例对象(内核空间用来描述一个binder进程的实例对象),切将它添加到一个全局列表binder_procs中,binder_procs存储了所有使用了binder进程通信的用户进程最后还在binder/proc/<PID>文件,后续可以通过进程PID来获取binder驱动相关信息。

3、Binder驱动内存映射

用户进程在打开/dev/binder设备文件之后,还必须要调用mmap函数把这个设备文件映射到用户进程的地址空间,这样才可以使用binder进程间通信机制。设备文件/dev/binder对应虚拟设备(驱动设备),进行映射是为了能够让binder驱动程序在内核空间为用户进程分配一块内核缓冲区,以便用来进行跨进程数据传输。mmap函数如下:

//kernel/drivers/staging/android/binder.c
//用户进程调用mmap函数
//其中参数filp为上节open中返回的文件描述符
//其中参数vma为用户进程用来进行数据交互的缓冲区,其实也是虚拟地址
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
    //定义vm_struct结构体类型的变量area
    //area用来表示binder驱动为当前进程在内核空间分配的一段缓冲区,当然这里也只是一个虚拟地址
	struct vm_struct *area;
    //得到当前进程的实例对象proc 在binder_open中将proc保存在filp中
	struct binder_proc *proc = filp->private_data;
    //定义binder_buffer结构体类型变量buffer
    //下文buffer用来描述分配出来的后的真实物理页面
	struct binder_buffer *buffer;
    //判断vma用户进程缓冲区大小是否超过4M(binder驱动最多为进程分配4M内核缓冲区来传输数据)
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M; //超过4M截断
    //通过vma用户进程缓冲区大小来为分配内核缓冲区area
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
	if (area == NULL) {
		ret = -ENOMEM;
		goto err_get_vm_area_failed;
	}
    //将area的首地址赋值给proc->buffer,后面讲用于物理页面申请
	proc->buffer = area->addr;
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
	mutex_unlock(&binder_mmap_lock);
    //开始为vma何area分配物理内存地址
    //首先创建一个物理页面结构体指针数组 PAGE_SIZE为4K(linux以物理页面为单位一般是4k大小)
	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
	if (proc->pages == NULL) {
		ret = -ENOMEM;
		goto err_alloc_pages_failed;
	}
	proc->buffer_size = vma->vm_end - vma->vm_start;
	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;
    //然后通过binder_update_page_range函数分配物理页面(在物理内存设备上进行分配)
	if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
		ret = -ENOMEM;
		goto err_alloc_small_buf_failed;
	}
    //使用一个binder_buffer结构体来描述上面分配好的物理页面
	buffer = proc->buffer;
	INIT_LIST_HEAD(&proc->buffers);
	list_add(&buffer->entry, &proc->buffers);
	buffer->free = 1;
    //最后调用binder_insert_free_buffer将它加入到进程结构体proc的空闲内核缓冲区红黑树free_buffers中
	binder_insert_free_buffer(proc, buffer);
	proc->free_async_space = proc->buffer_size / 2;
	barrier();
	proc->files = get_files_struct(proc->tsk);
	proc->vma = vma;
	proc->vma_vm_mm = vma->vm_mm;
	return 0;
}

其实上面的流程并不是很复杂,用户进程在通过open("/dev/binder")得到了设备驱动描述符,紧接着应该调用mmap函数进行内存缓冲区的映射,其中第二个参数vma就是用户空间的缓冲区(用户进程可以通过vma向binder驱动程序发送数据或者接收数据)。当进入内核态即binde_mmap函数中,binder驱动程序首先在内核空间创建了缓冲区area(该缓冲区位于内核空间,用于与其他进程通信)。这个时候不论是area还vma其实都是虚拟地址空间,因此还需要为他们分配一块真正的物理内存页面,即执行代码:

binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)

该函数的作用就是为一段指定的虚拟地址空间分配或者释放物理页面,其中第一个参数proc指向要操作的目标进程;第二参数allocate如果是0表示释放物理页面否则表示分配物理页面;第三个参数start和第四个参数end指定了要操作的内核地址空间的开始地址和结束地址;第五个参数指向要映射的用户地址空间。这里第三个参数proc->buffer在26行可以看出其实就是area的首地址,第五个参数为vma,即这里将area和vma直接关联到一起,指向同一块物理页面。

如果文字不是很好理解,那么请看看下面这张图,即vma是用户进程调用mmap传递进来,当然是在用户空间缓冲区,area是binder驱动程序为当前进程binder_proc在内核空间分配的缓冲区,他们都是虚拟地址,通过binder_update_page_range为它们在物理内存设备上面分配了同一块物理页面。

4、Binder驱动如何在用户态和内核态进行数据传递

Binder驱动程序为进程分配的内核缓冲区即为一系列物理页面,它们分别被映射到进程的用户地址空间和内核地址空间,当Binder驱动程序需要将一块数据传输给另外一个进程时,它就可以先把这块数据保存在为该进程所分配的一块内核缓冲区,然后再把这块内核缓冲区的用户空间地址告诉进程,最后进程就可以访问到里面的数据了,这样做的好吃是不需要将数据从内核空间拷贝到用户空间,从而提升了数据传输效率。如下图所示:

可能有些人觉得上面图呈现出来中间并不止一次拷贝,我们重新屡一下,数据发送进程通过系统调用copy_from_user将数据从用户空间拷贝到内核空间,binder驱动在内核空间维护了一个缓存区(具体实现没有研究,其实也可以去掉这个缓存区,但是调用binder驱动程序出现并发情况就很有可能出现问题),最后实际上将发送进程数据拷贝到了内核缓冲区area里面,因为area已经与接收进程缓冲区vma公用的同一块物理页面,所以area被刷新后,数据接收进程的vma也跟着被刷新了。从开始到结束只调用了copy_form_user,就没有再调用copy_to_user函数了,相比于管道等其他进程间通信,是不是只做了一次拷贝。

copy_from_user和copy_to_user这两个函数相信做内核开发的人都非常熟悉,分别是将用户空间的数据拷贝到内核空间以及将内核空间中的数据拷贝到用户空间。这两个函数一般用于系统调用中,前者将用户空间参数拷贝到内核,后者将系统用的结果返回到用户空间。

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
	unsigned long res = n;
	kasan_check_write(to, n);

    //检查用户空间地址是否正确,如果用户进程做系统调用时,故意传了一个内核地址下来,如果这里不做检查,这里将是一个很大的漏洞,可以轻而易举的对内核搞破坏
	if (access_ok(VERIFY_READ, from, n)) {
		check_object_size(to, n, false);
		res = __arch_copy_from_user(to, from, n);
	}
	if (unlikely(res))
		memset(to + (n - res), 0, res);
	return res;
}
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
	kasan_check_read(from, n);

	if (access_ok(VERIFY_WRITE, to, n)) {
		check_object_size(from, n, true);
        //将用户空间的数据拷贝到内核空间,由于内核空间可以访问用户空间的数据.内核空间可以很容易的将用户空间的数据拷贝到内核空间,反过来当然也一样
		n = __arch_copy_to_user(to, from, n);
	}
	return n;
}

为什么要使用copy_from_user和copy_to_user拷贝?

可以参考https://blog.csdn.net/21cnbao/article/details/106953600

5、Binder如何实现了一次拷贝?

Binder 内存映射和接收缓存区管理_csbhwy的博客-CSDN博客

mmap函数详解(what?why?how?)_黑子的ball的博客-CSDN博客

6、几个重要数据结构

在讲解binder驱动命令处理之前,先介绍几个比较重要的数据结构,因为我最初研究这一块的时候总是被那些模糊而庞大的概念搞得晕头转向。

参考:Binder机制学习总结之Driver部分

1)、binder_proc进程对象

结构体binder_proc用来描述一个正在使用binder进程间通信进制的进程。即第二章中提到的IPC机制中的两个需要进行通信的用户进程:扮演数据发送的客户端进程;扮演数据接收的服务端进程。他们在系统调用的时候切换到内核态(执行binder驱动程序),都有这样的一个结构体来代表他们。

用户进程如果想使用binder来进行进程间通信,不论作为客户端进程还是服务端进程,都需要系统调用open打开binder驱动设备文件/dev/biner,切换到内核态执行binder_open函数,在该函数中就为自己创建了一个binder_proc对象,并把这个对象添加到了全局的hash列表binder_procs中(哈希列表详情请点击)。那我们可以下一个结论:内核空间的binder驱动程序拥有一个全局列表binder_procs,该列表维护了所有使用binder驱动程序的用户进程,正因为binder驱动程序掌握了所有用户进程的名单,所以binder驱动程序就能够很方便的把他们揪出来单独喝茶(传输数据或命令)。

与binder_proc结构体和全局列表binder_procs相关代码如下:

#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
//定义全局列表:所有使用binder通信的进程实例对象列表
static HLIST_HEAD(binder_procs);
struct binder_proc {
	struct hlist_node proc_node; //代表自己(具体没研究,估计跟hash列表相关)
	struct rb_root threads; //线程池,维护了该进程所开启的所有binder线程
	struct rb_root nodes;
	int pid;
    //进程虚拟物理地址相关
    //进程内核缓冲区相关
    //进程地址映射相关
};
static int binder_open(struct inode *nodp, struct file *filp)
{
	struct binder_proc *proc;
    //给binder_proc分配内存空间
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	if (proc == NULL) return -ENOMEM;
	get_task_struct(current);
	proc->tsk = current;
    //通过宏初始化binder_proc
	INIT_LIST_HEAD(&proc->todo);
	init_waitqueue_head(&proc->wait);
	proc->default_priority = task_nice(current);
	mutex_lock(&binder_lock);
	binder_stats_created(BINDER_STAT_PROC);
    //将自己添加到全局列表binder_procs
	hlist_add_head(&proc->proc_node, &binder_procs);
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	filp->private_data = proc;
	mutex_unlock(&binder_lock);
	return 0;
}

2)、binder_node实体对象

结构体binder_node用来描述一个binder实体对象,每一个Service组件在Binder驱动程序中都对应有一个Binder实体对象。这里就不得不先理清楚一个概念,android中的binder机制并不是linux传统意义上的ipc进程通信,它其实是一个c/s模型。什么意思呢?

举个例子,media.player作为一个媒体播放器,可以解读成:media.player为Android系统所有进程提供了一项服务(播放视频),你们如果有谁想看电影了就来找我给你免费提供播放电影服务。这里media.player是提供服务的一方,因此被称为Service组件,当然这个Service组件想要工作起来还需要挂靠在某个进程上,android系统中的media.player就挂靠在进程MediaServer上面,这里我们又可以将这个进程称为Service进程(注意:一定要跟Android应用层的四大组件中的service区分开,这里的Service是一个广义的)。而一个Service进程往往是多功能的,例如进程MediaServer除了给系统提供媒体播放功能,还能系统提供照相功能,音频输入输出功能等。因此我们重新总结一下:服务端进程可能拥有一个或多个Service组件,每个Service组件在内核态都对应一个binder_node实体对象;结合上下节可以补充到每个服务端进程在内核态都对应一个binder_proc进程对象

struct binder_node {
	struct binder_work work;
	union {
		struct rb_node rb_node;
		struct hlist_node dead_node;
	};
	struct binder_proc *proc;  //代表当前挂靠的进程
	struct hlist_head refs;    //代表当前所有的binder_ref
    //...
};

3)、binder_ref引用对象

结构体binder_ref用来描述一个binder引用对象,每一个Client组件在Binder驱动程序中都对应一个Binder引用对象。还是上面那个例子,上面的Service组件提供了播放电影的服务,那么就有一些人想来看这些电影了,这些人就来消费服务的,通常被称为Client组件,它们所挂靠的宿主进程又叫客户端进程。当然来消费服务的可能不止一个人,因此往往一个Service组件在同一时刻可能有多个Client组件,因此上面的binder_node结构体中也有个列表struct hlist_head refs,该列表用来维护所有持有的Client组件。

除此之外binder_ref引用对象还有一个属性desc,被称为句柄值。因此当Client进程的用户控件通过Binder驱动程序来访问一个Service组件时,需要指定一个句柄值,binder驱动程序根据这个句柄值找到对应的Binder引用对象,然后再根据该引用对象的成员变量node找到对应的binder实体对象,最后就可以通过这个实体对象来访问Service组件。如下:

struct binder_ref {
	struct rb_node rb_node_desc;
	struct rb_node rb_node_node;
	struct hlist_node node_entry;
	struct binder_proc *proc;    //客户端宿主进程
	struct binder_node *node;    //持有了对应的Service组件的实体对象
	uint32_t desc;        //句柄值
	int strong;
	int weak;
	struct binder_ref_death *death;
};

4)、binder_thread线程对象

结构体binder_thread用来描述Binder线程池中的一个线程, 其中成员变量proc指向宿主进程。binder_proc使用一个红黑树来组织Binder线程池中的所有线程:struct rb_root threads。

5)、binder相关命令解析

static const char * const binder_return_strings[] = {
	"BR_ERROR",
	"BR_OK",
	"BR_TRANSACTION",
	"BR_REPLY",
	"BR_ACQUIRE_RESULT",
	"BR_DEAD_REPLY",
	"BR_TRANSACTION_COMPLETE",
	"BR_INCREFS",
	"BR_ACQUIRE",
	"BR_RELEASE",
	"BR_DECREFS",
	"BR_ATTEMPT_ACQUIRE",
	"BR_NOOP",
	"BR_SPAWN_LOOPER",
	"BR_FINISHED",
	"BR_DEAD_BINDER",
	"BR_CLEAR_DEATH_NOTIFICATION_DONE",
	"BR_FAILED_REPLY"
};
static const char * const binder_command_strings[] = {
	"BC_TRANSACTION",
	"BC_REPLY",
	"BC_ACQUIRE_RESULT",
	"BC_FREE_BUFFER",
	"BC_INCREFS",
	"BC_ACQUIRE",
	"BC_RELEASE",
	"BC_DECREFS",
	"BC_INCREFS_DONE",
	"BC_ACQUIRE_DONE",
	"BC_ATTEMPT_ACQUIRE",
	"BC_REGISTER_LOOPER",
	"BC_ENTER_LOOPER",
	"BC_EXIT_LOOPER",
	"BC_REQUEST_DEATH_NOTIFICATION",
	"BC_CLEAR_DEATH_NOTIFICATION",
	"BC_DEAD_BINDER_DONE",
	"BC_TRANSACTION_SG",
	"BC_REPLY_SG",
};
static const char * const binder_objstat_strings[] = {
	"proc",
	"thread",
	"node",
	"ref",
	"death",
	"transaction",
	"transaction_complete"
};

7、Binder驱动命令处理

前面已经介绍了binder驱动程序的open和mmap函数,当然也离不开binder驱动程序的ioctl命令处理函数。其实binder_ioctl函数中通过switch语句处理几条命令码,其中比较重要的命令有BINDER_WRITE_READ和BINDER_SET_CONTEXT_MGR。详情如下代码:

//kernel/drivers/staging/android/binder.h
//定义了几个重要的命令码
//其中BINDER_WRITE_READ实现与应用进程之间通信过程(接收来自应用进程的命令或数据并返回处理结果)
//其中BINDER_SET_CONTEXT_MGR注册上下文管理者(Service Manager进程注册)
#define BINDER_WRITE_READ   		_IOWR('b', 1, struct binder_write_read)
#define	BINDER_SET_IDLE_TIMEOUT		_IOW('b', 3, int64_t)
#define	BINDER_SET_MAX_THREADS		_IOW('b', 5, size_t)
#define	BINDER_SET_IDLE_PRIORITY	_IOW('b', 6, int)
#define	BINDER_SET_CONTEXT_MGR		_IOW('b', 7, int)
#define	BINDER_THREAD_EXIT		_IOW('b', 8, int)
#define BINDER_VERSION			_IOWR('b', 9, struct binder_version)
//kernel/drivers/staging/android/binder.c
//用户空间进程调用ioctl将跳转到内核态,并调用对应模块(驱动)的binder_ioctl函数
//使用binder通信机制的用户进程一般调用ioctl函数来进行数据通信cmd为BINDER_WRITE_READ,arg为Parcel数据
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    //得到当前进程实例(biner_open函数中创建并将其赋值到filp->private_data)
	struct binder_proc *proc = filp->private_data;
	unsigned int size = _IOC_SIZE(cmd);
    //得到用户进程传递的参数
	void __user *ubuf = (void __user *)arg;
	mutex_lock(&binder_lock);
    //得到当前线程实例(binder_get_thread从当前进程中的线程池中获取)
	struct binder_thread *thread = binder_get_thread(proc);
	switch (cmd) {
        //终止Binder线程池(与binder_get_thread对应)
	    case BINDER_THREAD_EXIT:
		    binder_free_thread(proc, thread);
		    thread = NULL;
		    break;
        //设置最大线程数量(将用户传递的参数直接copy_from_user拷贝到proc->max_threads)
    	case BINDER_SET_MAX_THREADS:
		    if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) { }
		    break;
	    //获取binder版本号(将当前版本号put_user拷贝到用户传递的参数中)
        case BINDER_VERSION:
		    if (put_user(BINDER_CURRENT_PROTOCOL_VERSION, &((struct binder_version *)ubuf)->protocol_version)) { }
		    break;
        case BINDER_WRITE_READ:
            //创建内核空间的binder_write_read用来描述进程间通信中请求数据和返回数据
		    struct binder_write_read bwr;
            //拷贝用户空间的请求数据到bwr中
		    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { }
            //将bwr中的请求数据交给上面的线程thread中进行处理
		    if (bwr.write_size > 0) ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
            //获取binder线程thread中处理的结果保存到bwr中
		    if (bwr.read_size > 0) ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
            //拷贝bwr中返回数据到用户空间
		    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { }
		    break;
            break;
        case BINDER_SET_CONTEXT_MGR:
            //后文详解
            break;
    }
}

上面代码也不难理解,先获取到当前进程实例对象binder_proc,由本章第二节可以知道当前进程实例对象在binder_open的时候被创建并保持在filp->private_data中。当得到了binder_proc实例对象后就可以从binder线程池中获取主线程thread,如果当前命令码是BINDER_WRITE_READ,那么就可以通过当前线程实例对象进行数据的读写(即将用户进程传递过来的参数arg交给该线程binder_thread实例处理并返回结果)

其中BINDER_SET_MAX_THREADS命令通过copy_from_user函数将用户进程传递过来的参数拷贝到proc->max_threads中保存;BINDER_VERSION命令通过put_user函数将本地定义的宏BINDER_CURRENT_PROTOCOL_VERSION拷贝到用户进程传递过来的参数中。copy_from_user和put_user其实都是linux内核用来实现用户空间与内核空间之间的数据拷贝(详情参考请点击)。

我们现在已经知道了binder驱动程序与用户进程之间的通信,最终交给了binder_thread类型的实例对象来进行处理。从上面可以发现,binder驱动程序通过函数binder_get_thread来获取线程池,通过binder_free_thread函数来释放线程池。代码如下:

//kernel/drivers/staging/android/binder.c
//通过binder进程的实例对象proc来获取它的主线程
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
    //binder_thread用来描述一个binder线程
	struct binder_thread *thread = NULL;
	struct rb_node *parent = NULL;
    //proc->threads是一个红黑树(用来管理组织binder线程池中的所有线程)
	struct rb_node **p = &proc->threads.rb_node;
    //变量binder线程池的红黑树
	while (*p) {
		parent = *p;
		thread = rb_entry(parent, struct binder_thread, rb_node);
		if (current->pid < thread->pid)
			p = &(*p)->rb_left;
		else if (current->pid > thread->pid)
			p = &(*p)->rb_right;
		else //遍历到了当前线程就break,此时的p指向当前线程binder_thread
			break;
	}
    //遍历完红黑树也没有找到binder线程就创建binder_thread对象
	if (*p == NULL) {
		thread = kzalloc(sizeof(*thread), GFP_KERNEL);
		binder_stats_created(BINDER_STAT_THREAD);
		thread->proc = proc;
		thread->pid = current->pid;
		init_waitqueue_head(&thread->wait);
		INIT_LIST_HEAD(&thread->todo);
		rb_link_node(&thread->rb_node, parent, p);
		rb_insert_color(&thread->rb_node, &proc->threads);
        //初始化新创建的binder线程需要马上返回到用户空间进行准备轮询
		thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
		thread->return_error = BR_OK;
		thread->return_error2 = BR_OK;
	}
    //返回该进程的binder主线程
	return thread;
}
//释放binder线程池
static int binder_free_thread(struct binder_proc *proc, struct binder_thread *thread)
{
	struct binder_transaction *t;
	struct binder_transaction *send_reply = NULL;
	int active_transactions = 0;
    //释放红黑树proc->threads
	rb_erase(&thread->rb_node, &proc->threads);
	t = thread->transaction_stack;
	if (t && t->to_thread == thread) send_reply = t;
    //遍历释放binder线程池中所有线程
	while (t) {
		active_transactions++;
		if (t->to_thread == thread) {
			t->to_proc = NULL;
			t->to_thread = NULL;
			if (t->buffer) {
				t->buffer->transaction = NULL;
				t->buffer = NULL;
			}
			t = t->to_parent;
		} else if (t->from == thread) {
			t->from = NULL;
			t = t->from_parent;
		}
	}
	if (send_reply) binder_send_failed_reply(send_reply, BR_DEAD_REPLY);
	binder_release_work(&thread->todo);
	kfree(thread);
	binder_stats_deleted(BINDER_STAT_THREAD);
	return active_transactions;
}

Binder驱动程序为每一个使用binder通信机制的用户进程创建了一个binder_proc类型的对象(描述该用户进程),该类型中有成员变量struct rb_root threads,threads就是binder进程用来组织管理binder线程池的红黑树

在binder_get_thread函数中遍历红黑树返回binder主线程否则创建binder_thread对象,在binder_free_thread函数中释放了该红黑树并将每个binder_thread线程节点置空。

三、service manager进程

Service Manager是Binder进程间通信机制的核心组件之一,扮演着Binder进程间通信机制上下文管理的角色,它的作用是用来管理所有能够提供服务的进程(服务端进程)。那么它是如何来管理的呢?所有服务端进程(简称Service进程)在启动的时候需要将自己注册到Service Manager里面,如果有对应的客户端进程(简称Client进程)需要与其进行访问,那么就需要从Service Manager中获取到对应Service进程的代理或者句柄才能与其进行通信。如下示意图:

Service Manager也是一个独立运行的进程,同时需要接收来自所有服务端进程的注册通信,和来自客户端进程的获取指定服务请求通信,因此Service Manager也需要使用一种进程间通信来实现,其实Service Manager也是通过Binder机制(通过Binder驱动)来实现,本章我们就来看看service manager是如何与Binder驱动程序进行交互的呢?

1、service manager进程的启动

service manager是由init进程负责启动的,init进程是android世界的第一个进程,不熟悉的可以点击此处进行参考。service manager进程路径如下:

如上图在android9.0版本中开始有了trable架构的身影,配置了servicemanager.rc和vndservicemanager.rc,android.bp中声明了binder和vndbinder,他们的区别仅仅在于bin文件名称不一样和所处分区(system和vendor分区)不一样而已。如下:

#frameworks/native/cmds/servicemanager/servicemanager.rc
service servicemanager /system/bin/servicemanager
    class core animation
    user system
    group system readproc
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart audioserver
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart inputflinger
    onrestart restart drm
    onrestart restart cameraserver
    onrestart restart keystore
    onrestart restart gatekeeperd
    writepid /dev/cpuset/system-background/tasks
    shutdown critical
#frameworks/native/cmds/servicemanager/vndservicemanager.rc
service vndservicemanager /vendor/bin/vndservicemanager /dev/vndbinder
    class core
    user system
    group system readproc
    writepid /dev/cpuset/system-background/tasks
    shutdown critical
#frameworks/native/cmds/servicemanager/Android.bp
cc_defaults {
    name: "servicemanager_flags",
    cflags: [ "-Wall", "-Wextra", "-Werror", ],
    product_variables: {
        binder32bit: {
            cflags: ["-DBINDER_IPC_32BIT=1"],
        },
    },
    shared_libs: ["liblog"],
}
#system分区的servicemanager
cc_binary {
    name: "servicemanager",
    defaults: ["servicemanager_flags"],
    srcs: [
        "service_manager.c",
        "binder.c",
    ],
    shared_libs: ["libcutils", "libselinux"],
    init_rc: ["servicemanager.rc"],
}
#vendor分区的vndservicemanager
cc_binary {
    name: "vndservicemanager",
    defaults: ["servicemanager_flags"],
    vendor: true,
    srcs: [
        "service_manager.c",
        "binder.c",
    ],
    cflags: [
        "-DVENDORSERVICEMANAGER=1",
    ],
    shared_libs: ["libcutils", "libselinux"],
    init_rc: ["vndservicemanager.rc"],
}

接下来我们来到service manger主函数,其中逻辑也不是太复杂,总体可以概括如下三个步骤:

  • 通过binder_open函数打开映射binder驱动设备程序
  • 通过binder_become_context_manager函数向binder驱动设备程序注册自己为上下文管理者
  • 通过binder_loop函数循环处理来自其他进程的binder请求
//frameworks/native/cmds/servicemanager/service_manager.c
int main(int argc, char** argv)
{
    struct binder_state *bs;
    union selinux_callback cb;
    //打开和映射binder驱动设备
    char *driver = "/dev/binder";;
    bs = binder_open(driver, 128*1024);
    //向binder驱动设备中注册上下文
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
#ifdef VENDORSERVICEMANAGER
    sehandle = selinux_android_vendor_service_context_handle();
#else
    sehandle = selinux_android_service_context_handle();
#endif
    selinux_status_open(true);
    if (getcon(&service_manager_context) != 0) {
        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
        abort();
    }
    //循环处理来自其他进程的消息
    binder_loop(bs, svcmgr_handler);
    return 0;
}

2、打开和映射Binder驱动设备

第一节可以知道service manager首先调用了binder_open函数打开和映射Binder驱动程序,代码如下:

//frameworks/native/cmds/servicemanager/binder.c
struct binder_state
{
    int fd;        //驱动设备文件描述符
    void *mapped;  //驱动程序映射的空间
    size_t mapsize;//驱动程序映射空间大小 从main中发现这里传入的是128*1024
};
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;
    //分配binder_state结构体 存储了binder驱动的局部和映射空间
    bs = malloc(sizeof(*bs));
    //打开binder驱动设备文件,并将文件描述符保存在binder_state中
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        goto fail_open;
    }
    //通过ioctl系统调用从binder驱动设备程序中获取binder驱动程序的版本号
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }
    //通过mmap系统调用映射binder驱动程序中的一段内核空间
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        goto fail_map;
    }
    return bs;
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}
void binder_close(struct binder_state *bs)
{
    munmap(bs->mapped, bs->mapsize);
    close(bs->fd);
    free(bs);
}

service manager打开了设备文件/dev/binder之后,就会得到文件描述符并将其保存在binder_state结构体的fd中,除此之外还映射了binder驱动程序中的一段内核虚拟空间。从Binder驱动内存映射这一节不难理解,用户进程(service manager)调用了mmap函数进行系统调用,系统立即进入内核态,在Binder驱动程序binder_mmap函数中创建一段内核虚拟空间作为缓冲区(这里是128k),并返回了用户进程虚拟空间的首地址,且内核虚拟空间与用户进程虚拟空间都是指向的同一段物理内存空间。因此就这样Service Manager拥有了一段Binder驱动的(内核态)缓冲区来保存与其他进程通信数据

3、向Binder驱动注册成为上下文管理者

接下来我们看看service manager是如何向Binder驱动程序注册成为上下文管理者的。该函数如下:

//frameworks/native/cmds/servicemanager/binder.c
int binder_become_context_manager(struct binder_state *bs) {
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

这个逻辑更简单了,service manager系统调用ioctl,控制命令BINDER_SET_CONTEXT_MGR就完成了上下文管理者的注册,还记得上章中的Binder驱动命令处理这一节吗?没错,BINDER_SET_CONTEXT_MGR命令就是Binder驱动程序指定某个进程为上下文管理者的逻辑,由此可见只有service manager进程向Binder驱动程序发送了该控制命令。

S的代码在这里有所改进,讲相关的操作移动到了ProcessState.cpp里面,但是逻辑流程基本一致:

//android/frameworks/native/cmds/servicemanager/main.cpp
int main(int argc, char** argv) {
    if (argc > 2) {
        LOG(FATAL) << "usage: " << argv[0] << " [binder driver]";
    }
    //打开binder驱动文件
    const char* driver = argc == 2 ? argv[1] : "/dev/binder";
    sp<ProcessState> ps = ProcessState::initWithDriver(driver);
    
    ps->setThreadPoolMaxThreadCount(0);
    ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
    sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
    if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
        LOG(ERROR) << "Could not self register servicemanager";
    }
    //sm进程像binder驱动注册,设置自己的handle句柄,这里的handle句柄为0
    IPCThreadState::self()->setTheContextObject(manager);
    ps->becomeContextManager();
    sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
    BinderCallback::setupTo(looper);
    ClientCallbackCallback::setupTo(looper, manager);
    //循环等待除了sm进程之外的进程发送过来的跨进程通信请求
    while(true) {
        looper->pollAll(-1);
    }
    // should not be reached
    return EXIT_FAILURE;
}
//android/frameworks/native/libs/binder/ProcessState.cpp
bool ProcessState::becomeContextManager()
{
    AutoMutex _l(mLock);
    flat_binder_object obj {
        .flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
    };
    int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);  //S的新扩展
    // fallback to original method
    if (result != 0) {
        android_errorWriteLog(0x534e4554, "121035042");
        //系统调用binder驱动程序,BINDER_SET_CONTEXT_MGR命令,该命令就是设置handle句柄的,sm进程的句柄固定死为0,这是一个约定,只要是0的binder进程就是万能管理者sm
        int unused = 0;
        result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused); 
    }
    return result == 0;
}

后文我们将知道binder_ioctl函数是Binder驱动程序处理来自用户进程的命令请求,BINDER_SET_CONTEXT_MGR分支如下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    //...省略...
    switch (cmd) {
	case BINDER_SET_CONTEXT_MGR:
        //判断是否已经有进程成功注册过上下文 如果有该变量不会为NULL
		if (binder_context_mgr_node != NULL) {
			ret = -EBUSY;
			goto err;
		}
		ret = security_binder_set_context_mgr(proc->tsk);
		if (ret < 0) goto err;
        //判断当前注册的进程是否与前面注册进程的ID一样
        //Binder驱动允许某进程重复使用该命令 因为前次注册可能会失败
		if (binder_context_mgr_uid != -1) {
			if (binder_context_mgr_uid != current->cred->euid) {
				ret = -EPERM;
				goto err;
			}
		} else
			binder_context_mgr_uid = current->cred->euid;
        //创建binder实例对象 并指定该实例对象的句柄为0
        //所有进程都通过调用binder_new_node创建binder本地对象实例
		binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
		if (binder_context_mgr_node == NULL) {
			ret = -ENOMEM;
			goto err;
		}
		binder_context_mgr_node->local_weak_refs++;
		binder_context_mgr_node->local_strong_refs++;
		binder_context_mgr_node->has_strong_ref = 1;
		binder_context_mgr_node->has_weak_ref = 1;
		break;
    }
}

再来看看Binder_new_node函数是如何创建Binder本地实例对象binder_node的呢?

static struct binder_node *binder_new_node(struct binder_proc *proc, void __user *ptr, void __user *cookie)
{
	struct rb_node **p = &proc->nodes.rb_node;
	struct rb_node *parent = NULL;
	struct binder_node *node;
    //从红黑树中获取当前binder进程实例
	while (*p) {
		parent = *p;
        //把当前实例进程插入到红黑树中
		node = rb_entry(parent, struct binder_node, rb_node);
		if (ptr < node->ptr)
			p = &(*p)->rb_left;
		else if (ptr > node->ptr)
			p = &(*p)->rb_right;
		else
			return NULL;
	}
    //红黑树为NULL就创建红黑树
	node = kzalloc(sizeof(*node), GFP_KERNEL);
	if (node == NULL) return NULL;
	binder_stats_created(BINDER_STAT_NODE);
	rb_link_node(&node->rb_node, parent, p);
	rb_insert_color(&node->rb_node, &proc->nodes);
	node->debug_id = ++binder_last_id;
	node->proc = proc;
	node->ptr = ptr; //这里的ptr参数是NULL
	node->cookie = cookie; //这里的cookie参数是NULL
	node->work.type = BINDER_WORK_NODE;
	INIT_LIST_HEAD(&node->work.entry);
	INIT_LIST_HEAD(&node->async_todo);
	return node;
}

使用binder方式进行通信的两个进程,将其中提供服务的进程成为Service进程端,另一个需要访问服务的进程称为Cilent进程端,它们之间需要进行进程间通信那么就需要借助Binder驱动程序来向上一个小节中提到的内核缓冲区(即Binder驱动程序与客户端进程之间建立映射的缓冲区)读写数据。因此服务端进程和客户端进程需要有一个句柄来进行对应,例如服务端进程Bn与客户端进程Bp他们的句柄必须为同一个值。

但是service manager肯定不是一个普通的Binder服务端进程,这里调用binder_new_node函数的时候,第二个参数和第三个参数均为NULL,这样就将binder_new_node中创建出来的binder_node实例对象指定的句柄为0,那么其他客户端进程或者服务端进程就可以遍历Binder驱动程序的红黑树获取句柄指定为0的进程代理对象,就能 得到serivice manager进程的真身。

4、循环等待Client进程请求

根据前面的介绍,service manager进程在系统启动的时候被init进程拉起来,service manager进程需要在系统运行期间为Service组件提供注册服务,为Client组件提供查询服务,因为Service组件或者Client组件在不同的进程之间,因此service manager进程也必须通过调用binder驱动程序在内核空间映射一段缓冲区来与它们进行通信。当一切都准备好之后,就需要进入轮询模式来获取来自其他进程的Service或Client组件的消息请求了。

根据第一节的内容,该进程的main函数最后调用了binder_loop(bs, svcmgr_handler),其中svcmgr_handler为函数指针,因此该函数指针指向的函数并不一定就在这里调用,如下代码:

//frameworks/native/cmds/servicemanager/binder.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    readbuf[0] = BC_ENTER_LOOPER;
    //进入轮询之前需要发送BC_ENTER_LOOPER命令
    //表示自己进入一切就绪进入轮询模式等待其他进程的请求
    binder_write(bs, readbuf, sizeof(uint32_t));
    //进入轮询模式 其实就是个死循环
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        //发送BINDER_WRITE_READ命令从映射好的内核缓冲区中获取数据(其他进程请求数据)
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        if (res < 0) break;
        //解析请求命令并交给binder_handler func函数来处理
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}

上面代码显而易见,service manager进程进入binder_loop函数中就陷入了死循环,通过binder驱动进行系统调用阻塞读取内核空间缓冲区数据,最后交给了函数binder_parse来传处理,同样需要注意的是函数指针svcmgr_handler在这里也没有并调用,只是作为参数被传递了进去,binder_parse代码如下:

//frameworks/native/cmds/servicemanager/binder.c
int binder_parse(struct binder_state *bs, struct binder_io *bio, uintptr_t ptr, size_t size, binder_handler func) {
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;
    while (ptr < end) {
        uint32_t cmd = *(uint32_t *) ptr;
        ptr += sizeof(uint32_t);
        switch(cmd) {
        case BR_NOOP:                 break;
        case BR_TRANSACTION_COMPLETE: break;
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS: ptr += sizeof(struct binder_ptr_cookie); break;
        case BR_TRANSACTION: {
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            if ((end - ptr) < sizeof(*txn))  return -1;
            binder_dump_txn(txn);
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;
                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, txn);
                //终于调用了函数指针svcmgr_handler
                res = func(bs, txn, &msg, &reply);
                if (txn->flags & TF_ONE_WAY) {
                    binder_free_buffer(bs, txn->data.ptr.buffer);
                } else {
                    //将函数指针svcmgr_handler写入缓冲区的数据通过binder驱动程序返回给其他进程
                    binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
                }
            }
            ptr += sizeof(*txn);
            break;
        }
        case BR_REPLY: /****省略****/   break;
        case BR_DEAD_BINDER: /****省略****/   break;
        case BR_FAILED_REPLY: r = -1; break;
        case BR_DEAD_REPLY: r = -1; break;
        default:  ALOGE("parse: OOPS %d\n", cmd); return -1;
        }
    }
    return r;
}

从上面的代码可以看出,函数binder_parse用来解析来自binder驱动程序的原始数据包,类似TCP数据包的原理,解析到BR_TRANSACTION类型(表示用于与其他进程进行消息通信)将其讲给前面提到过的函数指针svcmgr_handler处理,最后还调用了binder_send_reply函数将需要数据返回给其他进程svcmgr_handler代码如下:

//frameworks/native/cmds/servicemanager/service_manager.c
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    //...省略代码...
    switch(txn->code) {
    //查询获取指定服务Service组件(来自Client组件进程)
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        //转换被查询的Service名字,例如:String16("media.player")
        s = bio_get_string16(msg, &len);
        //do_find_service查找某个Service组件
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
        if (!handle) break;
        bio_put_ref(reply, handle);
        return 0;
    //注册当前服务Service组件(来自Service组件进程)
    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string16(msg, &len); 
        if (s == NULL) return -1;
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        dumpsys_priority = bio_get_uint32(msg);
        //do_add_service注册某个Service组件
        if (do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, dumpsys_priority, txn->sender_pid)) return -1;
        break;
    //获取所有Service组件
    case SVC_MGR_LIST_SERVICES: {
        uint32_t n = bio_get_uint32(msg);
        //svc_can_list遍历所有Service组件
        if (!svc_can_list(txn->sender_pid, txn->sender_euid)) return -1;
        si = svclist;
        return -1;
    }
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }
    bio_put_uint32(reply, 0);
    return 0;
}

这里可以作个总结,service manager进程在轮询binder驱动程序缓冲区的时候,通过binder_parse解析了驱动层通信的数据协议,如果是BR_TRANSACTION协议表示进程间数据传输,就将交给svcmgr_handler处理,最后通过binder_send_reply函数将需要返回的reply数据传递给binder驱动程序

//frameworks/native/cmds/servicemanager/binder.c
void binder_send_reply(struct binder_state *bs, struct binder_io *reply, binder_uintptr_t buffer_to_free, int status) {
    struct {
        uint32_t cmd_free;
        binder_uintptr_t buffer;
        uint32_t cmd_reply;
        struct binder_transaction_data txn;
    } __attribute__((packed)) data;
    data.cmd_free = BC_FREE_BUFFER;
    data.buffer = buffer_to_free;
    data.cmd_reply = BC_REPLY;
    data.txn.target.ptr = 0;
    data.txn.cookie = 0;
    data.txn.code = 0;
    if (status) {
        data.txn.flags = TF_STATUS_CODE;
        data.txn.data_size = sizeof(int);
        data.txn.offsets_size = 0;
        data.txn.data.ptr.buffer = (uintptr_t)&status;
        data.txn.data.ptr.offsets = 0;
    } else {
        data.txn.flags = 0;
        data.txn.data_size = reply->data - reply->data0;
        data.txn.offsets_size = ((char*) reply->offs) - ((char*) reply->offs0);
        data.txn.data.ptr.buffer = (uintptr_t)reply->data0;
        data.txn.data.ptr.offsets = (uintptr_t)reply->offs0;
    }
    binder_write(bs, &data, sizeof(data));
}

5、观察者模式

从消息处理函数svcmgr_handler的内容可以找到一种很熟悉的感觉, 没错那就是观察者模式的雏形。如下:

//frameworks/native/cmds/servicemanager/service_manager.c
//定义Service组件
struct svcinfo
{
    struct svcinfo *next;
    uint32_t handle;    //句柄,这个很重要
    struct binder_death death;
    int allow_isolated;
    uint32_t dumpsys_priority;
    size_t len;
    uint16_t name[0];
};
//所有Service组件链表的头指针
struct svcinfo *svclist = NULL;
//查找指定Service组件 遍历了整个链表
struct svcinfo *find_svc(const uint16_t *s16, size_t len)
{
    struct svcinfo *si;
    for (si = svclist; si; si = si->next) {
        if ((len == si->len) && !memcmp(s16, si->name, len * sizeof(uint16_t))) return si;
    }
    return NULL;
}
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
    //遍历svclist
    struct svcinfo *si = find_svc(s, len);
    if (!si || !si->handle) return 0;
    //...省略...
    //权限判断
    if (!svc_can_find(s, len, spid, uid))  return 0;  
    return si->handle;
}
//注册指定Service组件
int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle, uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid) {
    struct svcinfo *si;
    if (!handle || (len == 0) || (len > 127)) return -1;
    //权限判断
    if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n", str8(s, len), handle, uid);
        return -1;
    }
    //遍历svclist是否已经有该Service组件
    si = find_svc(s, len);
    if (si) {
        if (si->handle) {
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n", str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {
        //分配内存初始化将其插入到svclist中
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n", str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->dumpsys_priority = dumpsys_priority;
        si->next = svclist;
        svclist = si;
    }
    binder_acquire(bs, handle);
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}

到此,service manager进程的流程基本结束。该进程内部创建一个用来保存所有Service组件的链表。其他进程可以通过binder驱动程序来与service manager进程进行通信,向其发生指令来进行Service注册或者Service查询等服务

6、服务注册终极奥义

上节已经明白了service manager进程在初始化binder驱动模块之后就开启死循环模式,轮询检查解析来自binder驱动程序的一些命令,除此之外service manager还提供了几个接口供Android系统其他进程进行服务注册和查询等操作。service manager采用观察者模式简单的实现这些功能, 当然肯定不可能这么简单,service manager进程中的观察者再完美也只属于用户空间,并不能真正跨进程通信,那么一个能让所有进程都能够进行通信的观察者的实现在哪里呢?答案很明显,肯定是在内核空间的binder驱动程序里。

1)、service manager向binder驱动请求服务注册

接下来我们继续分析服务注册,它的目的地肯定不可能是service manager。如下代码:

//frameworks/native/cmds/servicemanager/service_manager.c
int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle, uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid) {
    //....
    //向binder驱动请求服务注册,并提供了handle
    binder_acquire(bs, handle);
    //死亡处理
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}
//frameworks/native/cmds/servicemanager/binder.c
void binder_acquire(struct binder_state *bs, uint32_t target)
{
    uint32_t cmd[2];
    cmd[0] = BC_ACQUIRE;
    cmd[1] = target; //参数为handle句柄
    //向binder驱动模块发起请求,子功能码BC_ACQUIRE
    binder_write(bs, cmd, sizeof(cmd));
}
int binder_write(struct binder_state *bs, void *data, size_t len)
{
    struct binder_write_read bwr;
    int res;
    bwr.write_size = len;
    bwr.write_consumed = 0;
    bwr.write_buffer = (uintptr_t) data;
    bwr.read_size = 0;
    bwr.read_consumed = 0;
    bwr.read_buffer = 0;
    //向binder驱动程序进行ioctl系统调用,控制命令BINDER_WRITE_READ
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    return res;
}

果然与我们预想的一样,当其他进程调用service manager的do_add_service接口进行服务注册的时候,最终进行了系统调用,向内核空间的binder驱动程序发起了ioctl操作,控制命令为BINDER_WRITE_READ(读写数据操作),子功能码BC_ACQUIRE(请求服务注册),且参数为该服务的handle句柄。

2)、binder驱动处理BC_ACQUIRE消息

再次将视线移步到binder驱动程序的binder_ioctl函数,如下代码:

//kernel/drivers/staging/android/binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    //.....
	switch (cmd) {
	case BINDER_WRITE_READ: 
        //创建内核空间的binder_write_read用来描述进程间通信中请求数据和返回数据
		struct binder_write_read bwr;
        //拷贝用户空间的请求数据到bwr中
		if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { }
        //将bwr中的请求数据交给上面的线程thread中进行处理
		if (bwr.write_size > 0) ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
        //获取binder线程thread中处理的结果保存到bwr中
		if (bwr.read_size > 0) ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
        //拷贝bwr中返回数据到用户空间
		if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { }
		break;
    }
    //.....
}
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, void __user *buffer, int size, signed long *consumed)
{
    //定义子功能码和跟随的参数指针
    uint32_t cmd;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;
    while (ptr < end && thread->return_error == BR_OK) {
        //获取子功能码
        if (get_user(cmd, (uint32_t __user *)ptr)) return -EFAULT;
        //解析子功能码
        switch (cmd) {
            case BC_INCREFS:
            case BC_ACQUIRE:    //服务请求注册
            case BC_RELEASE:
            case BC_DECREFS: {
                uint32_t target;            //参数target对应用户空间传递的handle
                struct binder_ref *ref;     //指定handle句柄的binder引用(代理)对象
                const char *debug_string;   //指定handle句柄的服务描述,例如"media.play"等
                //获取子功能码后面的参数,这里获取出来的就是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)) {
                    //handle句柄为0,即service manager
                    ref = binder_get_ref_for_node(proc, binder_context_mgr_node);
                    if (ref->desc != target) binder_user_error("......");
                } else {
                    //handle句柄非0,即注册/注销其他service组件
                    //创建/获取指定handle的binder引用对象
                    ref = binder_get_ref(proc, target);
                }
                switch (cmd) {
                    case BC_INCREFS:    //注册service
                        binder_inc_ref(ref, 0, NULL); break;
                    case BC_ACQUIRE:    //请求service
                        binder_inc_ref(ref, 1, NULL); break;
                    case BC_RELEASE:    //释放service
                        binder_dec_ref(ref, 1); break;
                    case BC_DECREFS:    //销毁service
                    default:
                        binder_dec_ref(ref, 0); break;
                }
                break;
            }
        }
    }
}

如上代码,binder_ioctl在解析BINDER_WRITE_READ控制命令调用了binder_thread_write函数,该函数中在解析功能码BC_ACQUIRE的时候,先获取了handle句柄值,然后创建或者从本地维护的红黑树中获取对应的句柄值的binder_ref引用对象。并提供了函数binder_inc_ref和binder_dec_ref来给这个红黑树添加或删除成员。如下代码:

//kernel/drivers/staging/android/binder.c
//全局链表表头 存储所有handle句柄对应的binder_node 观察者模式的数据体 
struct list_head *target_list;
//binder_thread_write调用的第三个参数是NULL,不知道为什么命名要跟上面一样?
static int binder_inc_ref(struct binder_ref *ref, int strong, struct list_head *target_list)
{
	int ret;
	if (strong) {
		if (ref->strong == 0) {
			//向红黑树中插入一个binder_node成员
            //这里通过binder_ref 引用对象得到实体对象binder_node
            ret = binder_inc_node(ref->node, 1, 1, target_list);
			if (ret) return ret;
		}
		ref->strong++;    //强引用计数
	} else {
		if (ref->weak == 0) {
			ret = binder_inc_node(ref->node, 0, 1, target_list);
			if (ret) return ret;
		}
		ref->weak++;    //弱引用计数
	}
	return 0;
}
static int binder_dec_ref(struct binder_ref *ref, int strong)
{
	if (strong) {
		if (ref->strong == 0) {
			binder_user_error("binder: %d invalid dec strong....");
			return -EINVAL;
		}
		ref->strong--;
		if (ref->strong == 0) {
			int ret;
            //向红黑树中删除一个binder_ref成员
			ret = binder_dec_node(ref->node, strong, 1);
			if (ret) return ret;
		}
	} else {
		if (ref->weak == 0) {
			binder_user_error("binder: %d invalid dec weak.....");
			return -EINVAL;
		}
		ref->weak--;
	}
    //引用计数为0的时候销毁该binder_ref对象
	if (ref->strong == 0 && ref->weak == 0) binder_delete_ref(ref);
	return 0;
}
//添加实体对象binder_node到红黑树target_list
static int binder_inc_node(struct binder_node *node, int strong, int internal, struct list_head *target_list)
{
	if (strong) {
		if (internal) {
			if (target_list == NULL && node->internal_strong_refs == 0 && !(node == binder_context_mgr_node && node->has_strong_ref)) return -EINVAL;
			node->internal_strong_refs++;
		} else
			node->local_strong_refs++;
		if (!node->has_strong_ref && target_list) {
			list_del_init(&node->work.entry);
            //将binder_node实体对象添加到红黑树末尾
			list_add_tail(&node->work.entry, target_list);
		}
	} else {
        //...弱引用处理方式同上
	}
	return 0;
}
//移除实体对象binder_node从红黑树target_list
static int binder_dec_node(struct binder_node *node, int strong, int internal)
{
	if (strong) {
		if (internal)
			node->internal_strong_refs--;
		else
			node->local_strong_refs--;
		if (node->local_strong_refs || node->internal_strong_refs) return 0;
	} else {
		if (!internal)
			node->local_weak_refs--;
		if (node->local_weak_refs || !hlist_empty(&node->refs)) return 0;
	}
	if (node->proc && (node->has_strong_ref || node->has_weak_ref)) {
		if (list_empty(&node->work.entry)) {
			list_add_tail(&node->work.entry, &node->proc->todo);
			wake_up_interruptible(&node->proc->wait);
		}
	} else {
		if (hlist_empty(&node->refs) && !node->local_strong_refs &&
		    !node->local_weak_refs) {
			list_del_init(&node->work.entry);
			if (node->proc) {
				rb_erase(&node->rb_node, &node->proc->nodes);
			} else {
				hlist_del(&node->dead_node);
			}
			kfree(node);
			binder_stats_deleted(BINDER_STAT_NODE);
		}
	}
	return 0;
}

上面的代码又给我们展示了一个经典的观察者模式的实现,最终水落石出,带有service组件的服务端进程通过service manager进程的注册服务接口,先添加在service manager进程中维护的svclist 列表中,并根据该列表的索引得到handle句柄值,最终将该handle值传递到内核空间的binder驱动层,由binder驱动程序在内核空间中创建对应binder_node实体对象,并添加到target_list红黑树中进行维护。注销服务同上,先在service manager进程中找到该服务和句柄值,最终由binder驱动程序统计其引用计数,如果引用计数已经递减到0,就从红黑树中移除并销毁对应的binder_node实体对象。

7、服务查询终极奥义

在处理SVC_MGR_GET_SERVICE消息的时候,从本地维护的观察者列表中获取到对应的句柄handle值(service manager进程维护的队列主要用来存储句柄值),然后将包含句柄值的重要数据包发送给binder驱动程序,如下代码:

//frameworks/native/cmds/servicemanager/service_manager.c
int svcmgr_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply)
    //...
    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL)  return -1; 
        //本地查询service的句柄
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
        if (!handle) break;
        //封装返回数据reply且把刚刚获取出来的句柄传递进去
        bio_put_ref(reply, handle);
        return 0;
    //...
    }
}
//frameworks/native/cmds/servicemanager/binder.c
void bio_put_ref(struct binder_io *bio, uint32_t handle) {
    struct flat_binder_object *obj;
    if (handle)
        obj = bio_alloc_obj(bio);
    else
        obj = bio_alloc(bio, sizeof(*obj));
    if (!obj)  return;
    obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    //封装返回数据reply,设置消息码BINDER_TYPE_HANDLE
    obj->hdr.type = BINDER_TYPE_HANDLE;
    //封装返回数据reply,将句柄值设置进去
    obj->handle = handle;
    obj->cookie = 0;
}
void binder_send_reply(struct binder_state *bs, struct binder_io *reply, binder_uintptr_t buffer_to_free, int status) {
    //封装binder驱动层的功能码,表示该数据包为进程间通信返回的数据
    data.cmd_reply = BC_REPLY;
    //...
    //内部封装了binder驱动程序系统调用
    binder_write(bs, &data, sizeof(data));
}

值得注意,在查询到对应service的句柄之后,将其封装成BINDER_TYPE_HANDLE填充句柄handle的数据包,最终通过binder_send_reply函数将其发送给驱动程序。因此我们依次在binder驱动程序中解析功能码BC_REPLY和子功能码BINDER_TYPE_HANDLE,流程如下:

int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, void __user *buffer, int size, signed long *consumed) {
    //获取命令CMD
    if (get_user(cmd, (uint32_t __user *)ptr)) return -EFAULT;
	switch (cmd) {
        //...
        case BC_TRANSACTION:
        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);
			break;
		}
    }
}
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) {
    //...省略...
    fp = (struct flat_binder_object *)(t->buffer->data + *offp);
	switch (fp->type) {
        //...省略...
		case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {
			struct binder_ref *ref = binder_get_ref(proc, fp->handle);
                    //对生命周期的引用进行处理
			if (ref->node->proc == target_proc) {
				if (fp->type == BINDER_TYPE_HANDLE)
					fp->type = BINDER_TYPE_BINDER;
				else
					fp->type = BINDER_TYPE_WEAK_BINDER;
				fp->binder = ref->node->ptr;
				fp->cookie = ref->node->cookie;
				binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
			} else {
				struct binder_ref *new_ref;
				new_ref = binder_get_ref_for_node(target_proc, ref->node);
				fp->handle = new_ref->desc;
				binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
			}
		} break;
    }
}

是的,Binder进程间通信(IPC)机制可以实现跨进程调用原进程的方法。 在Binder IPC中,存在客户端和服务端两个角色。客户端通过Binder代理(Proxy)向服务端发送请求,而服务端通过Binder存根(Stub)接收请求并执行相应的方法。 当客户端向服务端发送请求时,可以通过Binder IPC机制实现跨进程调用原进程的方法。具体步骤如下: 1. 客户端调用:客户端通过Binder代理(Proxy)调用服务端的方法,并传递相应的参数。 2. 数据传输:客户端将方法调用请求和参数打包成一个Parcel对象,并通过Binder IPC机制将该Parcel对象发送给服务端。 3. 服务端接收:服务端在接收到客户端的请求后,通过Binder存根(Stub)解析接收到的Parcel对象,并提取出方法调用请求和参数。 4. 方法执行:服务端根据解析得到的方法调用请求和参数,执行相应的方法。 5. 返回结果:在方法执行完毕后,服务端将返回的结果打包成一个Parcel对象,并通过Binder IPC机制将该Parcel对象发送回客户端。 6. 客户端接收:客户端在接收到服务端返回的结果后,通过Binder代理(Proxy)解析接收到的Parcel对象,提取出返回结果。 需要注意的是,跨进程调用原进程方法需要在服务端实现相应的方法,并且确保方法的调用参数和返回值能够正确地在进程间进行序列化和反序列化。此外,由于跨进程调用涉及到进程间的通信,因此需要注意线程安全和数据一致性的问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诸神黄昏EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值