Android IPC

相关知识

操作系统为什么需要进程

首先,我们需要明确,程序的执行需要一系列系统资源的支持,比如中央处理器,内存,文件,网络等,在早期的单道批处理系统的计算机中,每一个时刻最多只能有一道用户程序被装入内存中执行,其他用户程序只能等待,由于CPU的速度远远快于IO的速度,就会出现CPU需要等待IO就绪,在此期间CPU啥也不能干,只能干等着,造成了CPU的浪费,比如有A和B两个程序需要执行,流程可能为:

  1. CPU执行A程序
  2. A程序需要访问IO
  3. CPU等待A程序的IO访问,期间啥也不干
  4. A程序结束IO访问
  5. CPU继续执行A程序
  6. CPU完成A程序的执行
  7. CPU开始执行B程序

我们发现,上述流程中,第3步CPU需要陷入等待,等待A程序完成IO操作之后才能继续执行A程序,这个等待极大地降低了CPU的使用率,要知道CPU的速度比资源分配的速度快了不止一个量级,所以让CPU等待资源分配是对CPU资源极大的浪费。

所以后来引入了多批道处理系统,核心就是在原来CPU等待的时候让其执行其他程序,但是这样又会出现一个问题,系统的资源是有限的,多个“同时运行”的程序往往需要的资源是一样的,会出现资源使用的冲突,那么操作系统就需要以一个更合理的方式为不同程序分配系统资源,进程就是为了解决这个问题提出来的概念,进程是一个抽象的概念,他的作用是申请系统资源,并且给程序提供资源,从而解决原来的程序之间因为资源共享而相互限制的问题,这样就可以提高系统资源的利用率以及系统的处理能力。相当于操作系统以进程为单位进行资源的管理和分配,比如A进程正在执行的时候就为B进程分配好资源,那么当A进程执行完了,就可以立刻执行B进程。

相当于A在执行的时候,B进程就可能已经分配好资源了,只等CPU有空闲就可以立刻插空执行。

进程是资源分配的最小单位,线程是CPU调度的最小单位。
线程是啥?和进程的区别是啥?
我们说程序的执行需要一系列系统资源的支持,这些资源可以分为两大类:CPU和其他,对于其他资源,操作系统是以进程为单位进行分配和管理的,那么对于CPU资源,操作系统是以线程为单位进行分配和管理的,即操作系统以线程为基本单位进行CPU时间片的分配。
进程的上下文切换的时间开销是远远大于线程上下文时间的开销。这样就让CPU的有效使用率得到提高,线程是共享了进程的上下文环境的更为细小的CPU时间段。线程主要共享的是进程的地址空间。

preview

虚拟内存

我们知道,计算机中负责计算的单元是CPU,CPU的特点是速度快,但是容量小,而其他I/O硬件的特点是容量大,但是速度慢,为了协调二者的矛盾,内存应运而生。内存的速度介于CPU计算速度和I/O硬件之间,容量却比CPU要大,所以计算机一般的模式是先将相关数据从I/O硬件中加载到内存中,然后CPU再进行计算。

介绍进程的时候我们介绍到,现代操作系统中往往是多任务并行,这种做法的目的是为了提高CPU的利用率,但是这时候会引出一个问题,每个进程在执行的时候都会涉及到内存的操作,这样的话,会导致对内存的操作出现冲突,虚拟内存就是为了解决这种冲突而引入的:

虚拟内存 (1)

虚拟内存示意图

如上图所示,操作系统有一块物理内存(中间的部分),有两个进程(实际会更多)P1 和 P2,操作系统偷偷地分别告诉 P1 和 P2,我的整个内存都是你的,随便用,管够。可事实上呢,操作系统只是给它们画了个大饼,这些内存说是都给了 P1 和 P2,实际上只给了它们一个序号而已。只有当 P1 和 P2 真正开始使用这些内存时,系统才开始使用辗转挪移,拼凑出各个块给进程用,P2 以为自己在用 000这块 内存,实际上已经被系统悄悄重定向到真正的 001 去了,甚至,当 P1 和 P2 共用了某块内存,他们也不知道。

操作系统的这种欺骗进程的手段,就是虚拟内存。对 P1 和 P2 等进程来说,它们都以为自己占用了整个内存,而自己使用的物理内存的哪段地址,它们并不知道也无需关心。

虚拟内存就是一张张的对照表,这张表描述的是进程逻辑地址和实际物理地址的映射,比如对于P1,就应该维护这样一张表:

逻辑地址物理地址
000000
001003
002004
003011

对于P2,就应该维护这样一张表:

逻辑地址物理地址
000001
001002
002005
003007

这样,如果P1 获取 000 内存里的数据时应该去物理内存的 000 地址找,而找 001内存里的数据应该去物理内存的 003 地址,依次类推。

我们知道,操作系统内存的最小单位是Byte,像上面那样,如果物理内存的大小是4G(假设虚拟内存的大小也是4G),那么就会有4G条表的条目,每个条目包含两个32位的地址,分别是逻辑地址和物理地址,一共是2*32=64位,即8个字节(Byte),那么最后这张表所占用的内存大小是4G*8Byte=32GB,也就是说需要32GB的空间来存放单个进程的对照表,这未免太占空间了,为了解决这个问题,操作系统引入了 页(Page)的概念。

在系统启动时,操作系统将整个物理内存以 4K 为单位,划分为各个页。之后进行内存分配时,都以页为单位,那么虚拟内存页对应物理内存页的映射表就大大减小了,4G 内存,一共会有4G/4K=1024=1M个页,每个页占8Byte,总共是1M*8Byte=8M的大小,只需要 8M 的映射表即可,一些进程没有使用到的虚拟内存,也并不需要保存映射关系,这样就可以大大缩小映射表占据的空间,同样的道理,如果内存太大,比如有64G内存,担心映射表过大,可以采用多级多级页表(页表的页表),这样可以进一页减少内存消耗。操作系统虚拟内存到物理内存的映射表,就被称为页表

虚拟内存机制,会让每个进程都以为自己占用了全部内存,进程访问内存时,操作系统都会把进程提供的虚拟内存地址转换为物理地址,再去对应的物理地址上获取数据。CPU 中有一种硬件,内存管理单元 MMU(Memory Management Unit)专门用来将翻译虚拟内存地址。CPU 还为页表寻址设置了缓存策略(快表:把cpu最近找过的一个页所属的页表放进cpu中,这样cpu下次寻找的时候直接去这个表里查,如果查到就不用去内存查了,如果没查到,才去内存中查,然后更新快表),由于程序的局部性,其缓存命中率能达到 98%。

以上情况是页表内存在虚拟地址到物理地址的映射,而如果进程访问的物理地址还没有被分配,系统则会产生一个缺页中断,在中断处理时,系统切到内核态为进程虚拟地址分配物理地址。

内存映射

内存映射主要是为了提高文件的读写效率,linux文件读的核心流程是:

  1. 用户空间程序发起文件读请求
  2. 内核在内核空间的页缓存中寻找该文件,如果找到直接返回给用户空间的程序
  3. 如果不存在,先将文件页从磁盘拷贝到页缓存(常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制)中
  4. 由于用户空间无法直接访问内核空间,所以需要将上述页缓存中的内容拷贝到用户空间,完成文件读

文件写也是同样的原理和流程。

内存映射是基于上述虚拟内存产生的机制,核心作用是将逻辑地址与文件/设备进行映射,这样当程序访问到文件/设备时,内核会发送缺页中断,然后将其加载到内存中,提高了文件读写效率,具体过程如下:

  1. 进程在用户空间调用库函数mmap,原型:
    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  • start:要映射到的内存区域的起始地址,通常都是用NULL(NULL即为0)。NULL表示由内核来指定该内存地址
  • length:要映射的内存区域的大小
  • fd:文件描述符,最终可以定位到文件磁盘的物理地址
  • offset:被映射对象(即文件)从哪里开始映射,通常都是0,该值应该为PAGE_SIZE的整数倍
  1. 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

  2. 建立页表,索引是虚拟地址,值是对应的文件的物理地址

  3. 进程访问上述虚拟地址时,回去查询页表,发现对应的文件的物理地址不在内存页面上,发生缺页中断

  4. 内核发起请求调页的请求,调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则把所缺的页从磁盘装入到主存中

  5. 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是建立了页到用户空间的虚地址空间映射,以读取文件为例,避免了页从内核态拷贝到用户态。因此mmap效率更高。

动态内核可加载模块

Linux内核的整体结构已经非常庞大,而其包含的组件也非常多。我们怎样把需要的部分都包含在内核中呢?

一种方法是把所有需要的功能都编译到Linux内核。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。

有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?

Linux提供了这样一种机制,这种机制被称为模块(Module)。模块具有这样的特点:

  • 模块本身不被编译进入内核映像,从而控制了内核的大小
  • 模块一旦被加载,它就和内核中的其他部分完全一样

模块化的最大的好处是可以动态扩展应用程序的功能而无需重新编译链接生成一个新的应用程序映像(或内核)。这对应Windows系统上为动态链接库DDL(Dynamic Link Library),对应到Linux系统中为驱动模块和应用程序的共享库so(shared object)

模块的机制使驱动程序与Linux内核结合有两种方式:

  • 在编译内核时,静态地链接进内核;
  • 在系统运行时,以模块加载的方式加载进内核

进程通信

传统进程通信原理

首先我们需要知道,对于 32 位的系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。

简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

img

也就是说,对于所有的进程,其最高的1GB字节都会映射到内存的1GB区域,相当于所有进程公用物理内存的最高1GB,然后每个进程的低位3GB会基于虚拟内存的逻辑映射到物理内存低3GB的不同区域。

那么一次经典的跨进程通信的过程就是这样的:

  1. 数据发生进程发起数据发送的流程
  2. 通过系统调用copy_from_user()将数据从其用户空间拷贝到内核缓存区(内核空间),这是第一次数据拷贝,由于不同进程的内核空间是公用的,也即拷贝到了数据接收进程的内核空间
  3. 数据接收方在自己的用户空间开辟一块内存缓存区用来接收数据
  4. 数据接收进程通过系统调用copy_to_user()将数据从内核空间拷贝到自己的用户空间,这是第二次数据拷贝

如下图所示:

img

所以传统的这种跨进程通信方案至少需要2次数据的拷贝。

  1. 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;

  2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法前者太浪费空间,后者太浪费时间。

Android Binder通信原理

Android Binder机制的基础是上面介绍的内存映射和动态内核可加载模块。

首先,我们说mmap内存映射解决的问题是,由于进程中的用户区域不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间),这时可以使用 mmap() 在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

一次完整的 Binder IPC 通信过程通常是这样:

  1. 首先基于Linux动态内核可加载模块的特性,将Binder驱动装载到Linux内核(内核空间)
  2. Binder 驱动在内核空间创建一个内核缓存区;
  3. 建立内核缓存区和接收进程用户空间地址的映射;
  4. 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信;

Android Binder通信模型

介绍完 Binder IPC 的底层通信原理,接下来我们看看实现层面是如何设计的。

一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,双方实现通信必然需要借助 Binder 来实现。

preview

如上图所示,Binder主要由Client、Server、ServiceManager、Binder 驱动组成。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

角色作用备注
Server进程提供服务的进程服务端
Client进程使用服务的进程客户端
ServiceManager管理服务的注册与获取类似路由器
Binder驱动用于连接Service进程、Client进程和ServiceManager,是实现跨进程通信的桥梁Binder驱动持有每个Server进程在内核空间中的Binder实体,并给Client进程提供Binder实体的引用

一次完整的跨进程通信的流程是这样的:

  1. 首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager(在Android中这个进程是由init进程启动的/system/bin/servicemanager进程)

  2. Server 进程通过Binder驱动向 ServiceManager 注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder实体创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。

  3. Client 进程基于服务名,通过Binder 驱动从 ServiceManager 中获取到对应的 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

    preview

Android Binder实现

整体架构

Binder整体架构如下所示:

img

从上图看,Binder整体架构分为三个部分:

  • 内核空间中的驱动部分,这部分是基于上文介绍的动态内核可加载模块实现的内核逻辑
  • 用户空间中的C++部分,实现了与驱动部分的通信,实现了Binder的核心逻辑
  • 用户空间的Java部分,主要是对C++部分进行了包装,方便开发者直接使用

下面我们来自下而上介绍下Binder的整体流程。

驱动层

初始化binder驱动

开机时内核会初始化Binder驱动(binder_init),核心是通过:

//linux内核:drivers/android/binder.c
static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};
static struct miscdevice binder_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "binder",
	.fops = &binder_fops
};
ret = misc_register(&binder_miscdev);

向内核中注册了设备名称为“binder”的misc设备(创建一个/dev/binder文件),这样,用户空间的不同进程就可以基于此设备实现通信,这里需要特殊介绍的是这个file_operations结构体,该结构体的作用是,用户进程在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理,具体地,比如用户进程对/dev/binder文件进行open操作的时候,会对应到这里的binder_open函数中,用户进程对/dev/binder文件进行mmap操作的时候,会对应到这里的binder_mmap函数中,以此类推,我们发现,在最底层,我们只需要借助open、mmap、ioctl就可以实现跨进程通信,具体的:

  1. 借助open–>binder_open打开驱动设备(/dev/binder)
  2. 借助mmap–>binder_mmap建立内存映射
  3. 借助ioctl–>binder_ioctl实现用户空间向内核空间的数据传输

整个流程如下图所示:

img

什么是misc设备?
misc是英文的简称,中文名一般叫做杂项设备/杂散设备,我们知道大部分的设备都有一个明确的分类class,有一些设备进行分类时不太好分,我们不知道一些设备到底应该分到哪一类设备中去,所以最后将这些不知道分到哪类中的设备给分到misc设备中,也就是分到了杂散类中,杂散类设备是典型的字符设备,所以归到misc杂散类设备中的全部都是字符类设备。在后面我们也会看到misc杂散类设备本身就是被作为字符设备来来注册到内核中的。

下面我们分别介绍一下binder_open、binder_mmap和binder_ioctl这三个函数。

binder_open

任何进程在使用Binder之前,都需要先通过open("/dev/binder")打开Binder设备。上文已经提到,用户空间的open系统调用对应了驱动中的binder_open函数。在这个函数,Binder驱动会为调用的进程做一些初始化工作,该函数的实现如下:

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);

  // 锁保护
	binder_lock(__func__);

	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;

	binder_unlock(__func__);

	return 0;
}

struct binder_proc {
	struct hlist_node proc_node;
	struct rb_root threads;//binder_thread:描述使用Binder的线程
	struct rb_root nodes;//binder_node:描述Binder实体节点,即:对应了一个Server
	struct rb_root refs_by_desc;//binder_ref:描述对于Binder实体的引用
	struct rb_root refs_by_node;//binder_ref:描述对于Binder实体的引用
	struct list_head buffers;//binder_buffer:描述Binder通信过程中存储数据的Buffer
	struct rb_root free_buffers;//binder_buffer:描述Binder通信过程中存储数据的Buffer
	struct rb_root allocated_buffers;//binder_buffer:描述Binder通信过程中存储数据的Buffer
	...
};

这里涉及到几个结构体:

  • binder_proc:描述使用Binder的进程
  • binder_node:描述Binder实体节点,即:对应了一个Server
  • binder_thread:描述使用Binder的线程
  • binder_ref:描述对于Binder实体的引用
  • binder_buffer:描述Binder通信过程中存储数据的Buffer

binder驱动通过一个binder_procs链表来管理上述信息:

img

通过维护该链表,binder驱动可以实现对<binder节点的引用-binder节点>的管理,这样之后就可以将binder节点的引用返回给客户端,客户端通过此引用调用服务端的对应服务。

binder_mmap

在打开Binder设备之后,进程会通过mmap进行内存映射,binder_mmap函数对应了mmap的系统调用,该函数的作用有两个:

  • 申请一块内存空间,用来接收Binder通信过程中的数据
  • 对这块内存进行地址映射,以便将来访问

具体的,首先申请一块实际的物理内存,然后将Server端用户空间和内核空间的一片地址同时映射到这块内存上。在这之后,当有Client要发送数据给Server的时候,只需一次数据拷贝,即将Client发送过来的数据拷贝到上述申请的物理内存上即可(因为这块物理内存被映射到了内核空间,所以Client进程可以直接将数据拷贝上去),由于这个内存地址在服务端已经同时映射到用户空间,因此Server可以直接访问(无需额外拷贝),整个过程如下图所示:

img

对应到代码上具体对应了以下几个步骤:

  1. Server进程在启动之后,调用对/dev/binder设备的mmap操作
  2. 内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后在用户空间和内核空间同时进行映射
  3. Client通过BINDER_WRITE_READ命令发送请求,这个请求将先到驱动中,同时需要将数据从Client进程的用户空间拷贝到内核空间(绿色区域对应的地址)
  4. 驱动通过BR_TRANSACTION通知Server有人发出请求,Server进行处理。由于这块内存也在用户空间进行了映射,因此Server进程的代码可以直接访问

具体到代码实现上,我们可以看下Binder的源码:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;

	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;

	...
   // 在内核空间获取一块地址范围
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
	proc->buffer = area->addr;
	// 记录内核空间与用户空间的地址偏移
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
	mutex_unlock(&binder_mmap_lock);

  ...
	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
	proc->buffer_size = vma->vm_end - vma->vm_start;

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	// 通过下面这个函数真正完成内存的申请和地址的映射
	// 初次使用,先申请一个PAGE_SIZE大小的内存
	ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma);
	...
}

//真正完成内存的申请和地址的映射
static int binder_update_page_range(struct binder_proc *proc, int allocate,
				    void *start, void *end,
				    struct vm_area_struct *vma)
{
	void *page_addr;
	unsigned long user_page_addr;
	struct vm_struct tmp_area;
	struct page **page;
	struct mm_struct *mm;

	...

	for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
		int ret;
		struct page **page_array_ptr;
		page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

		BUG_ON(*page);
		// 真正进行内存的分配,分配一页物理大小的物理内存
		*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);

		tmp_area.addr = page_addr;
		tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
		page_array_ptr = page;
		// 在内核空间进行内存映射
		ret = map_kernel_range_noflush((unsigned long)page_addr,
					PAGE_SIZE, PAGE_KERNEL, page);
		user_page_addr =
			(uintptr_t)page_addr + proc->user_buffer_offset;
		// 在用户空间进行内存映射,vm_insert_page是Linux提供的函数,作用是映射内核页面到用户空间,vma:内核空间的地址,user_page_addr用户空间的地址,page[0]:映射的size
		ret = vm_insert_page(vma, user_page_addr, page[0]);

		/* vm_insert_page does not seem to increment the refcount */
	}
	return 0;
}
  • vm_area_struct:Linux内核中,vm_area_strcut代表了一段连续的虚拟地址,这些虚拟地址相应地映射到一个后备文件或者一个匿名文件的虚拟页。一个vm_area_struct映射到一组连续的页表项。页表项又指向物理内存page,这样就把一个文件和物理内存页相映射。

  • map_vm_area:将内核地址空间的一段地址和一些特定的内存物理页面映射

    • addr:内核虚地址的起始地址
    • size:这段虚拟地址的大小
    • port:物理页面的保护标志位
    • page:指向物理页面的指针
  • vm_insert_page:使驱动程序将分配给他们的单个page插入到用户空间对应的vm_area_struct中,该函数的参数说明:

    • vm_area_struct:要被插入的目标虚拟内存区域
    • addr:这个page的用户空间的地址
    • page:被插入的page

核心做的事就是初次mmap的时候申请一个PAGE_SIZE的内存,也就是4k大小的区域,然后将内核空间和用户空间的地址映射到分配好的物理内存区域。

这里只分配了4k大小的内存,在后续使用中很可能出现内存不够用的情况,binder驱动会在事务真正执行的时候再分配更多的内存,具体可参考:《内存的管理》

binder_ioctl

binder_ioctl是驱动中实现进程间数据传输的核心函数,该函数负责在两个进程间收发IPC数据和IPC reply数据,核心工作机制是解析不同的ioctl命令、执行对应的逻辑、replay结果(如果需要),核心代码如下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	...
	switch (cmd) {
	case BINDER_WRITE_READ:
		binder_ioctl_write_read(filp, cmd, arg, thread);
		...
		break;
	case BINDER_SET_MAX_THREADS:
		...
		break;
	case BINDER_SET_CONTEXT_MGR:
		...
		break;
	case BINDER_THREAD_EXIT:
	...
		break;
	case BINDER_VERSION: {
		...
		break;
	}
	default:
		...
	}
	...
}

可以看到,主要是解析了以下几个ioctl命令:

ioctl命令数据类型操作
BINDER_WRITE_READstruct binder_write_read收发Binder IPC数据
BINDER_SET_MAX_THREADS__u32设置Binder线程最大个数
BINDER_SET_CONTEXT_MGR__s32设置Service Manager节点
BINDER_THREAD_EXIT__s32释放Binder线程
BINDER_VERSIONstruct binder_version获取Binder版本信息

其中BINDER_WRITE_READ是使用最频繁,也是实现跨进程通信最核心的命令,该命令对应调用了binder_ioctl_write_read函数,该函数实现了跨进程间的交互,核心逻辑是:

  1. copy_from_user将请求数据copy到内核空间
  2. 处理对应逻辑
  3. 将结果通过binder_thread_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;
	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;
	}
  //把用户空间的ubuf拷贝到bwr中
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}

	if (bwr.write_size > 0) {
    //写缓存中有数据,执行binder写操作
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		trace_binder_write_done(ret);
		if (ret < 0) {
			bwr.read_consumed = 0;
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
				ret = -EFAULT;
			goto out;
		}
	}
	if (bwr.read_size > 0) {
    //读缓存中有数据,执行binder读操作
		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);
		if (!list_empty(&proc->todo))
			wake_up_interruptible(&proc->wait);
		if (ret < 0) {
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
				ret = -EFAULT;
			goto out;
		}
	}
	//将内核数据bwr拷贝到用户空间ubuf
	if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
		ret = -EFAULT;
	}
}

整体流程为:

  1. 把用户空间数据ubuf拷贝到内核空间bwr;

  2. 当bwr写缓存有数据,则执行binder_thread_write;当写失败则将bwr数据写回用户空间并退出;

  3. 当bwr读缓存有数据,则执行binder_thread_read;当读失败则再将bwr数据写回用户空间并退出;

  4. 把内核数据bwr拷贝到用户空间ubuf

具体的数据读写是在binder_thread_write和binder_thread_read中完成的,由于篇幅原因这里不展开介绍了,感兴趣的话可以查看对应源码。

总结

上面我们介绍了驱动层的核心函数和流程,驱动层的核心功能是支持底层的跨进程数据通信能力,但是如何用这些能力,是由上层的C++层来调度的,下面我们介绍一下Binder体系中的C++层。

C++层

概述

Binder Framework的C++部分,头文件位/frameworks/native/include/binder/,实现位于/frameworks/native/libs/binder/ ,Binder库最终会编译成一个动态链接库libbinder.so,供其他进程链接使用,下文中我们将Binder Framework 的C++部分称之为libbinder。

libbinder中,将实现分为Proxy和Native两端:

  • Proxy对应了上文提到的Client端,是服务对外提供的接口,代表了调用方,通常与服务的实现不在同一个进程,因此下文中,我们也称Proxy端为“远程”端,类名中带有小写字母p的(例如BpInterface),就是指Proxy端
  • Native是服务实现的一端,对应了上文提到的Server端,是服务实现的自身,因此下文中,我们也称Native端为”本地“端,类名带有小写字母n的(例如BnInterface),就是指Native端

下面我们来介绍下libbinder的核心实现。

Binder初始化
Binder初始化流程

在讲解Binder驱动的时候我们就提到:任何使用Binder机制的进程都必须要对/dev/binder设备进行open以及mmap之后才能使用,这部分逻辑是所有使用Binder机制进程共同的。对于这种共同逻辑的封装便是Framework层的职责之一。libbinder中,ProcessState类封装了这个逻辑,下面是ProcessState构造函数,在这个函数中,初始化mDriverFD的时候调用了open_driver方法打开binder设备,然后又在函数体中,通过mmap进行内存映射:

ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    ...
{
    if (mDriverFD >= 0) {
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            close(mDriverFD);
            mDriverFD = -1;
        }
    }
}

open_driver的函数实现如下所示,在这个函数中完成了三个工作:

  • 首先通过open系统调用打开了dev/binder设备
  • 然后通过ioctl获取Binder实现的版本号,并检查是否匹配
  • 最后通过ioctl设置进程支持的最大线程数量

由于Binder的数据需要跨进程传递,并且还需要在内核上开辟空间,因此允许在Binder上传递的数据并不是无无限大的。mmap中指定的大小便是对数据传递的大小限制:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) // 1M - 8k

mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

这里我们看到,在进行mmap的时候,指定了最大size为BINDER_VM_SIZE,即 1M - 8k的大小。 因此我们在开发过程中,一次Binder调用的数据总和不能超过这个大小。

为啥size指定为1M-8K这么特殊的一个值呢?

Android源码的commit信息中是这样介绍的:

commit c0c1092183ceb38dd4d70d2732dd3a743fefd567
Author: Rebecca Schultz Zavin <rebecca@android.com>
Date:   Fri Oct 30 18:39:55 2009 -0700

    Modify the binder to request 1M - 2 pages instead of 1M.  The backing store
    in the kernel requires a guard page, so 1M allocations fragment memory very
    badly.  Subtracting a couple of pages so that they fit in a power of
    two allows the kernel to make more efficient use of its virtual address space.

    Signed-off-by: Rebecca Schultz Zavin <rebecca@android.com>

diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index d7daf7342..2d4e10ddd 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -41,7 +41,7 @@
 #include <sys/mman.h>
 #include <sys/stat.h>

-#define BINDER_VM_SIZE (1*1024*1024)
+#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))

 static bool gSingleProcess = false;

Linux的内存管理是以内存页为单位去管理的,一个内存页是4K的大小,计算机读取2的n次方的内存是最高效的,所以这个1M的内存大小并没有什么毛病。

但是问题出在Linux会给内存自动添加一个保护页,如果我们指定1M大小的内存的话实际上计算机在加载内存的时候需要加载1M加1页的内存,十分零散,不高效。

所以这里减去两页,也就是8K,那所有的数据加起来小于1M,每次加载内存的时候只需要直接按1M去高效加载就可以了。

Binder通信

完成了Binder的初始化后就可以基于Binder进行跨进程通信了,C++层的跨进程通信涉及到的核心类如下:

IBinder

这个类描述了所有在Binder上传递的对象,它既是Binder本地对象BBinder的父类,也是Binder远程对象BpBinder的父类,这个类中的主要方法说明如下:

方法名说明
localBinder获取本地Binder对象
remoteBinder获取远程Binder对象
transact进行一次Binder操作
queryLocalInterface尝试获取本地Binder,如果失败返回NULL
getInterfaceDescriptor获取Binder的服务接口描述,其实就是Binder服务的唯一标识
isBinderAlive查询Binder服务是否还活着
pingBinder发送PING_TRANSACTION给Binder服务
BpBinder

代表远程Binder实例,客户端直接调用的就是这个对象,该实例有以下两个关键方法:

  • handle方法:返回指向Binder服务实现者的句柄
  • transact方法:传入不同的code和data,将远程调用的参数封装好发送给Binder驱动,达到调用远程服务的目的
BBinder

代表了本地Binder,它描述了服务的提供方,所有Binder服务的实现者都要继承这个类(的子类),客户端调用本地服务的时候是通过transact实现的:

status_t BBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    data.setDataPosition(0);

    if (reply != nullptr && (flags & FLAG_CLEAR_BUF)) {
        reply->markSensitive();
    }

    status_t err = NO_ERROR;
    switch (code) {
        case PING_TRANSACTION:
            err = pingBinder();
            break;
        case EXTENSION_TRANSACTION:
            err = reply->writeStrongBinder(getExtension());
            break;
        case DEBUG_PID_TRANSACTION:
            err = reply->writeInt32(getDebugPid());
            break;
        default:
            err = onTransact(code, data, reply, flags);
            break;
    }

    // In case this is being transacted on in the same process.
    if (reply != nullptr) {
        reply->setDataPosition(0);
    }

    return err;
}

可以看到,当transact方法被调用后最终会调用onTransact:

  • onTransact方法:根据参数中的code和data调用对应服务具体的实现
IInterface

每个Binder服务都是为了某个功能而实现的,因此其本身会定义一套接口集(通常是C++的一个类)来描述自己提供的所有功能。而Binder服务既有实际实现服务的类,也要有给客户端进程调用的类。为了便于开发,这两种类里面的服务接口应当是一致的,例如:假设服务实现方提供了一个接口为add(int a, int b)的服务方法,那么其远程接口中也应当有一个add(int a, int b)方法。因此为了实现方便,本地实现类和远程接口类需要有一个公共的描述服务接口的基类来继承。而这个基类通常是IInterface的子类,IInterface的定义如下:

class IInterface : public virtual RefBase
{
    public:
      IInterface();
      static sp<IBinder>  asBinder(const IInterface*);
      static sp<IBinder>  asBinder(const sp<IInterface>&);

    protected:
       virtual                     ~IInterface();
       virtual IBinder*            onAsBinder() = 0;
};

IInterface类定义了一个需要子类实现的onAsBinder方法,onAsBinder在本地对象的实现类中返回的是本地对象,在远程对象的实现类中返回的是远程对象。onAsBinder方法被两个静态方法asBinder方法调用,有了这些接口之后,在代码中便可以直接通过IXXX::asBinder方法获取到不用区分本地还是远程的IBinder对象,这个在跨进程传递Binder对象的时候有很大的作用(因为不用区分具体细节,只要直接调用和传递就好)。

BpInterface&BnInterface
  • BpInterface:远程接口的基类,远程接口是供客户端调用的接口集
  • BnInterface:本地接口的基类,本地接口是需要服务中真正实现的接口集

BpInterface和BnInterface的定义如下:

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;

protected:
    virtual IBinder*            onAsBinder();
};

template<typename INTERFACE>
IBinder* BnInterface<INTERFACE>::onAsBinder()
{
    return this;
}

template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
        const String16& _descriptor)
{
    if (_descriptor == INTERFACE::descriptor) return sp<IInterface>::fromExisting(this);
    return nullptr;
}

// ----------------------------------------------------------------------

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);

protected:
    virtual IBinder*            onAsBinder();
};

template<typename INTERFACE>
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
{
    return remote();
}

可以看到这两个类都是模板类,它们首先继承了INTERFACE,这里的INTERFACE便是我们Binder服务接口的基类。

其次,BnInterface继承了BBinder类,由此可以通过复写onTransact方法来提供实现。

另外,BpInterface继承了BpRefBase,通过这个类的remote方法可以获取到指向服务实现方的句柄。在客户端接口的实现类中,每个接口在组装好参数之后,都会调用remote()->transact来发送请求,而这里其实就是调用的BpBinder的transact方法,这样请求便通过Binder到达了服务实现方的onTransact中。

基于Binder框架开发的服务,除了满足上文提到的类名规则之外,还需要遵守其他一些共同的规约:

  • 为了进行服务的区分,每个Binder服务需要指定一个唯一的标识,这个标识通过getInterfaceDescriptor返回,类型是一个字符串。通常,Binder服务会在类中定义static const android::String16 descriptor;这样一个常量来描述这个标识符,然后在getInterfaceDescriptor方法中返回这个常量。
  • 为了便于调用者获取到调用接口,服务接口的公共基类需要提供一个android::sp<IXXX> asInterface方法来返回基类对象指针。

由于上面提到的这两点对于所有Binder服务的实现逻辑都是类似的。为了简化开发者的重复工作,在libbinder中,定义了两个宏来简化这些重复工作:

//声明
#define DECLARE_META_INTERFACE(INTERFACE)                            \
    static const android::String16 descriptor;                       \
    static android::sp<I##INTERFACE> asInterface(                    \
            const android::sp<android::IBinder>& obj);               \
    virtual const android::String16& getInterfaceDescriptor() const; \
    I##INTERFACE();                                                  \
    virtual ~I##INTERFACE();                                         \

//实现
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                    \
    const android::String16 I##INTERFACE::descriptor(NAME);          \
    const android::String16&                                         \
            I##INTERFACE::getInterfaceDescriptor() const {           \
        return I##INTERFACE::descriptor;                             \
    }                                                                \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(             \
            const android::sp<android::IBinder>& obj)                \
    {                                                                \
        android::sp<I##INTERFACE> intr;                              \
        if (obj != NULL) {                                           \
            intr = static_cast<I##INTERFACE*>(                       \
                obj->queryLocalInterface(                            \
                        I##INTERFACE::descriptor).get());            \
            if (intr == NULL) {                                      \
                intr = new Bp##INTERFACE(obj);                       \
            }                                                        \
        }                                                            \
        return intr;                                                 \
    }                                                                \
    I##INTERFACE::I##INTERFACE() { }                                 \
    I##INTERFACE::~I##INTERFACE() { }                                \

有了这两个宏之后,开发者只要在接口基类(IXXX)头文件中,使用DECLARE_META_INTERFACE宏便完成了需要的组件的声明。然后在cpp文件中使用IMPLEMENT_META_INTERFACE便完成了这些组件的实现。

总结一下,C++层的Binder通信流程大致是这样的:

img

与驱动的通信

上文提到ProcessState是一个单例类,一个进程只有一个实例。而负责与Binder驱动通信的IPCThreadState也是一个单例类。但这个类不是一个进程只有一个实例,而是一个线程有一个实例。

IPCThreadState负责了与驱动通信的细节处理。这个类中的关键几个方法说明如下:

方法说明
transact公开接口,供Proxy发送数据到驱动,并读取返回结果
sendReply供Server端写回请求的返回结果
waitForResponse发送请求后等待响应结果
talkWithDriver通过ioctl BINDER_WRITE_READ来与驱动通信
writeTransactionData写入一次事务的数据
executeCommand处理binder_driver_return_protocol协议命令
freeBuffer通过BC_FREE_BUFFER命令释放Buffer

BpBinder::transact方法在发送请求的时候,其实就是直接调用了IPCThreadState对应的方法来发送请求到Binder驱动的,相关代码如下:

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }

    return DEAD_OBJECT;
}

IPCThreadState::transact方法主要逻辑如下:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;

    if (err == NO_ERROR) {
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }

    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }

    if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }

    return err;
}

这段代码应该还是比较好理解的:首先通过writeTransactionData写入数据,然后通过waitForResponse等待返回结果。TF_ONE_WAY表示此次请求是单向的,即不用真正等待结果即可返回

writeTransactionData方法其实就是在组装binder_transaction_data数据:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;

    tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } else if (statusBuffer) {
        tr.flags |= TF_STATUS_CODE;
        *statusBuffer = err;
        tr.data_size = sizeof(status_t);
        tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
        tr.offsets_size = 0;
        tr.data.ptr.offsets = 0;
    } else {
        return (mLastError = err);
    }

    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));

    return NO_ERROR;
}

这里有一个Parcel,Parcel就像一个包装器,调用者可以以任意顺序往里面放入需要的数据,所有写入的数据就像是被打成一个整体的包,然后可以直接在Binde上传输。对于基本类型,开发者可以直接调用接口写入和读出。而对于非基本类型,需要由开发者将其拆分成基本类型然后写入到Parcel中(读出的时候也是一样)。 Parcel会将所有写入的数据进行打包,Parcel本身可以作为一个整体在进程间传递。接收方在收到Parcel之后,只要按写入同样的顺序读出即可。

需要说明一下的是,Parcel类除了可以传递基本数据类型,还可以传递Binder对象

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

这个方法写入的是sp<IBinder>类型的对象,而IBinder既可能是本地Binder,也可能是远程Binder,这样我们就不可以不用关心具体细节直接进行Binder对象的传递。

这也是为什么IInterface中定义了两个asBinder的static方法,如果你不记得了,请回忆一下这两个方法:

static sp<IBinder>  asBinder(const IInterface*);
static sp<IBinder>  asBinder(const sp<IInterface>&);

而对于Binder驱动,我们前面已经讲解过,Binder驱动并不是真的将对象在进程间序列化传递,而是由Binder驱动完成了对于Binder对象指针的解释和翻译,使调用者看起来就像在进程间传递对象一样。

ServiceManager

使用Binder框架的既包括系统服务,也包括第三方应用。因此,在同一时刻,系统中会有大量的Server同时存在。那么,Client在请求Server的时候,是如果确定请求发送给哪一个Server的呢?

这个问题,就和我们现实生活中如何找到一个公司/商场,如何确定一个人/一辆车一样,解决的方法就是:每个目标对象都需要一个唯一的标识。并且,需要有一个组织来管理这个唯一的标识。

而Binder框架中负责管理这个标识的就是ServiceManager。ServiceManager对于Binder Server的管理就好比车管所对于车牌号码的的管理,派出所对于身份证号码的管理:每个公开对外提供服务的Server都需要注册到ServiceManager中(通过addService),注册的时候需要指定一个唯一的id(这个id其实就是一个字符串)。

Client要对Server发出请求,就必须知道服务端的id。Client需要先根据Server的id通过ServerManager拿到Server的标示(通过getService),然后通过这个标示与Server进行通信。

整个过程如下图所示:

img

结合我们上文的介绍,每一个Binder Server在驱动中会有一个binder_node进行对应。同时,Binder驱动会负责在进程间传递服务对象,并负责底层的转换。另外,我们也提到,每一个Binder服务都需要有一个唯一的名称。由ServiceManager来管理这些服务的注册和查找。

而实际上,为了便于使用,ServiceManager本身也实现为一个Server对象。任何进程在使用ServiceManager的时候,都需要先拿到指向它的标识。然后通过这个标识来使用ServiceManager。

这似乎形成了一个互相矛盾的现象:

  1. 通过ServiceManager我们才能拿到Server的标识
  2. ServiceManager本身也是一个Server

解决这个矛盾的办法其实也很简单:Binder机制为ServiceManager预留了一个特殊的位置。这个位置是预先定好的,任何想要使用ServiceManager的进程只要通过这个特定的位置就可以访问到ServiceManager了(而不用再通过ServiceManager的接口)。

在Binder驱动中,有一个全局的变量:

static struct binder_node *binder_context_mgr_node;

这个变量指向的就是ServiceManager。

当有进程通过ioctl并指定命令为BINDER_SET_CONTEXT_MGR的时候,驱动被认定这个进程是ServiceManager,binder_ioctl函数中对应的处理如下:

case BINDER_SET_CONTEXT_MGR:
	if (binder_context_mgr_node != NULL) {
		pr_err("BINDER_SET_CONTEXT_MGR already set\n");
		ret = -EBUSY;
		goto err;
	}
	ret = security_binder_set_context_mgr(proc->tsk);
	if (ret < 0)
		goto err;
	if (uid_valid(binder_context_mgr_uid)) {
		if (!uid_eq(binder_context_mgr_uid, current->cred->euid)) {
			pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
			       from_kuid(&init_user_ns, current->cred->euid),
			       from_kuid(&init_user_ns, binder_context_mgr_uid));
			ret = -EPERM;
			goto err;
		}
	} else
		binder_context_mgr_uid = current->cred->euid;
	binder_context_mgr_node = binder_new_node(proc, 0, 0);
	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;

ServiceManager应当要先于所有Binder Server之前启动。在它启动完成并告知Binder驱动之后,驱动便设定好了这个特定的节点。

在这之后,当有其他模块想要使用ServerManager的时候,只要将请求指向ServiceManager所在的位置即可。

在Binder驱动中,通过handle = 0这个位置来访问ServiceManager。例如,binder_transaction中,判断如果target.handler为0,则认为这个请求是发送给ServiceManager的,相关代码如下:

if (tr->target.handle) {
	struct binder_ref *ref;
	ref = binder_get_ref(proc, tr->target.handle, true);
	if (ref == NULL) {
		binder_user_error("%d:%d got transaction to invalid handle\n",
			proc->pid, thread->pid);
		return_error = BR_FAILED_REPLY;
		goto err_invalid_target_handle;
	}
	target_node = ref->node;
} else {
	target_node = binder_context_mgr_node;
	if (target_node == NULL) {
		return_error = BR_DEAD_REPLY;
		goto err_no_context_mgr_node;
	}
}
举例

下面以PowerManager为例,来看看C++的Binder服务是如何实现的:

  1. IPowerManager定义了PowerManager所有对外提供的功能接口,其子类都继承了这个接口
    1. BpPowerManager是提供给客户端调用的远程接口
    2. BnPowerManager中只有一个onTransact方法,该方法根据请求的code来对接每个请求,并直接调用PowerManager中对应的方法
    3. PowerManager是服务真正的实现

在IPowerManager.h中,通过DECLARE_META_INTERFACE(PowerManager);声明一些Binder必要的组件。在IPowerManager.cpp中,通过IMPLEMENT_META_INTERFACE(PowerManager, "android.os.IPowerManager");宏来进行实现。

本地服务的实现

本地服务的实现主要是BnPowerManagerPowerManager

  • BnPowerManager:继承自IPowerManager,复写onTransact方法,对code和data进行解析,调用对应的virtual方法(会调用到实现这些方法的PowerManager中)
  • PowerManager:继承自BnPowerManager,实现对应的virtual方法

比如:

status_t BnPowerManager::onTransact(uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags) {
  switch (code) {
  	...
      case IPowerManager::REBOOT: {
      CHECK_INTERFACE(IPowerManager, data, reply);
      bool confirm = data.readInt32();
      String16 reason = data.readString16();
      bool wait = data.readInt32();
      return reboot(confirm, reason, wait);
    }
  ...
  }
}

status_t PowerManager::reboot(bool confirm, const String16& reason, bool wait) {
  const std::string reason_str(String8(reason).string());
  if (!(reason_str.empty() || reason_str == kRebootReasonRecovery)) {
    LOG(WARNING) << "Ignoring reboot request with invalid reason \""
                 << reason_str << "\"";
    return BAD_VALUE;
  }

  LOG(INFO) << "Rebooting with reason \"" << reason_str << "\"";
  if (!property_setter_->SetProperty(ANDROID_RB_PROPERTY,
                                     kRebootPrefix + reason_str)) {
    return UNKNOWN_ERROR;
  }
  return OK;
}

通过这样结构的设计,将框架相关的逻辑(BnPowerManager中的实现)和业务本身的逻辑(PowerManager中的实现)彻底分离开,避免了二者的耦合。

服务的发布

服务实现完成之后,并不是立即就能让别人使用的。上文中,我们就说到过:所有在Binder上发布的服务必须要注册到ServiceManager中才能被其他模块获取和使用。而在BinderService类中,提供了publishAndJoinThreadPool方法来简化服务的发布,其代码如下:

static void publishAndJoinThreadPool(bool allowIsolated = false) {
   publish(allowIsolated);
   ...
}

static status_t publish(bool allowIsolated = false) {
   sp<IServiceManager> sm(defaultServiceManager());
   return sm->addService(
           String16(SERVICE::getServiceName()),
           new SERVICE(), allowIsolated);
}

核心是通过IServiceManager::addService在ServiceManager中进行服务的注册。

远程接口的实现

Proxy类是供客户端使用的。BpPowerManager需要实现IPowerManager中的所有接口。

我们还是以上文提到的reboot接口为例,来看看BpPowerManager::reboot方法是如何实现的:

virtual status_t reboot(bool confirm, const String16& reason, bool wait)
{
   Parcel data, reply;
   data.writeInterfaceToken(IPowerManager::getInterfaceDescriptor());
   data.writeInt32(confirm);
   data.writeString16(reason);
   data.writeInt32(wait);
   return remote()->transact(REBOOT, data, &reply, 0);
}

这段代码很简单,逻辑就是:通过Parcel写入调用参数进行打包,然后调用remote()->transact将请求发送出去。

其实BpPowerManager中其他方法,甚至所有其他BpXXX中所有的方法,实现都是和这个方法一样的套路。就是:通过Parcel打包数据,通过remote()->transact发送数据。而这里的remote()返回的其实就是BpBinder对象,由此经由IPCThreadState将数据发送到了驱动层:

img

服务的获取

服务已经发布之后,客户端该如何获取其服务接口然后对其发出请求调用呢?

很显然,客户端应该通过BpPowerManager的对象来请求其服务。但看一眼BpPowerManager的构造函数,我们会发现,似乎没法直接创建一个这类的对象,因为这里需要一个sp<IBinder>类型的参数。

BpPowerManager(const sp<IBinder>& impl)
   : BpInterface<IPowerManager>(impl)
{
}

那么这个sp<IBinder>参数我们该从哪里获取呢?

回忆一下前面的内容:Proxy其实是包含了一个指向Server的句柄,所有的请求发送出去的时候都需要包含这个句柄作为一个标识。而想要拿到这个句柄,我们自然应当想到ServiceManager。我们再看一下ServiceManager的接口自然就知道这个sp<IBinder>该如何获取了:

/**
* Retrieve an existing service, blocking for a few seconds
* if it doesn't yet exist.
*/
virtual sp<IBinder>         getService( const String16& name) const = 0;

/**
* Retrieve an existing service, non-blocking.
*/
virtual sp<IBinder>         checkService( const String16& name) const = 0;

这里的两个方法都可以获取服务对应的sp<IBinder>对象,一个是阻塞式的,另外一个不是。传递的参数是一个字符串,这个就是服务在addServer时对应的字符串,而对于PowerManager来说,这个字符串就是”power”。因此,我们可以通过下面这行代码创建出BpPowerManager的对象。

sp<IBinder> bs = defaultServiceManager()->checkService(serviceName);
sp<IPowerManager> pm = new BpPowerManager(bs);

但这样做还会存在一个问题:BpPowerManager中的方法调用是经由驱动然后跨进程调用的。通常情况下,当我们的客户端与PowerManager服务所在的进程不是同一个进程的时候,这样调用是没有问题的。那假设我们的客户端又刚好和PowerManager服务在同一个进程该如何处理呢?

针对这个问题,Binder Framework提供的解决方法是:通过interface_cast这个方法来获取服务的接口对象,由这个方法本身根据是否是在同一个进程,来自动确定返回一个本地Binder还是远程Binderinterface_cast是一个模板方法,其源码如下:

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

调用这个方法的时候我们需要指定Binder服务的IInterface,因此对于PowerManager,我们需要这样获取其Binder接口对象:

const String16 serviceName("power");
sp<IBinder> bs = defaultServiceManager()->checkService(serviceName);
if (bs == NULL) {
  return NAME_NOT_FOUND;
}
sp<IPowerManager> pm = interface_cast<IPowerManager>(bs);

我们再回头看一下interface_cast这个方法体,这里是在调用INTERFACE::asInterface(obj),而对于IPowerManager来说,其实就是IPowerManager::asInterface(obj)。那么IPowerManager::asInterface这个方法是哪里定义的呢?

这个正是上文提到的DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE两个宏所起的作用。IMPLEMENT_META_INTERFACE宏包含了下面这段代码:

android::sp<I##INTERFACE> I##INTERFACE::asInterface(  \
       const android::sp<android::IBinder>& obj)      \
{                                                     \
   android::sp<I##INTERFACE> intr;                    \
   if (obj != NULL) {                                 \
       intr = static_cast<I##INTERFACE*>(             \
           obj->queryLocalInterface(                  \
                   I##INTERFACE::descriptor).get());  \
       if (intr == NULL) {                            \
           intr = new Bp##INTERFACE(obj);             \
       }                                              \
   }                                                  \
   return intr;                                       \
}                                                     \

这里我们将“##INTERFACE”通过“PowerManager”代替,得到的结果就是:

android::sp<IPowerManager> IPowerManager::asInterface(
        const android::sp<android::IBinder>& obj)
{
    android::sp<IPowerManager> intr;
    if (obj != NULL) {
        intr = static_cast<IPowerManager*>(
            obj->queryLocalInterface(
                    IPowerManager::descriptor).get());
        if (intr == NULL) {
            intr = new BpPowerManager(obj);
        }
    }
    return intr;
}

这个便是IPowerManager::asInterface方法的实现,这段逻辑的含义就是:

  • 先尝试通过queryLocalInterface看看能否获得本地Binder,如果是在服务所在进程调用,自然能获取本地Binder,否则将返回NULL
  • 如果获取不到本地Binder,则创建并返回一个远程Binder。

由此保证了:我们在进程内部的调用,是直接通过方法调用的形式。而不在同一个进程的时候,才通过Binder进行跨进程的调用。

这里可能有的读者会问,queryLocalInterface是怎么实现根根据是否是服务端返回不同的结果的即该方法是如何实现服务端返回本地对象,客户端返回proxy对象的,这里说下我的理解,如果有不对的地方,欢迎大家纠正:

首先,这里的binder对象即obj是从binder驱动那边获取到的,即服务端发布服务的时候向binder驱动add的那个binder对象,拿到这个obj对象之后,我们可以去本地看下这个对象对应的地址中是否存在binder对象,如果存在,那说明本进程就是服务端,如果是客户端,因为之前没有实例化过binder对象,拿着这个从binder驱动中获取到的binder对象,自然是找不到任何东西的,这样就区分开了客户端和服务端。

整体类图如下图所示:

Binder c++层关键类作用

Java层

Java层概述

我们日常开发的大部分工作都是使用Java开发的,所以Binder对Java层也有相应的支持,我们上面介绍到,C++层已经实现了一套完整的Binder机制的模型,所以对于Java层来说不需要再重复实现一次,只要通过JNI复用下C++层的逻辑即可,核心的结构如下图所示:

img

对比C++层,Java层的核心类如下:

Java层名称类型说明对应C++的类
IInterfaceinterface供Java层Binder服务接口继承的接口IInterface
IBinderinterfaceJava层的IBinder类,提供了transact方法来调用远程服务IBinder
Binderclass实现了IBinder接口,封装了JNI的实现。Java层Binder服务的基类BBinder
BinderProxyclass实现了IBinder接口,封装了JNI的实现。提供transact方法调用远程服务BpBinder
JavaBBinderHolderclass内部存储了JavaBBinder
JavaBBinderclass将C++端的onTransact调用传递到Java端本来就是C++的类
ParcelclassJava层的数据包装器,见C++层的Parcel类分析Parcel

那么Java层和C++层是如何相互协调、相互调用的呢?

Java调C++

Binder.java中声明了待native的JNI方法:

public static final native int getCallingPid();

android_util_Binder.cpp中定义Java方法与C++方法的对应关系:

static const JNINativeMethod gBinderMethods[] = {
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid }
};

那么当Java层的getCallingPid方法被调用后,就会调用到C++层的android_os_Binder_getCallingPid方法:

static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz)
{
    return IPCThreadState::self()->getCallingPid();
}

而c++层的方法其实是对接到了libbinder中,如上面的代码所示。

这样我们就完成了Java层到C++层的调用。

C++调Java

C++层被调用之后如何调用到Java层呢?比如最关键的libbinder中的BBinder::onTransact被调用后是如何调用到Java测过的Binder::onTransact的?

这段逻辑就是android_util_Binder.cpp中JavaBBinder::onTransact中处理的了。JavaBBinder是BBinder子类,其类结构如下:

img

JavaBBinder::onTransact关键代码如下:

virtual status_t onTransact(
   uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
{
   ...
 		JNIEnv* env = javavm_to_jnienv(mVM);
   jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
       code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
   ...
}

这里调用到了env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags),这个方法的参数说明如下:

  • mObject 指向了Java端的Binder对象
  • gBinderOffsets.mExecTransact 指向了Binder类的execTransact方法
  • data 调用execTransact方法的参数
  • code, data, reply, flags都是传递给调用方法execTransact的参数

JNIEnv.CallBooleanMethod这个方法是由虚拟机实现的,即虚拟机会提供native方法来调用一个Java Object上的方法。

这样,就在C++层的JavaBBinder::onTransact中调用了Java层Binder::execTransact方法。而在Binder::execTransact方法中,又调用了自身的onTransact方法,由此保证整个过程串联了起来:

private boolean execTransact(int code, long dataObj, long replyObj,
       int flags) {
   Parcel data = Parcel.obtain(dataObj);
   Parcel reply = Parcel.obtain(replyObj);
   boolean res;
   
   res = onTransact(code, data, reply, flags);
   ...

   return res;
}
Java层的ServiceManager

有Java端的Binder服务,自然也少不了Java端的ServiceManager。我们先看一下Java端的ServiceManager的结构:

img

通过这个类图我们看到,Java层的ServiceManager和C++层的接口是一样的。

然后我们再选取addService方法看一下实现:

public static void addService(String name, IBinder service, boolean allowIsolated) {
   try {
       getIServiceManager().addService(name, service, allowIsolated);
   } catch (RemoteException e) {
       Log.e(TAG, "error in addService", e);
   }
}

   private static IServiceManager getIServiceManager() {
   if (sServiceManager != null) {
       return sServiceManager;
   }

   // Find the service manager
   sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
   return sServiceManager;
}

很显然,这段代码中,最关键就是下面这个调用:

ServiceManagerNative.asInterface(BinderInternal.getContextObject());

然后我们需要再看一下BinderInternal.getContextObject()和ServiceManagerNative.asInterface两个方法。

BinderInternal.getContextObject()是一个JNI方法,其实现代码在android_util_Binder.cpp中:

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}

而ServiceManagerNative.asInterface的实现和其他的Binder服务是一样的套路:

static public IServiceManager asInterface(IBinder obj)
{
   if (obj == null) {
       return null;
   }
   IServiceManager in =
       (IServiceManager)obj.queryLocalInterface(descriptor);
   if (in != null) {
       return in;
   }

   return new ServiceManagerProxy(obj);
}

先通过queryLocalInterface查看能不能获得本地Binder,如果无法获取,则创建并返回ServiceManagerProxy对象。

而ServiceManagerProxy自然也是和其他Binder Proxy一样的实现套路:

public void addService(String name, IBinder service, boolean allowIsolated)
       throws RemoteException {
   Parcel data = Parcel.obtain();
   Parcel reply = Parcel.obtain();
   data.writeInterfaceToken(IServiceManager.descriptor);
   data.writeString(name);
   data.writeStrongBinder(service);
   data.writeInt(allowIsolated ? 1 : 0);
   mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
   reply.recycle();
   data.recycle();
}

有了上文的讲解,这段代码应该都是比较容易理解的了,我们不再展开赘述了。

AIDL

AIDL全称是Android Interface Definition Language,它是Android SDK提供的一种机制。借助这个机制,应用可以提供跨进程的服务供其他应用使用,我们日常工作中开发的Binder程序大多数都是基于AIDL实现的,AIDL的详细说明可以参见《官方开发文档》

这里,我们就以官方文档上的例子看来一下AIDL与Binder框架的关系。

开发一个基于AIDL的Service需要三个步骤:

  1. 定义一个.aidl文件
  2. 实现接口
  3. 暴露接口给客户端使用

aidl文件使用Java语言的语法来定义,每个.aidl文件只能包含一个interface,并且要包含interface的所有方法声明。

默认情况下,AIDL支持的数据类型包括:

  • 基本数据类型(即int,long,char,boolean等)
  • String
  • CharSequence
  • List(List的元素类型必须是AIDL支持的)
  • Map(Map中的元素必须是AIDL支持的)

对于AIDL中的接口,可以包含0个或多个参数,可以返回void或一个值。所有非基本类型的参数必须包含一个描述是数据流向的标签,可能的取值是:inout或者inout

比如我们通过Android Studio创建一个默认的AIDL文件:

// IRemoteService.aidl
package com.ss.android.article.aidl_demo;

// Declare any non-default types here with import statements

interface IRemoteService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

然后build一下,Android Studio会自动为我们生成对应代码:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.ss.android.article.aidl_demo;
// Declare any non-default types here with import statements

public interface IRemoteService extends android.os.IInterface
{
  /** Default implementation for IRemoteService. */
  public static class Default implements com.ss.android.article.aidl_demo.IRemoteService
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.ss.android.article.aidl_demo.IRemoteService
  {
    private static final java.lang.String DESCRIPTOR = "com.ss.android.article.aidl_demo.IRemoteService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.ss.android.article.aidl_demo.IRemoteService interface,
     * generating a proxy if needed.
     */
    public static com.ss.android.article.aidl_demo.IRemoteService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.ss.android.article.aidl_demo.IRemoteService))) {
        return ((com.ss.android.article.aidl_demo.IRemoteService)iin);
      }
      return new com.ss.android.article.aidl_demo.IRemoteService.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.ss.android.article.aidl_demo.IRemoteService
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.ss.android.article.aidl_demo.IRemoteService sDefaultImpl;
    }
    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.ss.android.article.aidl_demo.IRemoteService impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.ss.android.article.aidl_demo.IRemoteService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

生成的代码的结构如下:

image-20210414230625886

可以看到主要有以下几个部分:

  1. 名为IRemoteService的接口,该接口继承自android.os.IInterface
  2. IRemoteService中包含了一个名为Stub的内部类(服务端),这个类是个静态抽象类,它继承自android.os.Binder并且实现了IRemoteService这个接口,复写了onTransact方法,实现了服务端被调用后code的解析
  3. Stub内部包含了一个名为Proxy的静态内部类(客户端),该类实现了IRemoteService这个接口,对应方法的实现体为打包Parcel参数、调用remote.transact、读取返回值

最终,Stub需要由开发者自己实现(因为开发者需要实现自己的业务逻辑),比如:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
   public int getPid(){
       return Process.myPid();
   }
   public void basicTypes(int anInt, long aLong, boolean aBoolean,
       float aFloat, double aDouble, String aString) {
       // Does something
   }
};

那么整个流程就是:

  1. 客户端调用Proxy.basicTypes方法
  2. Proxy.basicTypes方法中调用remote.transact方法
  3. Stub的onTransact方法被调用
  4. Stub解析code、data,调用由开发者实现的basicTypes方法

总结

本文我们首先介绍了操作系统相关的知识,涉及到为什么操作系统需要进程,以及虚拟内存、内存映射、动态内核可加载模块等基础知识,然后我们介绍了Linux传统跨进程通信的原理,基于传统通信原理的不足我们引入Android的解决方案,即Android Binder机制。

然后我们大概介绍了一下Android Binder机制的原理、模型设计、具体实现,那么最后我们总结一下Binder跨进程通信的相比于传统跨进程方案的优点:

  1. 高效
    1. 数据只需要1次拷贝,传统的管道、消息队列、Socket方案都需要2次拷贝
    2. 驱动负责内核空间的数据拷贝,相比于传统的共享内存的跨进程通信方案,不需要额外的同步处理
  2. 安全
    1. Binder机制会为进程分配UID/PID来作为身份标识,通信是会基于UID/PID进行有效性检验,传统的跨进程通信方式没有对通信双方的身份进行严格校验,比如Socket通信中只需要填入ip即可通信,比较容易被伪造
  3. 使用简单
    1. Binder机制采用CS架构,并且是基于面向对象实现的,使用者在使用时可以像调用本地方法那样调用远程服务

Android Binder机制涉及到的内容很多,本文只是简单地总结和梳理了一下整体流程,具体细节我们后面再慢慢分析,最后感谢参考文章中每一位作者的贡献,你们的无私奉献为我节省了很多时间。

参考

  1. 《写给 Android 应用工程师的 Binder 原理剖析》
  2. 《Linux进程的虚拟内存》
  3. 《Linux mmap内存映射》
  4. 《Linux内存映射mmap原理分析》
  5. 《理解Linux的虚拟内存》
  6. 《Linux内存映射表mmap详解》
  7. 《Linux的虚拟内存详解(MMU、页表结构)》
  8. 《Android 进阶13:几种进程通信方式的对比总结》
  9. 《Linux进程间通信的几种方式总结–linux内核剖析(七)》
  10. 《Android启动流程——1序言、bootloader引导与Linux启动》
  11. 《Linux驱动开发之misc类设备介绍》
  12. 《详解:Android中binder机制的实现原理及过程》
  13. Android源码
  14. Linux源码
  15. 《Android之bindService流程》
  16. 《Android跨进程通信:图文详解 Binder机制 原理》
  17. 《简析Binder数据传输流程》
  18. 《理解Android Binder机制(1/3):驱动篇》
  19. 《理解Android Binder机制(2/3):C++层》
  20. 《理解Android Binder机制(3/3):Java层》
相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页