linux bootmem (3)

本文详细介绍了Linux内核中恒等映射IDMAP空间的实现,包括其采用的Section mapping,利用3级页表映射2MB物理地址,以及1.6KB的恒等映射代码空间布局。还探讨了页表结构、地址转换和创建恒等映射页表的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

恒等映射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

https://images2018.cnblogs.com/blog/480488/201806/480488-20180608212539819-1133633285.png

上图中浅黄色部分就是恒等映射的代码地址空间。

 

这里是每级页表都使用一个页面(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   这个我们之前的判断没有区别

 

小知识

展讯的目前项目虚拟地址的有效bit39,就是没有第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为该页表项在扩展页表中的indextmp2为页表项描述符
.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  建立各个中间leveltable描述符

 

.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 计算starttable index

orr     \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT   table entry 增加属性

lsr     \end, \end, #SWAPPER_BLOCK_SHIFT

and   \end, \end, #PTRS_PER_PTE - 1     计算endtable 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

上面完成了恒等映射的页表创建

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值