恒等映射IDMAP空间
由于后面kernel会自己重新再建立页表,所以这里采用的映射比较粗糙,在level2里使用的是Block descriptor(称之为section mapping),每个block descriptor可以映射2MB物理地址,所以最多只要3级页表就ok了,即Level0~level2。这3级页表用3个4KB页面存放。恒等映射和kernel空间映射都是这种方式。
恒等映射的这段空间并不大(如下只要1.6KB),一个映射页面(2MB)足够了。
下面是这段空间的位置:
_idmap_text定义的位置
VMLINUX_SYMBOL(__idmap_text_start) = .; \
*(.idmap.text) \
VMLINUX_SYMBOL(__idmap_text_end) = .;
下面的函数和其它符号在这个区间内,他们在系统启动后会重新映射到”ffffff8”(有效位为39)开头的虚拟地址上,空间大小为0x630 = 1584 Bytes 为什么是这些函数?
ffffff800893e000 T __idmap_text_start
ffffff800893e000 T kimage_vaddr
ffffff800893e008 T el2_setup
ffffff800893e05c t set_hcr
ffffff800893e108 t install_el2_stub
ffffff800893e13c t set_cpu_boot_mode_flag
ffffff800893e160 T secondary_holding_pen
ffffff800893e184 t pen
ffffff800893e198 T secondary_entry
ffffff800893e1a4 t secondary_startup
ffffff800893e1b4 t __secondary_switched
ffffff800893e1e8 T __enable_mmu
ffffff800893e23c t __no_granule_support
ffffff800893e260 t __relocate_kernel
ffffff800893e2a8 t __primary_switch
ffffff800893e318 T cpu_resume
ffffff800893e338 T cpu_do_resume
ffffff800893e3ac T idmap_cpu_replace_ttbr1
ffffff800893e3dc t __idmap_kpti_flag
ffffff800893e3e0 T idmap_kpti_install_ng_mappings
ffffff800893e418 t do_pgd
ffffff800893e430 t next_pgd
ffffff800893e440 t skip_pgd
ffffff800893e474 t walk_puds
ffffff800893e47c t next_pud
ffffff800893e480 t walk_pmds
ffffff800893e488 t do_pmd
ffffff800893e4a0 t next_pmd
ffffff800893e4b0 t skip_pmd
ffffff800893e4c0 t walk_ptes
ffffff800893e4c8 t do_pte
ffffff800893e4ec t skip_pte
ffffff800893e4fc t __idmap_kpti_secondary
ffffff800893e540 T __cpu_setup
ffffff800893e618 t crval
ffffff800893e630 T __idmap_text_end
上图中浅黄色部分就是恒等映射的代码地址空间。
这里是每级页表都使用一个页面(4KB)来存储。最多能映射2MBx(4KB/8) = 1GB空间。
#define IDMAP_DIR_SIZE (IDMAP_PGTABLE_LEVELS * PAGE_SIZE) PAGE_SIZE实际上是config定死的。
#define IDMAP_PGTABLE_LEVELS (ARM64_HW_PGTABLE_LEVELS(PHYS_MASK_SHIFT) - 1)
#define PHYS_MASK_SHIFT (48)
#define ARM64_HW_PGTABLE_LEVELS(va_bits) (((va_bits) - 4) / (PAGE_SHIFT - 3))
代入数据为 (((48) - 4) / (12 - 3)) = 44/9 =4
那么IDMAP_PGTABLE_LEVELS就是4-1=3
那么IDMAP_DIR_SIZE 就是 3 ×4KB = 12KB 这个我们之前的判断没有区别
小知识
展讯的目前项目虚拟地址的有效bit为39,就是没有第0级页表
下面是虚拟地址为39bit的页表转换过程,可见只用了3级页表,而且页表地址需要是4KB对齐的。
最后第3级描述符的[47:12]就是页面对应的物理地址,再加上虚拟地址的低12bit 构成最终的物理地址,注意每级描述符的Properties部分,它包含了对应地址范围的内存属性。
描述符的bit0表示当前描述符是否有效,bit1表示描述符的类别:0:block 1:Table
描述符分为Block descriptor和Table descriptor两类,它们之间的区别是
Block descriptor:
这个描述符包含一段内存的物理基地址,以及相关的内存属性。仅适用于level0~level2。如果该level返回block型描述符,则查表结束,返回物理地址。
Table descriptor:
这个描述符给出下一级描述符的地址,以及该阶段下转换表的个别属性,使用于level0~level3。
继续介绍恒等映射
将存放页表的内存清0
adrp x0, idmap_pg_dir
ldr x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE)
1: stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
subs x1, x1, #64
b.ne 1b
mov x7, SWAPPER_MM_MMUFLAGS
创建恒等映射
adrp x0, idmap_pg_dir
adrp x3, __idmap_text_start // __pa(__idmap_text_start)
#ifndef CONFIG_ARM64_VA_BITS_48 我们的项目中没有定义,所以走下面的代码
如果物理内存的地址位于非常高的位置,那么在进行identity mapping可能出现问题了,因为有可能虚拟地址的VA_BITS不够大(如展讯的39),物理地址超出了虚拟地址的范围(因为要保持物理地址和虚拟地址相同)。这时候,就需要为虚拟地址扩展range了。当然,如果虚拟地址配置了48bits的VA_BITS就不存在这样的问题了,因为ARMv8支持的物理地址最大BIT数也是48个,不需要扩展虚拟地址。
如果虚拟地址最大范围小于物理地址,需要扩展虚拟地址范围,将虚拟地址的有效为从39扩展到最大值48。
#define EXTRA_SHIFT (PGDIR_SHIFT + PAGE_SHIFT - 3) 30+12-3 = 39
#define EXTRA_PTRS (1 << (48 - EXTRA_SHIFT)) 1<<9
PGDIR_SHIFT计算过程,其中CONFIG_PGTABLE_LEVELS为3
#define PGDIR_SHIFT ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS)
#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3)
n为4-3=1,所以返回 ((12 - 3) * (4 - 1) + 3)=(9*3+3)=30
adrp x5, __idmap_text_end
clz x5, x5
cmp x5, TCR_T0SZ(VA_BITS) //虚拟地址最大值前导0的个数 和最高物理地址比较,看是否够用
b.ge 1f // .. then skip additional level 如果物理地址的前导0多,则不用扩展,跳到1
adr_l x6, idmap_t0sz
str x5, [x6] 把当前VA前导0的个数存入idmap_t0sz
dmb sy
dc ivac, x6 // Invalidate potentially stale cache line
create_table_entry x0, x3, EXTRA_SHIFT, EXTRA_PTRS, x5, x6 传入6个参数 创建扩展页表项
1:
#endif
如果需要扩展页表,那只要在扩展的页表上增加一个页表项就够了,这个页表项管理512GB的区域。
6个参数分别为:
扩展页表地址、虚拟/物理首地址、扩展前的VA bit数、扩展后VA增加的bit数、两个寄存器做临时参数
tmp1为该页表项在扩展页表中的index,tmp2为页表项描述符
.macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
lsr \tmp1, \virt, #\shift tmp1 = virt<<39,相当于只保留了低39位
and \tmp1, \tmp1, #\ptrs – 1 tmp1 &= (ptrs – 1) 即该页表项在扩展页表上的index
add \tmp2, \tbl, #PAGE_SIZE tmp2 = tbl + PAGE_SIZE 下一级页表的地址
orr \tmp2, \tmp2, #PMD_TYPE_TABLE tmp2 |= PMD_TYPE_TABLE 设置下一级页表的type
str \tmp2, [\tbl, \tmp1, lsl #3] 将tmp2写入tbl+tmp1<<3指向的地址中,每个entry 8字节
add \tbl, \tbl, #PAGE_SIZE tbl = tbl + PAGE_SIZE 下一级页表的地址
.endm
create_pgd_entry x0, x3, x5, x6 建立各个中间level的table描述符
.macro create_pgd_entry, tbl, virt, tmp1, tmp2
create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2 ------ pgd
create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2 ---- pud
.endm
上面的函数create_table_entry前面刚介绍过
PGDIR_SHIFT 为30;PTRS_PER_PGD即(1 << (VA_BITS - PGDIR_SHIFT)) = (1<<9) 每个页表项能管1GB空间
SWAPPER_TABLE_SHIFT为30;PTRS_PER_PTE即(1 << (PAGE_SHIFT - 3))= (1<<9) 和上一级PG重复
所以中间级别页表就只有一级。
mov x5, x3 // __pa(__idmap_text_start)
adr_l x6, __idmap_text_end // __pa(__idmap_text_end)
create_block_map x0, x7, x3, x5, x6 创建PTE
创建恒等映射的PTE(其实是level2 页表,因为它是)
SWAPPER_BLOCK_SHIFT 即PAGE_SHIFT =12
PTRS_PER_PTE 即(1 << (PAGE_SHIFT - 3)) = (1<<9)
SWAPPER_BLOCK_SIZE 就是2MB,说明Level2 页表是block型描述符,不需要PTE了
create_block_map函数的输入参数为:
tbl:页表基地址
flags:页表项的flags
phys:物理地址
start:虚拟地址起始地址
end:虚拟地址结束地址
.macro create_block_map, tbl, flags, phys, start, end
lsr \phys, \phys, #SWAPPER_BLOCK_SHIFT
lsr \start, \start, #SWAPPER_BLOCK_SHIFT
and \start, \start, #PTRS_PER_PTE - 1 计算start的table index
orr \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT 给table entry 增加属性
lsr \end, \end, #SWAPPER_BLOCK_SHIFT
and \end, \end, #PTRS_PER_PTE - 1 计算end的table index
9999: str \phys, [\tbl, \start, lsl #3] // store the entry 进入循环
add \start, \start, #1 // next entry 更新新的描述符索引
add \phys, \phys, #SWAPPER_BLOCK_SIZE // next block 更新新的描述符内容
cmp \start, \end
b.ls 9999b
.endm
上面完成了恒等映射的页表创建