写点什么

Android 高阶:了解这些知识点,学习 Binder 就不成问题,androidapp 开发教程推荐

用户头像
Android架构
关注
发布于: 刚刚

系统调用 用户空间需要访问内核空间,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。


进程 A 和进程 B 的用户空间可以通过如下系统函数和内核空间进行交互。


  • copy_from_user:将用户空间的数据拷贝到内核空间。

  • copy_to_user:将内核空间的数据拷贝到用户空间。


内存映射 由于应用程序不能直接操作设备硬件地址,所以操作系统提供了一种机制:内存映射,把设备地址映射到进程虚拟内存区。 举个例子,如果用户空间需要读取磁盘的文件,如果不采用内存映射,那么就需要在内核空间建立一个页缓存,页缓存去拷贝磁盘上的文件,然后用户空间拷贝页缓存的文件,这就需要两次拷贝。 采用内存映射,如下图所示。



由于新建了虚拟内存区域,那么磁盘文件和虚拟内存区域就可以直接映射,少了一次拷贝。


内存映射全名为 Memory Map,在 Linux 中通过系统调用函数 mmap 来实现内存映射。将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间,反之亦然。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。

2.1 Linux 的 IPC 通信原理

了解 Liunx 中的几个概念后,就可以学习 Linux 的 IPC 通信原理了,如下图所示。



内核程序在内核空间分配内存并开辟一块内核缓存区,发送进程通过 copy_from_user 函数将数据拷贝到到内核空间的缓冲区中。同样的,接收进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程。这样数据发送进程和数据接收进程完成了一次数据传输,也就是一次进程间通信。


Linux 的 IPC 通信原理有两个问题:


  1. 一次数据传递需要经历:用户空间 --> 内核缓存区 --> 用户空间,需要 2 次数据拷贝,这样效率不高。

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

2.2 Binder 的通信原理

Binder 是基于开源的 OpenBinder 实现的,OpenBinder 最早并不是由 Google 公司开发的,而是 Be Inc 公司开发的,接着


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


由 Palm, Inc.公司负责开发。后来 OpenBinder 的作者 Dianne Hackborn 加入了 Google 公司,并负责 Android 平台的开发工作,顺便把这项技术也带进了 Android。


Binder 是基于内存映射来实现的,在前面我们知道内存映射通常是用在有物理介质的文件系统上的,Binder 没有物理介质,它使用内存映射是为了跨进程传递数据。



Binder 通信的步骤如下所示。


1.Binder 驱动在内核空间创建一个数据接收缓存区。


2.在内核空间开辟一块内核缓存区,建立内核缓存区和数据接收缓存区之间的映射关系,以及数据接收缓存区和接收进程用户空间地址的映射关系。


3.发送方进程通过 copy_from_user()函数将数据拷贝 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。


整个过程只使用了 1 次拷贝,不会因为不知道数据的大小而浪费空间或者时间,效率更高。

3.为什么要使用 Binder

Android 是基于 Linux 内核的 ,Linux 提供了很多 IPC 机制,而 Android 却自己设计了 Binder 来进行通信,主要是因为以下几点。


性能方面 性能方面主要影响的因素是拷贝次数,管道、消息队列、Socket 的拷贝次书都是两次,性能不是很好,共享内存不需要拷贝,性能最好,Binder 的拷贝次书为 1 次,性能仅次于内存拷贝。


稳定性方面 Binder 是基于 C/S 架构的,这个架构通常采用两层结构,在技术上已经很成熟了,稳定性是没有问题的。共享内存没有分层,难以控制,并发同步访问临界资源时,可能还会产生死锁。从稳定性的角度讲,Binder 是优于共享内存的。


安全方面 Android 是一个开源的系统,并且拥有开放性的平台,市场上应用来源很广,因此安全性对于 Android 平台而言极其重要。 传统的 IPC 接收方无法获得对方可靠的进程用户 ID/进程 ID(UID/PID),无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,通过进程的 UID 来鉴别进程身份。另外,Android 系统中的 Server 端会判断 UID/PID 是否满足访问权限,而对外只暴露 Client 端,加强了系统的安全性。


语言方面 Linux 是基于 C 语言,C 语言是面向过程的,Android 应用层和 Java Framework 是基于 Java 语言,Java 语言是面向对象的。Binder 本身符合面向对象的思想,因此作为 Android 的通信机制更合适不过。


从这四方面来看,Linux 提供的大部分 IPC 机制根本无法和 Binder 相比较,而共享内存只在性能方面优于 Binder,其他方面都劣于 Binder,这些就是为什么 Android 要使用 Binder 来进行进程间通信,当然系统中并不是所有的进程通信都是采用了 Binder,而是根据场景选择最合适的,比如 Zygote 进程与 AMS 通信使用的是 Socket,Kill Process 采用的是信号。

4.为什么要学习 Binder?

Binder 机制在 Android 中的地位举足轻重,我们需要掌握的很多原理都和 Binder 有关:


  1. 系统中的各个进程是如何通信的?

  2. Android 系统启动过程

  3. AMS、PMS 的原理

  4. 四大组件的原理,比如 Activity 是如何启动的?

  5. 插件化原理

  6. 系统服务的 Client 端和 Server 端是如何通信的?(比如 MediaPlayer 和 MeidaPlayerService)


上面只是列了一小部分,简单来说说,比如系统在启动时,SystemServer 进程启动后会创建 Binder 线程池,目的是通过 Binder,使得在 SystemServer 进程中的服务可以和其他进程进行通信了。再比如我们常说的 AMS、PMS 都是基于 Binder 来实现的,拿 PMS 来说,PMS 运行在 SystemServer 进程,如果它想要和 DefaultContainerService 通信(是用于检查和复制可移动文件的系统服务),就需要通过 Binder,因为 DefaultContainerService 运行在 com.android.defcontainer 进程。 还有一个比较常见的 C/S 架构间通信的问题,Client 端的 MediaPlayer 和 Server 端的 MeidaPlayerService 不是运行在一个进程中的,同样需要 Binder 来实现通信。


可以说 Binder 机制是掌握系统底层原理的基石。根据 Android 系统的分层,Binder 机制主要分为以下几个部分。



上图并没有给出 Binder 机制的具体的细节,而是先给出了一个概念,根据系统的 Android 系统的分层,我将 Binder 机制分为了 Java Binder、Native Binder、Kernel Binder,实际上 Binder 的内容非常多,完全可以写一本来介绍,但是对于应用开发来说,并不需要掌握那么多的知识点,因此本系列主要会讲解 Java Binder 和 Native Binder。

最后

最后为了帮助大家深刻理解 Handler 相关知识点的原理以及面试相关知识,这里还为大家整理了 Android开发相关源码精编解析


深入解析 Binder 源码解析


  • Binder 设计基础

  • ioctl(): 内核/用户空间调用

  • mmap(): 内核/用户空间内存映射

  • Binder 中的 ONEWAY 与非 ONEWAY 调用

  • Binder 中的生产者与消费者

  • Binder 代理对象的 handle 句柄

  • Binder 内核中的红黑树

  • 实名服务的注册与获取

  • 跨进程数据传输

  • 匿名服务的跨进程传输与回调

  • 死亡回调的注册与获取



用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android高阶:了解这些知识点,学习Binder就不成问题,androidapp开发教程推荐