01 binder理论基础(上)

0

01 binder理论基础(上)

1、binder是什么

file

binder起源于1991年be公司研发的操作系统BeOS,binder是其中的一个跨进程通信组件。
后来Be公司 于2001年被Palm收购了,工程师Dianne Hackbod基于BeOS的进程通信架构自己重新实现优化,给这架构取名为Binder并应用到PalmOS。后来还把binder开源了,开源项目为Openbinder。再后来安卓之父Andy Rubin创建Android之后邀请Dianne成为安卓系统总架构师。binder也由于其优秀的性能自然而然地成为了Android的跨进程通信组件。

2、binder诞生的必要性,他的存在是为了解决什么问题?

其实历史原因只是Android选择binder作为跨进程通信架构的一部分,主要原因还是因为Binder的优秀架构和性能。

那么原有的IPC方式都有哪些呢?有哪些优缺点?为啥不能直接用原有的IPC方式,而要设计出binder这一IPC通信机制?下面我们来看看linux原有的跨进程通信机制有哪些?

2.1 进程的概念

在了解linux 跨进程是什么之前需要先理解进程是什么?

2.1.1物理内存和虚拟内存的概念

在了解进程之前,我们需要先摸清楚内存的概念,毕竟进程的运行是加载在内存上的,没有内存就没有所谓的进程。那么进程使用的内存是物理内存还是虚拟内存,访问数据地址是实际的硬件地址吗?如果不是那又是如何与实际物理地址关联的?

file

物理内存与物理内存地址

物理内存就是与CPU相连实际存在的存储设备,是CPU的地址线可以直接进行寻址的内存空间大小。可以理解为我们现在电脑上的内存条。与之对应的物理内存地址(Physical Memory Address)就是实际在内存硬件里边的空间地址。

在linux中程序使用到的都是虚拟内存,那为什么不直接使用物理内存?
在还没有虚拟内存概念的时候,程序寻址直接使用的内存物理地址是自然而然的。

然而这种寻址方式只适用于单进程程序,假如计算机要同时运行多进程,那么每个进程分配的物理内存空间必须是独立的,否则不同的进程同时访问修改同一个地址的数据程序很容易崩溃。

基于进程内存隔离的前提下,由于早期计算机内存较小,假如根据物理内存分配则很快就分配完,此时要运行新的进程只能等待正在运行的进程执行结束,再将新的进程装载进内存,效率低下。

此外由于程序代码使用的是实际的物理地址,假如物理地址变更,则同一份代码必然无法在其他设备上正常运行。

由于以上问题,操作系统必须要有个中转逻辑去处理。

根据计算机的著名理论:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,虚拟内存就是解决这个问题的中间层方案

2.1.2 虚拟内存与虚拟内存地址

file

以32位操作系统为例,操作系统根据cpu的带宽,划分了地址0~2^32 的虚拟空间 也就是 4G的虚拟空间。

对于64位系统划虚拟地址只使用了 48 位,划分了0~2^48 也就是 128T的虚拟内存空间。

此外整块虚拟内存空间一分为二,高地址内存空间为内核态专门用来存放内核进程相关的数据,低地址为用户态用来存放用户进程数据。

file

内存管理系统将虚拟内存和物理内存指定的大小定义一个页(linux默认页大小为4K),通过映射表建立起虚拟地址和物理地址的内存映射关系。

file

这个映射表由内存管理单元MMU来进行维护,MMU的角色相当于图书管理员的角色,一个图书馆有几十万本书,但是我们可以通过图书的索引来快速找到,比如某本书在13楼b区3排 5列,我们就可以通过索引13B35这样子的索引快速地找到我们心仪的书。

通过内存控制单元管理实际物理内存的使用,此外,所有的进程都需要内存,但是不是每个进程都需要一直将对象加载到内存中。因此内存过长时间不使用的进程的部分或者全部内存还会通过优先级算法换出到磁盘中,来增加实际可使用内存,直到需要再次使用的时候再读取到内存中,由此在保证进程数据独立的前提下,大大提高内存使用效率。

2.1.3 概念总结

了解完了linux内存相关概念之后,我们来回答最初的问题:进程是什么?

进程从动态的角度来看就是一个独立运行的程序,就像我们手机上的微信,抖音,微博等应用,独立地运行自己的功能。

从静态的角度来看,进程是虚拟内存中用户态的数据。这些数据分类为不同的类型分布在用户态的不同数据区中。这些数据区是虚拟内存,他们会通过MMU内存管理单元 通过多级页表映射到实际的物理内存中。

用户态的内存还进一步划分为 栈、mmap、堆、BSS、Data、Text 区,用来存放进程中不同类型的数据。

file

每个进程的数据根据不同的类型和作用分别被保存在不同的区中。

file

我们讨论的跨进程通信实际就是进程之间的数据交互。

2.2 linux的跨进程通信方式

Android 底层是linux内核,在linux中的跨进程通信方式主要有:

1、共享内存

2、管道

3、消息队列

4、信号

5、socket

我们来看看不同跨进程通信方式的原理和特点

2.2.1共享内存

file

2、管道

file

管道本质都是内核里的一串缓存,不同进程之间通过管道的句柄进行读数据和写数据。
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道,一般只能用于父子进程之间。

管道分为命名管道和无名管道,它是以一种特殊的文件作为中间介质,我们称为管道文件,它具有固定的读端和写端,写进程通过写段向管道文件里写入数据,读进程通过读段从读进程中读出数据,构成一条数据传递的流水线,它的原理如下图所示:

管道一次通信需要经历2次数据复制(进程A -> 管道文件,管道文件 -> 进程B)。管道的读写分阻塞和非阻塞,管道创建会分配一个缓冲区,而这个缓冲区是有限的,如果传输的数据大小超过缓冲区上限,或者在阻塞模式下没有安排好数据的读写,会出现阻塞的情况。管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式。

3、消息队列

file

消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个独立的链表。

消息队列一次通信同样需要经历2次数据复制(进程A -> 消息队列,消息队列 -> 进程B)

消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。消息队列允许多个进程同时读写消息,发送方与接收方要约定好,消息体的数据类型与大小。消息队列克服了信号承载信息量少、管道只能承载无格式字节流等缺点,消息队列一次通信同样需要经历2次数据复制(进程A -> 消息队列,消息队列 -> 进程B),它的原理如下图所示:

4、信号

file

信号(Signal)是Unix中一个古老的通信机制,主要用来通知进程某个特定事件的发生,或者是让进程执行某个特定的处理函数。

file

5、socket

主要用于用于不同机器之间的进程间网络通信。

Socket原本是为了网络设计的,但也可以通过本地回环地址 (127.0.0.1) 进行进程间通信,后来在Socket的框架上更是发展出一种IPC机制,名叫UNIX Domain Socket。Socket是一种典型的C/S架构,一个Socket会拥有两个缓冲区,一读一写,由于发送/接收消息需要将一个Socket缓冲区中的内容拷贝至另一个Socket缓冲区,所以Socket一次通信也是需要经历2次数据复制,它的原理如下图所示:

file