1
虚拟地址与物理地址
在和群里的小伙伴交流的过程中发现很多同学对于计算机组成和操作系统这块基础了解的比较少,所以我觉得有必要简单科普一下虚拟地址和物理地址。
操作系统内核一般以页为单位管理物理内存,一页的大小通常是 4k 即 4096个Byte。为方便管理,通常会给这些页编一个号, 0,1,2,3,4。0 1 2 3 4 只是举例,实际不一定是。
对于进程而言,他访问的内存地址不是物理地址而是一个虚拟的地址空间。
一样通过页的方式管理,一样的编号。
当我们申请内存的时候,(c语言一般是 malloc 函数, c++ java 一般是通过关键字 new),比如 new Integer() ,操作系统先在虚拟地址空间找到一个空闲页比如 2 号页,然后在物理地址找到空闲页 比如 0 号页。
找到以后,会将这个对应关系放到一个表中记录,这个表通常就是常说的 MMU 单元(内存管理单元)。
为什么要搞虚拟地址和 mmu,因为操作系统是多进程的,如果没有虚拟地址,多个进程的物理内存访问会变得难以管理。有了虚拟地址以后,每个进程看到的都是完整的干净的地址空间。内存的管理扔给了操作系统,简化了应用程序的开发。
以上就是一个便于理解的简化的内存管理的模型,实际情况要复杂一些,涉及了分段分页,一二级页表,快表等内容,更为详细的内容可以参考计算机组成,操作系统相关的书籍。
2
Binder 是什么?
在 linux 中,每个进程都有自己的虚拟内存地址空间。虚拟内存地址空间又分为了用户地址空间和内核地址空间。
不同进程之间用户地址空间的变量和函数是不能相互访问的。
很多时候,提供系统功能的数据和函数都会放在固定的几个进程中(比如显示画面,播放声音等),我们编写的用户程序要实现相应的功能就需要通过跨进程通信技术来访问这些数据和函数。
3
实现跨进程通信的思路
虽然用户地址空间是不能互相访问的,但是不同进程的内核地址空间是相同和共享的,我们可以借助内核地址空间作为中转站来实现进程间数据的传输。
具体的我们在 B 进程使用 copy_from_user 将用户态数据 int a 拷贝到内核态,这样就可以在 A 进程的内核态中访问到 int a。
更进一步,可以在 A 进程中调用 copytouser 可以将 int a 从内核地址空间拷贝到用户地址空间。至此,我们的进程 A 用户态程序就可以访问到进程 B 中的用户地址空间数据 int a 了。
4
优化我们的方案
为了访问 int a ,需要拷贝两次数据。能不能优化一下?我们可以通过 mmap 将进程 A 的用户地址空间与内核地址空间进行映射,让他们指向相同的物理地址空间:
完成映射后,B 进程只需调用一次 copyfromuser,A 进程的用户空间中就可以访问到 int a了。这里就优化到了一次拷贝。
以上就是 binder 最基本的原理了。
5
总结
跨进程通信的需求普遍存在。
binder 跨进程通信核心原理是 copytouser 和 mmap。