上一篇介绍了0.12版本内核地址映射线性地址和物理地址是通过减去0xC0000000互相转化的,但是这里存在一个问题的,那就是内核只能读取1G的内存,即便物理内存大于4G空间,这样不就浪费了吗?那么对于高端内存,32位系统是怎么解决的呢?如果是64为系统,这个问题肯定是不存在的,因为它可以读取到512G的内存,通常是够的,回到之前的问题,那么32位的怎么处理呢?
先回顾之前的地址映射
上面图简单回顾了整个地址映射的过程。
内核地址空间划分为三个部分
ZONE_DMA 内存开始的16MB
ZONE_NORMAL 16MB~896MB
ZONE_HIGHMEM 896MB ~ 结束
上面的DMA是为设备映射地址用,后面有机会在详细介绍,NORMAL就是上一篇blog介绍的方式映射,关键是HIGHMEM这段存储,高端内存HIGHMEM地址空间范围为0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB),那么怎么通过这个128MB高端内存地址空间是如何实现访问可以所有物理内存呢?
当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。
对于这部分高端存储有三个部分,分别对应三种方式,
动态映射
通过 vmalloc(),vmalloc是一个接口函数,内核代码用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存。
void *vmalloc(unsigned longsize);
该函数只需要一个参数,用于指定所需内存的长度,单位为字节。但是要注意,vmalloc是以页大小为单位分配内存的。底层还是通过伙伴算法分配页。
持久映射
类同vmalloc需要建立物理页到虚拟地址的映射关系,持久映射显然要简单的多
struct page_address_map {
struct page *page; //物理页
void *virtual; //虚拟地址,该地址必须在持久映射区(PKMAP_BASE→ FIXMAP_START)
struct list_head list;//系统标准链表
};
图中vitual address space里面的每一个格子的空间大小为4kB,及一个页的大小,该空间及虚拟空间。pkmap_count所指代的数组的每个单位大小是4B,及int类型,该数组主要是为了对vitual address space 中的虚拟地址被映射多少次的计数
临时映射
内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为”固定映射空间”在这个空间中,有一部分用于高端内存的临时映射。
这块空间具有如下特点:
(1)每个 CPU 占用一块空间
(2)在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。
当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。通过 kmap_atomic() 可实现临时映射。