Android Binder机制解析
在Android中跨进程通信被频繁使用,比如当用户点击按钮跳转到一个新的Activity页面,这时我们的app进程会和SystemServer进程频繁通信来创建一个新的Activity,这种例子不胜枚举。所以作为一个Android应用的开发者,理应理解其原理。在讨论Binder前,我们先列举下Linux支持的进程通信方式都有哪些。
Android进程间通信场景
Linux进程通信方式
为什么要自己实现ipc
通过对比,分别介绍优点
使用?
上层拓展?
Linux进程通信方式
- 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道 (named pipe) : 命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存( shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 文件(File):文件系统对文件描述符的读/写控制,进程间一方对文件写,一方对文件读,达到文件之间的通信,可以是不相关进程间的通信。
Android为什么要自己实现ipc
- 性能:在移动设备上,由于性能受到各方面的限制,例如电量,所以在使用进程间通信时,需要对性能这块有很大的要求。Socket、消息队列这些,在进程间通信时,数据拷贝需要两次,而内存共享只需拷贝无需拷贝,但实现起来比较复杂。Binder在数据上拷贝只需一次,它通过内存映射的机制实现了一次数据拷贝。
- 安全:传统的IPC,UID/PID只能由用户在数据包填入,这样会导致接收方无法获取到可靠UID/PID,而且还会存在被恶意程序利用的可能。Binder机制中可靠的身份标志只能在IPC机制的内核中添加的,它从协议本身就支持对通信双方进行身份校验,大大提高了安全性。
- 稳定:Binder基于C/S架构,使用者与提供服务者相互独立,整体清晰,从而提高了整体的稳定性。
下表是Binder和主要Linux进程通信方式的对比:
| IPC方式 | 效率 | 单双向 | 模型 | 安全性 |
|---|---|---|---|---|
| Binder | 高 | 双向 | 多堆多 | ✅ |
| Pipe | 一般 | 单向 | 一对一 | ✅ |
| 共享内存 | 最高 | 双向 | 多堆多 | ❎ |
| Socket | 很低 | 双向 | 多堆多 | ✅ |
| File | 低 | 双向 | 多堆多 | ❎ |
Binder
Binder 是基于内存映射 mmap 设计实现的,我们需要先了解一下 mmap 的概念。
mmap
mmap 是一个 Unix 系统调用,用于将文件或设备映射到进程的地址空间,从而允许进程通过内存访问文件内容或设备数据。mmap 的全称是 “memory map”,它将文件或设备的一部分直接映射到进程的地址空间中,使得对该部分数据的读写操作可以像访问内存一样简单高效。
通过 mmap,进程可以将文件映射到内存中的一个地址区域,然后直接读写该区域的数据,而不需要通过传统的文件 I/O 接口,例如 read 和 write。这样可以提高 I/O 操作的性能,特别是对于大文件的处理。此外,mmap 还可以用于创建共享内存区域,使得不同进程之间可以共享数据。
在使用 mmap 后,可以通过修改内存区域的内容来修改文件内容,这种修改会直接反映到原始文件中。因此,mmap 也可以用于实现文件的原子写入或原子更新操作。
总之,mmap 提供了一种灵活高效的方式来访问文件内容或设备数据,并且可以简化对文件和内存的管理和操作。
原理
一次 Binder IPC 通信的过程分为以下几个步骤:
首先,Binder 驱动在内核空间中开辟出一个数据接收缓冲区
接着,在内核空间中开辟出一个内核缓冲区
将内核缓冲区与数据接收缓冲区建立映射关系
将数据接收缓冲区与接收进程的用户空间地址建立映射关系
发送方进程通过 copy_from_user 将数据从用户空间复制到内核缓冲区
由于内核缓冲区与数据接收缓冲区有映射关系,同时数据接收缓冲区与接收进程的用户空间地址有映射关系,所以在接收进程中可以直接获取到这段数据
这样便完成了一次 Binder IPC 通信,它的原理如下图所示:

通过 mmap,Binder 通信时,只需要经历一次数据复制,性能要优于管道、消息队列、socket等方式,在安全性,易用性方面又优于共享内存。鉴于上述原因,Android 选择了这种折中的 IPC 方式,来满足系统对稳定性、传输性能和安全性方面的要求。
Binder架构
Binder 是一种 C/S 架构,分为 BpBinder(客户端)和 BBinder(服务端),他们都派生自 IBinder。其中 BpBinder 中的p表示 proxy,即代理。BpBinder 通过 transact 来发送事务请求,BBinder 通过 onTransact 来接收相应的事务。

Binder 采用分层架构设计:

Binder 一次通信的时序图如下:


