要分析的代码
/*
* Macro to populate page table entries, these entries can be pointers to the next level
* or last level entries pointing to physical memory.
*
* tbl: page table address
* rtbl: pointer to page table or physical memory
* index: start index to write
* eindex: end index to write - [index, eindex] written to
* flags: flags for pagetable entry to or in
* inc: increment to rtbl between each entry
* tmp1: temporary variable
*
* Preserves: tbl, eindex, flags, inc
* Corrupts: index, tmp1
* Returns: rtbl
*/
.macro populate_entries, tbl, rtbl, index, eindex, flags, inc, tmp1
.Lpe\@: phys_to_pte \tmp1, \rtbl
orr \tmp1, \tmp1, \flags // tmp1 = table entry
str \tmp1, [\tbl, \index, lsl #3]
add \rtbl, \rtbl, \inc // rtbl = pa next level
add \index, \index, #1
cmp \index, \eindex
b.ls .Lpe\@
.endm
注释解义
这个宏定义我们按照老规矩,先读注释
第1行到第16行时注释,大概意思是填充页表项,当前页表的每个页表项的内容是下一级页表的物理地址,所以牵扯到本级页表和下级页表。这里牵扯到2个问题。
第一个问题:向哪里填充?这个由上一个宏定义 compute_indices 计算出来 istart 和 iend
也就是说,本级页表的基地址为 tbl,在 tbl 的 [istart, iend] 这个区间填充页表项
第二个问题:填充什么内容?填充下一级页表项的地址 + 页表属性
基于如上的原则,我们看看各个参数的含义
第一个参数 tbl,入参,本级页表的起始物理地址
第二个参数 rtbl,入参,下一级页表的起始物理地址
第三个参数 index,入参,起始索引,由上一个宏定义 compute_indices 给出
第四个参数 eindex,入参,结束索引,上一个宏定义 compute_indices 给出
第五个参数 flags,入参,该级页表项的属性
对于全局页表目录等非最后一级页表来说,其页表项填充的值为 PMD_TYPE_TABLE
$ grep -rn PMD_TYPE_TABLE ./arch/arm64/
./arch/arm64/include/asm/pgtable-hwdef.h:109:#define PMD_TYPE_TABLE (_AT(pmdval_t, 3) << 0)
对于 _AT 来说,它是一个类型转换宏,用于显式地将一个值转换为指定的类型,以避免隐式类型转换可能带来的问题。在这个上下文中,它将数字 3 转换为 pmdval_t 类型。
这个值(即3)在页表操作中用于指示PMD条目指向下一级页表。
对于最后一级的页表来说,其值是在 __create_page_tables 前面的语句赋值
第六个参数 inc,入参,步长的意思,也就是相邻的页表项的值相差多少
对于全局页表目录等非最后一级页表来说,其页表项填充的值为 PAGE_SIZE,也就是说两两相差一个页表
对于最后一级的页表来说,其值是 SWAPPER_BLOCK_SIZE,其定义如下
/* Initial memory map size */
#if ARM64_SWAPPER_USES_SECTION_MAPS
#define SWAPPER_BLOCK_SHIFT SECTION_SHIFT
#define SWAPPER_BLOCK_SIZE SECTION_SIZE
#define SWAPPER_TABLE_SHIFT PUD_SHIFT
#else
#define SWAPPER_BLOCK_SHIFT PAGE_SHIFT
#define SWAPPER_BLOCK_SIZE PAGE_SIZE
#define SWAPPER_TABLE_SHIFT PMD_SHIFT
#endif
而 ARM64_SWAPPER_USES_SECTION_MAPS 在前面的章节分析为1,所以继续分析 SECTION_SIZE
$ grep -rnw SECTION_SIZE ./arch/arm64/
./arch/arm64/include/asm/pgtable-hwdef.h:78:#define SECTION_SIZE (_AC(1, UL) << SECTION_SHIFT)
SECTION_SHIFT 的定义如下
$ grep -rnw SECTION_SHIFT ./arch/arm64/
./arch/arm64/include/asm/pgtable-hwdef.h:77:#define SECTION_SHIFT PMD_SHIFT
PMD_SHIFT 的定义如下
$ grep -rnw PMD_SHIFT ./arch/arm64/
./arch/arm64/include/asm/pgtable-hwdef.h:49:#define PMD_SHIFT ARM64_HW_PGTABLE_LEVEL_SHIFT(2)
ARM64_HW_PGTABLE_LEVEL_SHIFT 在前面的章节分析过
#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3) == (12 - 3) * (4 - 2) + 3 == 21
这表示最后一级是块映射
根据上下文,调用函数 __create_page_tables 会生成3级映射,可以通过 SWAPPER_PGTABLE_LEVELS == 3 分析出来
PGD + PMD + PTE(最后一级是块映射) + 页内偏移
有效位宽分别为 9 + 9 + 9 + 21 == 48
第七个参数 tmp1 ,用于函数内部作为临时变量
下面开始分析宏的详细内容
代码分析
第18行调用了宏定义 phys_to_pte,该宏的定义如下
.macro phys_to_pte, pte, phys
#ifdef CONFIG_ARM64_PA_BITS_52
/*
* We assume \phys is 64K aligned and this is guaranteed by only
* supporting this configuration with 64K pages.
*/
orr \pte, \phys, \phys, lsr #36
and \pte, \pte, #PTE_ADDR_MASK
#else
mov \pte, \phys
#endif
.endm
我们没有使能 CONFIG_ARM64_PA_BITS_52,而是使能了 CONFIG_ARM64_PA_BITS_48,所以该宏就是把 rtbl,也就把是下一级的页表物理地址存放在 tmp1 里面
这说明 rtbl 还可能有用,我们下面的操作会修改它,所以放在临时变量里面进行后续处理
第19行将 tmp1 与 flags 进行或操作,也就是说,将页表基地址与页表属性组合在一起,由于页表基地值的有效位和页表属性的有效位在不同的位域里面,所以可以认为是缝合在一起。
第20行是最关键的一步,将上一步逻辑运算得出的缝合怪,也就是下一级页表基地址+页表属性存储在本级页表的指定的表项里面。
其中,指定的表项由 index 决定,index 是起始索引,由于每个表项的大小是8B,所以表项的 index 每次需要自增8,也就是逻辑左移3
第21行开始准备下一个表项要写的内容,在注释部分里面我们描述过,inc 是步长,所以该语句执行完毕 rtbl 成为了下一级页表第二个页面的基地址。
这个操作说明,rtbl 也会修改,按照这样操作,该宏执行完毕后,rtbl 指向下下级页表的基地址
第22行将索引值自增1,准备下一次的写页表处理操作
第23行将下一步要处理的索引与结束索引进行比较,如果前者较小的话说明没有到结束索引,所以跳转到18行重新执行