管道
- 半双工通信,单向的,一个进程发送另一个进程接收
- 通常在父子进程、兄弟进程间通信
socket 通信
- 由网络 socket 发展而来, 不需要经过网络协议栈,打包和拆包。 只需要将应用层数据从一个进程拷贝到另一个进程
消息队列
- 消息队列是一个消息的链表
- 具备异步能力,同时克服了拥有同样能力的信号承载信息量少的问题
- 具有数据传输能力,克服了管道缓冲区大小受限(1页 4kb)的问题, 有 16kb(总容量),单词 8KB
- 应用:Android 线程通信使用的就是 消息队列
共享内存
- 将一块物理地址映射到多个进程的用户地址空间, 但是会有安全问题,多线程通信时需要对 共享区域加锁,例如信号量
信号
- 标记事件的整型数据
- 异步通信机制,通知接收进程某个事件的发生。通过注册响应函数,完成对事件的处理
- 应用: 程序出现ANR问题就会发送 SIGNALQUIT 信号,可以 使用 sigaction 函数 注册该信号的响应以监听 ANR
信号量
- 信号量实际上可以看成是一个计数器,用来控制多个进程对共享资源的访问。主要作为进程间以及同一进程内不同线程之间的同步手段
- 信号量会有初值(>0),每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程要想访问就必须等待,当该进程执行完这段工作(我们称之为临界区)之后,就会执行V操作来对信号量进行+1操作。
为什么Android选择Binder作为应用程序中主要的IPC机制?
Binder基于C/S架构,进行跨进程通信数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要;从性能角度看,Binder性能虽然比管道等方式好,但是不如共享内存。
但是传统Linux IPC的接收方无法获得对方进程可靠的UID/PID (权限校验、安全管控),只能由使用者在传递的数据包里填入UID/PID,伪造身份非常简单;而Binder不同,可靠的身份标记只有由IPC机制本身在内核中添加,binder就是这么做的,不由用户应用程序控制,直接在内核向数据中添加了进程身份标记。
因此综合考虑,Binder更加适合system_server进程与上层App层的IPC交互。
PID(Process ID,进程 ID)内核用来标识一个进程。
UID(User ID,Android 重点)代表应用身份标识
Binder 是一种跨进程通信的方式,
传统的跨进程通信需要数据拷贝两次,也就是 从 用户空间 复制到内核空间的内核缓存区,然后再将数据复制到另一个进程的用户空间
Binder 机制 使用了内存映射,只需要数据拷贝1次
具体原理:
Binder驱动,在内核空间创建了一块 接收缓存区,将发送进程的内核缓存区 和 接收进程的用户空间地址 同时映射在同一个共享接收缓存中(同一个物理内存)。
发送进程,将数据发送到内核缓存区(数据拷贝一次),由于 内核缓存区 与 接收进程的用户空间地址存在映射,因此,相当于发送到了接收进程的用户空间地址。所以一次通信只需要数据拷贝一次
具体流程
角色模型:
Binder 跨进程通信机制基于 Client - Server 模式
Client 进程 表示使用服务 的进程
Server 进程 是 提供服务的进程
ServiceManager 管理 Service 的注册 和 查询 。类似于路由器
Binder 驱动,是 连接 Service 进程、Client 进程 和 ServiceManager 的桥梁, 通过内存映射 传递进程间的数据
具体流程:
步骤一:首先服务端 向 ServiceManager 注册服务:
- Server 进程 创建Binder对象(继承 IBinder),实现具体的业务逻辑
- Server 进程 通过 Binder 驱动向 ServiceManager.addService() 发起服务注册请求,将 Binder对象 和 服务名称绑定
步骤二:客户端从ServiceManager获取服务代理(Proxy):
- 客户端 通过Binder 驱动向 ServiceManager传递 服务名称(如, “android.app.ActivityManager”) getServicde() 发起查询
- ServiceManager 在注册表查找对应服务,并通过Binder驱动向客户端返回一个Binder引用(内核分配的句柄)
- 客户端将该引用封装为Proxy对象, 后续通过Proxy调用服务端方法
步骤三:客户端通过 Proxy 调用服务端方法
- 客户端调用 Proxy 的方法(如startActivity()),Proxy 将参数打包为 Parcel 对象;
- Proxy 通过transact()方法向 Binder 驱动发送请求,传入目标服务的句柄、方法编号、Parcel 数据;
- Binder 驱动通过句柄找到对应的服务端进程,将 Parcel 数据从客户端用户空间拷贝到内核空间;
- 内核唤醒服务端进程,将数据从内核空间映射到服务端用户空间;
- 服务端的 Binder 对象接收数据,解析后调用实际方法(如 AMS 的startActivity());
- 服务端将返回结果打包为 Parcel,通过 Binder 驱动回传客户端;
- 客户端 Proxy 解析 Parcel,获取返回结果。
Binder 架构中,ServiceManager、客户端和服务端都运行在用户空间,Binder 驱动运行在 Linux 内核空间,负责进程间通信。
Binder默认采用同步通信方式,如果 AIDL 方法声明 oneway,则变成异步调用。
Binder 驱动会为每个进程维护事务缓冲区,大小约为 1MB,一次事务的数据需要写入接收方的 Binder 缓冲区,因此受到该限制。多个未处理事务会共享该空间,其中 oneway 异步事务最多占用约一半。由于这个限制,Intent、Bundle、AIDL 参数等不能传递过大的数据。 这块区域就是 接收方用户空间 和 内核缓冲区 映射的大小。
Binder 相比于 其他 跨进程通信机制优势:
- 性能更好,只需要拷贝一次
- 方便易用,逻辑更加简单直接
- 安全,Binder将进程的 UID/PID 与通信对象绑定,接收方可以直接验证发送方身份,防止恶意进程伪装系统服务(如 AMS)
参考:
1
2