Binder是什么?

  • 进程间通信机制,多进程通信
  • binder也是一个驱动
  • Binder.java -> 实现了Ibinder接口——跨进程的能力

Handler : 是App内的通信,epoll机制事件驱动静态界面动起来

多进程的优点: 开辟更多的内存 getprop dalvik.vm.heapsize:获取虚拟机的内存量 每个APP分配的内存是有限的,通过多进程的话,每个进程都会分配一定的内存。 风险隔离:每一个进程,都是单独的一个app.

Binder 有什么优势?为什么Android要采用Binder进行进程间的通信呢?

Linux进程间的通信:管道、信号量、socket、共享内存、File等等。 Linux有这么多进程间的通信,为什么Android要专门增加Binder呢?Binder性能小于共享内存,优于其他IPC(进程间通信)。

Binder 共享内存 Socket 管道
性能 需要拷贝一次 无需拷贝 需要拷贝两次 需要拷贝两次
特点 基于C/S架构易用性高 控制复杂易用性差 基于C/S架构作为一款通用接口,其传输效率低,开销大 管道作为数据的中转站
安全性 为每个APP分配UID同时支持实名和匿名 依赖上层协议访问接入点是开放的不安全 依赖上层协议访问接入是开放的不安全的 安全的

上述Linux系统有这么多的进程通信方式,那么进程间通信的本质是什么呢?

  1. 例如进程A 进程B
  2. 进程A调用new B().test()方法不行的

进程之间的是隔离的.

Binder 为什么只需要拷贝一次?拷贝是用户空间->内核空间

Linux 内存基础

内存被操作系统划分为两块:用户空间内核空间用户空间是App程序代码运行的地方,内核空间是内核系统代码运行的地方。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。

什么是物理地址和虚拟地址?

MMU:Memory Management Unit 内存管理单元,负责虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权、多任务多进程操作系统。

程序局部性原则:假设App100M,活跃代码1M 放到物理内存,剩下的放到磁盘,那么内存条就可以加载很多程序。磁盘和内存的交互过程会非常快,因为磁盘是有高速缓存的
image.png

物理地址:是物理内存的实际地址(内存条)

虚拟地址:是MMU提供的虚拟地址。

物理内存、虚拟内存

MMU中有一个页表,类似key:虚拟地址,value:物理地址,第一次读取不存在就会从磁盘读取出来,然后在内存分配物理内存地址,把物理地址记录下来,产生虚拟地址,CPU拿到的就是虚拟地址。
例如:a = 10; 通过页表虚拟地址找到物理地址,定位到物理内存对应的地址进行赋值为10.
image.png

binder/Activity 传递数据的大小:1M-8K。

为什么是1M - 8K 呢?请求头占2页,一页是4K,1M - 8K 是4K的整数倍

页的概念

交换区存放了大部分的可执行代码与数据,而物理内存中,执行的是少部分的可执行代码与数据。那么此时需要从交换区获取程序的代码,将它拿到物理内存中执行,那么一次那多少代码?

为了CPU高效执行以及方便的内存管理,每次拿一页的代码,这个页是一段连续的存储空间(常见的是4K),也叫做块。页的大小为P,在虚拟内存中叫做虚拟页(VP ) 1M-8K就是4K的整数倍。
未分配:VM系统还未分配的页,未分配的页没有任何数据与代码与他们相关联,不占用任何磁盘
缓存的:当前已缓存存在物理内存中的已分配页
未缓存的:未缓存在物理内存中的已分配页
image.png

页表的结构如下:

页表实际上就是一个数组,这个数组存放的是一个称为页表条目(PTE)的结构,虚拟地址空间的每一个页在页表中,都有一个对应的页表条目,虚拟页地址翻译的时候就是查询在各个虚拟页在页表中的PTE,从而进行地址翻译的。 每一个PTE都有一个有效位和一个n位字段的地址:

  • 有效位:表示对应的虚拟也是否缓存在了物理内存中,0表示未缓存,1表示已缓存。
  • n位地址字段:如果未缓存,有效位字段为0,n位地址字段不为空的话,这个n位地址字段就表示该虚拟页在磁盘上的起始位置。如果已缓存,有效位字段为1,n为地址字段肯定不为空,它表示虚拟页在物理内存中的起始地址。

image.png

页命中:

如下图假设在CPU想读取VP2页面中的某一个字节的内容,CPU得到一个地址vaddr将虚拟地址addr作为索引定位到页表的PTE条目中的PTE2,从内存中读取PTE2的有效位为1,说明该虚拟页面已经被缓存了,所以CPU使用该PTE2条目中的物理内存地址。

image.png
虚拟地址相当于地球仪,物理地址相当于地球

Binder 如何做到一次拷贝

Binder 是如何做到一次拷贝的呢?copy_from_user从用户空间拷贝到内核空间 copy_to_user从内核空间拷贝到用户空间
image.png

上述图中经过copy_from_user一次拷贝,在经过copy_to_user两次拷贝,那么Binder如何做到一次拷贝的呢?

现实中的例子:送快递,快递员拿到包裹,一次拷贝,然后快递员将包裹放到速递柜(物理内存)中,收件人直接从速递柜拿包裹就可以了。速递柜与快递员和收件人之间存在映射关系所以就不需要在拷贝一次,Binder少拷贝一次就是发生在内核空间,将从用户空间拿到的数据进行映射

image.png

1M - 8K 是在哪里限制的,请求头2页,一页4K,开辟1M-8K的内存,在App启动通过Zgoty 调用了ProcessState ,Mmap绑定,提供一块虚拟地址空间来接收事务。

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享

image.png
image.png

mmap执行流程:

  1. 将物理内存中开辟1M-8K字节内存
  2. 物理内存与磁盘进行对应映射
  3. MMU将mmap开辟的物理内存地址,转换成虚拟地址

所以进程通信的数据大小不能超过1M-8K,

Framework | 探索Binder的设计原理 - 图10

Binder 源码分析

在上述的分析中,我们知道了进程间的通信其实就是通过mmap开辟一段物理内存,进行映射,来实现进程之间的通信,那么可以手写一个进程通信的Demo来更加深入的理解。

如下代码:通过底层C++实现:

  1. extern "C"
  2. JNIEXPORT void JNICALL
  3. Java_com_example_mmapmodule_NativeLib_writre(JNIEnv *env, jobject thiz) {
  4. std::string file = "/sdcard/binder";
  5. int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
  6. //磁盘4KB的大小
  7. ftruncate(m_fd, 4096);
  8. //物理地址1 虚拟地址2
  9. int8_t *m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd,
  10. 0));
  11. //m_ptr 虚拟地址 通过MMU找到物理内存赋值
  12. std::string data("测试数据");
  13. //通过m_ptr 赋值
  14. memcpy(m_ptr, data.data(), data.size());
  15. }
  16. extern "C"
  17. JNIEXPORT jstring JNICALL
  18. Java_com_example_mmapmodule_NativeLib_read(JNIEnv *env, jobject thiz) {
  19. std::string file = "/sdcard/binder";
  20. int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
  21. //磁盘4KB的大小
  22. ftruncate(m_fd, 4096);
  23. //物理地址1 虚拟地址2
  24. int8_t *m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd,0));
  25. char *buf = static_cast<char *>(malloc(100));
  26. //通过m_ptr 将数据拷贝到buf中
  27. memcpy(buf, m_ptr, 100);
  28. std::string result(buf);
  29. //取消映射
  30. munmap(m_ptr, 4096);
  31. close(m_fd);
  32. return env->NewStringUTF(result.c_str());
  33. }

用户空间 ——copy_form_user ——- 内核空间 ——- copy_to_user ——- 用户空间

少拷贝的一次是发生在调用端还是服务端(发生在服务端)

Binder的应用层
JNI层
Native层
驱动层

进程源码分析

image.png