文章目录
背景
在插件化使用时,进程间通讯使用了AIDL
进行跨进程通讯,而AIDL
底层的实现是使用Binder
机制。
在深刻了解了AIDL
以后,咱们还须要再深刻学习Binder
。html
为何须要跨进程通讯(IPC)
一个进程通常是对应一个App
,你不会但愿别的进程(App
)可以垂手可得的能操做你的App
吧,因此你的App
只能访问App
内部的数据。
可是有些场景是须要经过一个App
去操做另外一个App
的,好比:从App
中调用系统的文件管理,实现文件读写。好比从App
中读取手机通讯录的联系人信息。这种状况就须要实现进程间通讯了。web
为何是Binder?
Android
使用的 Linux
内核拥有着很是多的跨进程通讯机制,好比:管道,消息队列,共享内存,System V,Socket
等;segmentfault
那么Android
系统中的Binder
究竟有何过人之处呢?缓存
上述的进程间通讯存在的问题:安全
Socket
做为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通讯和本机上进程间的低速通讯。- 消息队列和管道采用
存储-转发
方式,即数据先从发送方缓存区
拷贝到内核开辟的缓存区
中,而后再从内核缓存区
拷贝到接收方缓存区
,至少有两次拷贝
过程。 - 共享内存虽然无需拷贝,但控制复杂,难以使用。
- 传统
IPC
没有任何安全措施,彻底依赖上层协议来确保。
Binder
的优点是:性能、稳定、安全。网络
-
性能
| IPC方式 | 数据拷贝次数|
| ---- | ---- |
| 共享内存 | 0 |
| Binder | 1 |
| Socket/管道/消息队列 | 2 |架构 -
稳定
Binder
是基于C/S
架构。经过客户端(Client
)给服务端(Server
)发送指令而服务端根据指令返回数据的方式实现。
职责明确且互相独立,所以不易出错稳定性高。svg -
安全
传统Linux IPC
的接收方没法得到发送方进程可靠的UID/PID
,从而没法鉴别对方身份;
而Android做为一个开源系统,拥有很是多的开发平台,App来源甚广,所以手机的安全显得额外重要;
对于普通用户,毫不但愿从商店下载的App
能偷窥隐私数据、后台形成手机耗电等等问题。函数
Android
为每一个安装好的应用程序分配了本身的UID
,故进程的UID
是鉴别进程身份的重要标志
而Binder
通讯能够得到通讯进程的UID
,有了UID
就能够鉴别进程的身份。
同时 Binder
支持实名 Binder
, 保证了安全性。性能
在分析性能时,谈到了App数据缓存区
和内核缓存区
。
App数据缓存区
用于进程间数据隔离,而内核缓存区
的数据是能够共享的。
为了进一步了解进程间通讯
,咱们还须要去了解
- 用户空间/内核空间
- 内核态/用户态
- 内核模块/驱动
用户空间/内核空间
内核空间(Kernel Space
)是系统内核运行的空间
用户空间(User Space
)是用户程序运行的空间。
为了保证安全性,它们之间是隔离的。可是有的时候用户空间是须要去访问内核空间的。
好比:文件读写操做。
而用户空间访问内核空间的惟一方式就是系统调用
。
系统调用:内核态/用户态
Linux
使用两级保护机制:0
级供系统内核使用,3
级供用户程序使用。
经过系统调用
这个统一入口接口,全部的资源访问都是在内核的控制下执行,以避免致使对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
当一个进程执行系统调用而陷入内核代码中执行时,咱们就称进程处于内核态。此时处理器处于特权级最高的(0级)内核代码中执行
当进程在执行用户本身的代码时,则称其处于用户态。此时处理器在特权级最低的(3级)用户代码中运行。
系统调用
主要经过以下两个函数来实现:
copy_from_user()
//将数据从用户空间拷贝到内核空间copy_to_user()
//将数据从内核空间拷贝到用户空间
传统的IPC
就是使用上述两个系统调用
的方法来实现进程间通讯 。
传统 IPC 的原理
- 消息发送方将要发送的数据存放在
用户的内存缓存区
中,经过系统调用
进入内核态。 - 内核程序在内核空间开辟一块
内核缓存区
,操做系统调用copy_from_user() 函数
将数据从用户空间的内存缓存区
拷贝到内核空间的内核缓存区
中。 - 接收方进程在本身的
用户空间开辟一块内存缓存区
,而后内核程序调用copy_to_user() 函数
将数据从内核缓存区拷
贝到接收进程的内存缓存区
。
这样数据发送方进程和数据接收方进程就完成了一次数据传输,也就是进程间通讯。
内核模块 / “驱动”
经过系统调用,用户空间能够访问内核空间,
那么若是一个用户空间想与另一个用户空间进行通讯怎么办呢?
很天然想到的是让操做系统内核添加支持;
传统的Linux
通讯机制,好比Socket, 管道等
都是内核支持的;
可是Binder
并非Linux
内核的一部分,它是怎么作到访问内核空间的呢?
Linux
的动态可加载内核模块(Loadable Kernel Module,LKM)
机制解决了这个问题;
该模块是具备独立功能的程序,它能够被单独编译,但不能独立运行。
它在运行时被连接到内核做为内核的一部分在内核空间运行。
Android
系统经过添加一个内核模块运行在内核空间,用户进程之间经过这个模块做为桥梁,就能够完成通讯。
在Android
系统中,这个运行在内核空间的,负责各个用户进程经过 Binder
通讯的内核模块叫作Binder驱动
;
TIP:
驱动程序
通常指的是设备驱动程序(Device Driver
),是一种可使计算机和设备通讯的特殊程序。
至关于硬件的接口,操做系统只有经过这个接口,才能控制硬件设备的工做;
驱动
就是操做硬件的接口,为了支持Binder
通讯过程,Android
经过软件层面实现的Binder驱动
,它相似于硬件接口用于和内核交互。
所以这个模块被称之为驱动。
前面说到了Binder
的数据拷贝只有一次,而传统的IPC
除了共享文件外都是最少两次数据拷贝
那么Binder
驱动是如何实现的呢?
Binder IPC 机制实现原理
有点深奥,晦涩难懂。之后慢慢啃
Binder IPC
机制中实现数据拷贝仅一次的原理是用到了内存映射
。数据拷贝是在
内存映射:
首先
映射
是指创建一种关系,而这里的内存映射
顾名思义就是将用户空间
的一块内存区域映射到内核空间
。在映射的过程当中数据并无拷贝,只是双方创建了连接。这个连接在物理上是不存在,只是逻辑上存在。也就是说这个联系咱们看不见摸不着,只是咱们代码上给它们进行了关联。
映射关系创建后,用户对这块内存区域的修改能够直接反应到内核空间;反以内核空间对这段区域的修改也能直接反应到用户空间。
而咱们如何在代码上进行关联呢?
答案是经过操做系统调用 mmap() 方法
来实现,可是 mmap()
一般是用在有物理介质
的文件系统上的。
而Binder
并不存在物理介质,所以mmap() 方法
并非为了在物理介质和用户空间之间创建映射,
mmap() 方法
会返回一个指针ptr
,该指针指向逻辑地址中的空间,这时候尚未数据拷贝。
要实现诗句拷贝须要将逻辑地址
转换为物理地址
,这个过程须要经过MMU(MemoryManagementUnit 内存管理单元)
实现。
因为第一次数据通讯双方还没创建映射,MMU
在地址映射表中是没法找到与指针ptr
相对应的物理地址的,也就是MMU
失败,将产生一个缺页中断,
缺页中断的中断响应函数会在swap 分区
中寻找相对应的页面,
若是找不到(也就是该文件历来没有被读入内存的状况),则会经过mmap()
创建的映射关系,从硬盘上将文件读取到物理内存中。
TIP:
swap 分区
一般被称为交换分区,这是一块特殊的硬盘空间,即当实际内存不够用的时候,操做系统会从内存中取出一部分暂时不用的数据,放在交换分区中,从而为当前运行的程序腾出足够的内存空间。
因此数据拷贝是经过缺页中断机制
将用户数据写入内存中。
一次完整的Binder IPC 通讯过程
一般是这样:
- 首先
Binder 驱动
在内核空间建立一个数据接收缓存区; - 接着在内核空间开辟一块内核缓存区,创建发送方进程和接收方进程对内科缓存区的映射关系;
- 发送方进程经过
系统调用 copy_from_user()
将数据copy
到内核中的内核缓存区,因为内核缓存区和接收进程的用户空间存在内存映射,所以也就至关于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通讯。
总结
Binder
是Android系统
提供的进程间通讯的一种方式。
之因此提供Binder
是由于传统的IPC
机制存在一些问题
- 性能:传统的
IPC
机制,如:Socket,管道,消息队列
在通讯时数据会经历两次拷贝。而Binder
仅一次。 - 稳定:
Binder
基于C/S
架构模式,代码结构清晰不易出错。 - 安全:
Android
是开源系统,众多软件鱼龙混杂。传统的IPC
机制,通讯双方不能鉴别身份。而Binder
提供了UID
用于标识进程,进而能鉴别通讯双方。
传统的IPC
通讯机制原理是用到了系统调用
的两个方法
copy_from_user()
: 将数据从用户空间拷贝到内核空间copy_to_user()
: 将数据从内核空间拷贝到用户空间
实现过程是:
- 发送方将数据存入
用户的内存缓存区
- 接收方在在本身进程开辟
用户的内存缓存区
- 内核空间开辟
内核缓存区
- 发送方从用户态进入内核态,并经过系统调用
copy_from_user()
将发送方的内存缓存区
的数据拷贝放入内核缓存区
,而后经过copy_to_user()
将数据拷贝到接收方的内存缓存区
。
这就是传统IPC
机制通讯须要两次数据拷贝的问题。
而Binder
仅须要一次数据拷贝。
它的原理是用到了内存映射和系统调用 mmap() 方法
- 经过内存映射的方式实现
发送方-内核-接收方
之间的对应关系。 - 再经过
系统调用 mmap() 方法
返回Binder驱动
中的逻辑地址,而逻辑地址要和物理地址转换须要经过MMU
MMU
在链接逻辑地址和物理地址的时候会调用缺页中断方法
在swap 分区
中寻找相对应的数据。若是没找到,说明数据不存在,则须要进行数据拷贝数据拷贝使用的仍是系统调用 copy_from_user()
方法。- 因为存在映射关系,因此数据拷贝一次便可实现发送方和接收方的进程通讯。
参考
- Binder学习指南
- 为何 Android 要采用 Binder 做为 IPC 机制?
- Android跨进程通讯:图文详解 Binder机制 原理
- Android Bander设计与实现 - 设计篇
- 写给 Android 应用工程师的 Binder 原理剖析!
- 内存映射原理
- 系统调用mmap详解整理
- Linux swap分区及做用详解
本文同步分享在 博客“_龙衣”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。