进程空间划分
在 Linux 中一个进程空间可以分为用户空间和内核空间,不同的进程它们的用户空间数据不可共享,但是它们的内核空间的数据可共享,即所有进程共用 1 个内核空间。进程内用户空间和内核空间进行交互需通过系统调用。

为什么 Android 使用 Binder 实现多进程通信?
Android 系统时基于 Linux 内核的,Linux 已经提供了多种 IPC 方式,如下:
- 管道:在创建时分配一个 page 大小的内存,缓存区大小比较有限
- 消息队列:信息复制两次,额外的 CPU 消耗;不合适频繁或信息量大的通信
- 共享内存:无须复制,共享缓存区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决。
- Socket:作为更通用的接口,传输效率低,主要用于不同机器或者跨网络的通信。
- 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程不同线程之间的同步手段。
所以,Android 为啥又单独弄出一个 Binder 呢?主要有如下原因:
- 性能
Binder 数据拷贝只需要一次(内存映射),而管道、消息队列、Socket 都需要 2 次,但共享内存方式一次内存拷贝都不需要;从性能角度来说,Binder 性能仅次于共享内存。 - 稳定性
Binder 是基于 C/S 架构的,Client 端有什么需求,直接发送给 Server 端去完成,架构清晰明朗,Server 端和 Client 端相对独立,稳定性较好;而共享内存实现方式复杂,没有客户端与服务端之别,需要充分考虑到访问临界资源的并发同步问题,否则可能出现死锁等问题;从稳定性角度看,Binder 架构优于共享内存。 - 安全性
Binder 机制为每个进程分配了 UID/PID 来作为鉴别身份的标示,在 Binder 通信时会根据 UID/PID 进行有效性检测,而传统的进程间通信方式并没有做出严格的验证,如 Socket 通信 IP 地址时客户端手动填入,容易出现伪造。
UID 和 PID 区别
PID(Process Identifier) 来源于 Linux,在进程启动的时候系统会为进程分配一个独一无二的标识,进程销毁后 PID 会被系统回收,但是在 Android 中一般不会重新分配,后面的进程 PID 会比前面的进程的大。
UID(User Identifier),同样来源于 Linux 中,但是在 Android 中不太一样,Android 最初的设计是单用户,所以 UID 并不是为了区别用户的,而是为了不同程序间进行数据共享。默认情况下每个应用都有自己一个 UID,不过可以通过在 Manifenst 文件下设置 sharedUserId 相同的 UID,同时保证签名文件一样,这样就达到共享数据的目的。
Binder 原理
直观地说,Binder 是 Android 中的一个类,它实现了 IBinder 接口;从 Android Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager...) 和相应 ManagerService 的桥梁;从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 服务。
Binder 定义了四个角色:Server,Client,ServiceManager 和 Bidner 驱动,其中 Server、Client、ServiceManager 运行于用户空间,Binder 驱动运行于内核空间。
-
ServiceManager,服务的管理者,将 Binder 名字转换为 Client 中对该 Binder 的引用,使得 Client 可以通过 Binder 名字获得 Service 中 Binder 实体的引用。
-
Binder 驱动,可以理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,提供 open()、mmap()、poll()、ioctl() 等标准文件操作。负责进程之间 binder 通信的建立,传递,计数管理以及数据的传递交互等底层支持。
-
Server&Client,服务器和客户端,在 Binder 驱动和 ServiceManager 提供的基础设施上,进行 Client-Server 之间的同行。
Binder 工作原理:
- 服务端 ,在服务端创建好了一个 Binder 对象后,内部就会开启一个线程用于接收 Binder 驱动发送的消息,收到消息后会执行 onTransact() 方法,并按照参数执行不同的服务端代码。
- Binder驱动,在服务端成功创建 Binder 对象后,Binder 驱动也会创建一个 mRemote 对象(也是 Binder 类),客户端可借助它调用 transact() 即可向服务端发送消息。
- 客户端,客户端要想访问 Binder 的远程服务,就必须获取远程服务的 Binder 对象在 Binder 驱动层对应 mRemote 引用。当获取 mRemote 对象的引用后,就可以调用 Binder 对象暴露给客户端的方法。
当客户端发起远程请求时,由于当前线程会被挂起直至线程服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在 UI 线程中发起此远程请求。其次,由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
网友评论