目录
  1. 1. 简介
    1. 1.1. 内存划分
    2. 1.2. 传统 IPC 传输数据
    3. 1.3. Binder 传输数据
  2. 2. Binder
    1. 2.1. 三种 IPC 方式的数据的拷贝次数
    2. 2.2. 面向对象的 Binder IPC
  3. 3. Binder 模型
    1. 3.1. Linux进程间通信机制
  4. 4. Binder的驱动注册
  5. 5. Binder的JNI注册
  6. 6. 剑指大厂
    1. 6.1. Binder有什么优势? – 《ByteDance》
    2. 6.2. Binder是如何做到一次拷贝? – 《Tencent》
    3. 6.3. mmap的原理讲解 – 《Tencent》
【吃透源码系列】之Binder

我们知道Android系统启动之后,要想启动一个应用程序则需要保证该应用程序的进程先被启动,如果该进程不存在就会请求 Zygote 进程启动需要的应用程序进程。这样在 Zygote 的 Java 框架层(Framework源码层)中会创建一个 Server 端的 Socket,这个 Socket 用来等待 AMS 请求 Zygote 来创建新的应用程序进程(Zygote 进程通过 fork 自身创建应用程序进程),这样应用程序进程就会获得 Zygote 进程在启动时创建的虚拟机实例。与此同时,也创建出了 Binder 线程池消息循环,这样运行在应用进程中的应用程序就可以很方便的使用 Binder 进行进程间通信以及处理消息了。

简介

既然决定吃透源码,那就必须从源头捋起,所以这篇文章会综合《深入理解Android内核设计思想》、《Android开发艺术探索》以及《Android进阶解密》三书,加上在享学课堂学习有关 Binder 进程课程,会在本文中对Binder设计细节做一个全面的阐述。首先通过介绍 Binder 通信模型和 Binder 通信协议了解 Binder 的设计需求;然后分别阐述 Binder 在系统不同架构分层间的呈现方式及其作用,最后分析 Binder 在数据接收端的设计考虑,包括:线程池管理、内存映射、等待队列等。为读者尽最大可能展现 Android Binder 原本的样貌。这样学就够了!

内存划分

内存划分.png

传统 IPC 传输数据

传统 IPC 传输数据.png

Binder 传输数据

Binder 传输数据.png

Binder

Binder 是 Android 系统进程间通信(IPC)方式之一,这里为了突出 Android Binder IPC 通信机制的优越性,先分析对比一波 共享内存Socket 和本文介绍的 Binder 的数据拷贝。

三种 IPC 方式的数据的拷贝次数

图片1.png

共享内存

共享内存可以类比 Java 的内存, 堆就是线程共享区域,而此处的堆就是 Linux 里的内存共享区域,虽然无需拷贝,但控制复杂,难以复用,基于此方案的通信,试想一下,如果有一个恶意的程序或者某个app运行崩溃,就会导致关联的应用甚至系统跟着奔溃,这是致命的。

Socket/管道/消息队列

基于 C/S 架构的通信,目前 linux 支持 的 IPC 包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及 socket 中只有 socket 支持 Client-Server 的通信方式。

socket 作为一款通用接口,其传输效率低效,开销巨大,主要用在跨网络的进程间通信和本机上进程间的低速通信。如果采用socket在底层架设一套协议,这无疑加重系统设计的复杂性,而且也难以保证通信效率。

消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,这里至少有两次拷贝过程。

Binder(基于传输性、安全性考虑均是最佳)

基于 C/S 通信模式,传输过程只需一次拷贝,为发送方添加 UID/PID 身份,既支持实名 Binder,也支持匿名 Binder,安全性高。

面向对象的 Binder IPC

Binder 使用 Client-Server 通信方式:一个进程作为 Server 提供诸如视频/音频解码,视频捕获,地址本查询,网络连接等服务;多个进程作为 Client 向 Server 发起服务请求,获得所需要的服务。

要想实现 Client-Server 通信据必须实现以下两点:

  • Server 必须有确定的访问接入点或者说地址来接受Client的请求, 并且 Client 可以通过某种途径获知 Server 的地址;

  • 制定 Command-Reply 协议来传输数据

这就好比,网络通信中,Server 的访问接入点就是 Server 主机的IP地址+端口号,传输协议为 TCP 协议;

对 Binder 而言,Binder 可以看成 Server 提供的实现某个特定服务的访问接入点,Client 通过这个 “地址” 向 Server 发送请求来使用该服务;

对 Client 而言,Binder 可以看成是通向 Server 的管道入口,要想和某个 Server 通信首先必须建立这个管道并获得管道入口。

与其他 IPC 方式不同点在于,Binder 使用了面向对象思想来描述作为访问接入点的 Binder 和在 Client 中的入口:

Binder 是一个实体位于 Server 中的对象,该对象提供了一套方法用以实现对服务的请求,就象类的成员函数。

遍布于 Client 中的入口可以看成指向这个 Binder 对象的“指针”,一旦获得了这个“指针”就可以调用该对象的方法访问 Server。

Binder 模型

Binder 框架定义了四个角色:ServerClientServiceManagerBinder驱动,这其中前三个位于 用户空间,驱动位于 内核空间。这四个角色的关系和互联网类似:Server 是服务器,Client 是客户终 端,SMgr 是域名服务器(DNS),驱动是路由器。

Linux进程间通信机制

管道、信号量、Socket、共享内存

共享内存:数据发送方、内核、数据接收方 三者共享一块物理内存空间

Binder:数据发送方通过 copy_fromm_user() 拷贝一次,将数据拷贝到内核,而内核和数据接收方有一块内存共享区域,所以这两者无需拷贝,即可使用。

Binder的驱动注册

Linux 一切皆文件

binder_init

  • 分配内存

  • 初始化设备

  • 放入设备链表 binder_devices

binder_open

  • 初始化 proc 创建 binder_proc 进程对象

  • 创建同步锁 binder_lock

  • 串联进程链表 binder_procs -> binder_proc -> binder_proc-> // hlist_head -> hlist_node -> hlist_node ->

  • 内核函数filp -> proc 赋值 private_data(后续binder访问全是它) // flip()函数反置bitset中所有的位,即将1设为0,0设为1。

  • 解除同步锁 binder_unlock

binder_mmap

  • struct vm_struct*area —— 内核的虚拟内存

  • struct vm_area_struct*vma —— 进程的虚拟内存(4M 驱动设定;1M - 8k 应用层设定 ===》 binder传输超过1M就会崩溃)

  • area = get_vm_area(vma->vma_end - vma->vma_start, VMREMAP) 分配内存重新建立映射关联

  • proc buffer指针 指向 area 的内核虚拟指针

  • 计算用户空间偏移值 user_buffer_offset:用户空间虚拟内存地址(proc->buffer) = 虚拟内存地址(vma->vm_start) + 偏移值(proc->user_buffer_offset)

Binder的JNI注册

剑指大厂

Binder有什么优势? – 《ByteDance》

优点:
内存开辟
风险隔离

Binder是如何做到一次拷贝? – 《Tencent》

内存划分:内存被操作系统分成两块:用户空间和内核空间,用户空间时用户程序代码运行的地方,内核空间是系统内核运行的地方,可以共享。为了安全,这两者是隔离的,这样即使用户的应用程序崩溃也不会影响到内核。

32位系统 Vs 64位系统

32位系统,即2^32,即总共可访问地址为4G。内核1G,用户空间3G。

64位系统,低位:0-47位才是有效的可变地址(寻址空间256T),高位:48-63位全补0或1。
一般高位全补0对应的地址空间是用户空间。高位全补1对应的是内核空间。

mmap的原理讲解 – 《Tencent》

MMAP:Linux通过将一个虚拟 内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)

对文件进行mmap,会在进程的虚拟内存分配地址空间,建立映射关系。实现这样的映射关系后,就可以对这一段内存采用指针的方式进行读写操作,而系统会自动回写到对应的文件磁盘上。

打赏
  • 微信
  • 支付宝

评论