因为Android是基于Linux系统内核的,所以在学习Binder一次拷贝原理之前必须先学习一下Linux操作系统的基本知识。
用户空间和内核空间
- linux系统运行模式分为两层:高优先级模式(特权模式),低优先级模式(普通模式)。在 CPU 的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。所以,CPU 将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。
- linux系统在高优先级模式中运行系统内核代码以及与硬件密切相关的代码。 低优先级运行营运程序与硬件无关部分。应用程序不能直接操控硬件或者调用内核函数,需借助一系列接口函数申请让系统调用相关代码在内核空间运行,获取代码运行权限。
- Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。每一个系统进程都拥有自己私有的地址空间和数据,用户空间造成的进程错误会被局部化,而不会影响到内核或者其他进程。每个应用程序或者进程都会有自己特定的地址、私有数据空间,程序之间一般不会相互影响。
内核态与用户态:
当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。
在内核态下,进程运行在内核地址空间中,此时 CPU 可以执行任何指令。运行的代码也不受任何的限制,可以自由地访问任何有效地址,也可以直接进行端口的访问。在用户态下,进程运行在用户地址空间中,被执行的代码要受到 CPU 的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中 I/O 许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。
内核地址空间划分
一、32位Linux系统虚拟地址空间
32位Linux内核虚拟地址空间划分0-3G为用户空间,3~4G为内核空间(注意,内核可以使用的线性地址只有1G)。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的(64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存)。如下图所示:用户空间内存分为3G,内核空间内存分为1G。
二、逻辑内存地址与物理内存映射(内存条物理地址)
当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射;
假如内核逻辑地址空间访问为0xc0000000 ~ 0xffffffff,那么对应的物理内存范围就为0×0 ~ 0×40000000,即只能访问1G物理内存。若机器中安装8G物理内存,那么内核就只能访问前1G物理内存,后面7G物理内存将会无法访问,因为内核的地址空间已经全部映射到物理内存地址范围0×0 ~ 0×40000000。
三、Linux内核高端内存
上面提到了内核就只能访问前1G物理内存,后面7G物理内存将会无法访问,所以不能将内核地址空间0xc0000000 ~ 0xfffffff全部用来简单的地址映射。x86架构中将内核地址空间划分三部分:ZONE_DMA
、ZONE_NORMAL
和ZONE_HIGHMEM
。ZONE_HIGHMEM
即为高端内存,这就是内存高端内存概念的由来。
在x86结构中,三种类型的区域(从3G~4G计算)如下:
ZONE_DMA
: 内存开始的16MBZONE_NORMAL
:16MB~896MB,880MZONE_HIGHMEM
:896MB ~ 结束(1G),128M
高端内存HIGH_MEM地址空间范围为 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如内核是如何借助128MB高端内存地址空间是如何实现访问可以所有物理内存?
当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。
所以高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,从而访问所有物理内存。
四、Linux内核高端内存的划分
内核将高端内存划分为3部分:VMALLOC_START ~ VMALLOC_END、KMAP_BASE ~ FIXADDR_START 和 FIXADDR_START ~ 4G
。