页表映射

页表映射

chcore 内核启动的最后一步是完成页表的地址映射工作。在前文中,我们于 tool.S(被 init_c.c 调用)中启用了 MMU 以及相关配置,但具体的页表映射工作尚未提及,本节内容即为对 chcore 页表映射内容的源码解析

参考源码文件:mmu.c ,与 init_c.c 同目录

复习:页表结构

页表基址寄存器

在 AArch64 架构的 EL1 异常级别存在两个页表基址寄存器:ttbr0_el1 和  ttbr1_el1,分别用作虚拟地址空间低地址和高地址的翻译。而关于高低地址的具体范围则由由  tcr_el1  翻译控制寄存器控制。

一般情况下,我们会将  tcr_el1  配置为高低地址各有 48 位的地址范围,即:

  • 0x0000_0000_0000_00000x0000_ffff_ffff_ffff  为低地址
  • 0xffff_0000_0000_00000xffff_ffff_ffff_ffff  为高地址

页表地址翻译

有了页表基址寄存器的知识,我们再来看 chcore 是如何翻译地址的。chcore 中页表的地址翻译采取了多级页表的形式,如下图所示:

多级页表

所谓多级页表,是一种内存管理技术,用于虚拟内存系统中将虚拟地址映射到物理地址。它通过多级层次结构来减少页表所占用的内存空间,并提高页表的查找效率。

在多级页表结构中,虚拟地址被分割成多个字段,每个字段对应不同级别的页表索引。最顶层的页表包含指向下一级页表的指针,而每一层页表都包含指向更详细页表或物理内存页的指针。

在 Chcore 中,页表一共分为 4 级:从 L0-L2 都是对下一级别索引的指针,一直到最后一级 L3,才指向具体到页(以 4KB 粒度)

Chcore 的物理地址空间分布

如下图所示,这是我们后面页表映射时确定物理地址的重要信息

接下来,就让我们一起看看具体的页表映射源码吧!

Chcore 页表映射

总览

先总览代码,获取一个对源码的大致印象与结构

  • 页表映射源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    #include <common/macro.h>
    #include "image.h"
    #include "boot.h"
    #include "consts.h"

    typedef unsigned long u64;
    typedef unsigned int u32;

    /* Physical memory address space: 0-1G */
    #define PHYSMEM_START (0x0UL)
    #define PERIPHERAL_BASE (0x3F000000UL)
    #define PHYSMEM_END (0x40000000UL)

    /* The number of entries in one page table page */
    #define PTP_ENTRIES 512
    /* The size of one page table page */
    #define PTP_SIZE 4096
    #define ALIGN(n) __attribute__((__aligned__(n)))
    u64 boot_ttbr0_l0[PTP_ENTRIES] ALIGN(PTP_SIZE);
    u64 boot_ttbr0_l1[PTP_ENTRIES] ALIGN(PTP_SIZE);
    u64 boot_ttbr0_l2[PTP_ENTRIES] ALIGN(PTP_SIZE);
    u64 boot_ttbr0_l3[PTP_ENTRIES] ALIGN(PTP_SIZE);

    u64 boot_ttbr1_l0[PTP_ENTRIES] ALIGN(PTP_SIZE);
    u64 boot_ttbr1_l1[PTP_ENTRIES] ALIGN(PTP_SIZE);
    u64 boot_ttbr1_l2[PTP_ENTRIES] ALIGN(PTP_SIZE);
    u64 boot_ttbr1_l3[PTP_ENTRIES] ALIGN(PTP_SIZE);

    #define IS_VALID (1UL << 0)
    #define IS_TABLE (1UL << 1)
    #define IS_PTE (1UL << 1)

    #define PXN (0x1UL << 53)
    #define UXN (0x1UL << 54)
    #define ACCESSED (0x1UL << 10)
    #define NG (0x1UL << 11)
    #define INNER_SHARABLE (0x3UL << 8)
    #define NORMAL_MEMORY (0x4UL << 2)
    #define DEVICE_MEMORY (0x0UL << 2)
    #define RDONLY_S (0x2UL << 6)

    #define SIZE_2M (2UL * 1024 * 1024)
    #define SIZE_4K (4UL * 1024)

    #define GET_L0_INDEX(x) (((x) >> (12 + 9 + 9 + 9)) & 0x1ff)
    #define GET_L1_INDEX(x) (((x) >> (12 + 9 + 9)) & 0x1ff)
    #define GET_L2_INDEX(x) (((x) >> (12 + 9)) & 0x1ff)
    #define GET_L3_INDEX(x) (((x) >> (12)) & 0x1ff)

    extern int boot_cpu_stack[PLAT_CPU_NUMBER][INIT_STACK_SIZE];

    void init_kernel_pt(void)
    {
    u64 vaddr = PHYSMEM_START;

    /* TTBR0_EL1 0-1G */
    boot_ttbr0_l0[GET_L0_INDEX(vaddr)] = ((u64)boot_ttbr0_l1) | IS_TABLE
    | IS_VALID | NG;
    boot_ttbr0_l1[GET_L1_INDEX(vaddr)] = ((u64)boot_ttbr0_l2) | IS_TABLE
    | IS_VALID | NG;

    boot_ttbr0_l2[GET_L2_INDEX(vaddr)] = ((u64)boot_ttbr0_l3) | IS_TABLE
    | IS_VALID | NG;

    /* first 2M, including .init section */
    for (; vaddr < SIZE_2M; vaddr += SIZE_4K) {
    boot_ttbr0_l3[GET_L3_INDEX(vaddr)] =
    (vaddr) | UXN /* Unprivileged execute never */
    | PXN /* Privileged execute never */
    | ACCESSED /* Set access flag */
    | NG /* Mark as not global */
    | INNER_SHARABLE /* Sharebility */
    | NORMAL_MEMORY /* Normal memory */
    | IS_PTE | IS_VALID;

    /*
    * Code in init section(img_start~init_end) should be mmaped as
    * RDONLY_S due to WXN
    * The boot_cpu_stack is also in the init section, but should
    * have write permission
    */
    if (vaddr >= (u64)(&img_start) && vaddr < (u64)(&init_end)
    && (vaddr < (u64)boot_cpu_stack
    || vaddr >= ((u64)boot_cpu_stack)
    + PLAT_CPU_NUMBER
    * INIT_STACK_SIZE)) {
    boot_ttbr0_l3[GET_L3_INDEX(vaddr)] &= ~PXN;
    boot_ttbr0_l3[GET_L3_INDEX(vaddr)] |=
    RDONLY_S; /* Read Only*/
    }
    }

    /* Normal memory: PHYSMEM_START ~ PERIPHERAL_BASE */
    /* Map with 2M granularity */
    for (; vaddr < PERIPHERAL_BASE; vaddr += SIZE_2M) {
    boot_ttbr0_l2[GET_L2_INDEX(vaddr)] =
    (vaddr) /* low mem, va = pa */
    | UXN /* Unprivileged execute never */
    | ACCESSED /* Set access flag */
    | NG /* Mark as not global */
    | INNER_SHARABLE /* Sharebility */
    | NORMAL_MEMORY /* Normal memory */
    | IS_VALID;
    }

    /* Peripheral memory: PERIPHERAL_BASE ~ PHYSMEM_END */
    /* Map with 2M granularity */
    for (vaddr = PERIPHERAL_BASE; vaddr < PHYSMEM_END; vaddr += SIZE_2M) {
    boot_ttbr0_l2[GET_L2_INDEX(vaddr)] =
    (vaddr) /* low mem, va = pa */
    | UXN /* Unprivileged execute never */
    | ACCESSED /* Set access flag */
    | NG /* Mark as not global */
    | DEVICE_MEMORY /* Device memory */
    | IS_VALID;
    }

    /* TTBR1_EL1 0-1G */
    /* BLANK BEGIN */
    vaddr = KERNEL_VADDR + PHYSMEM_START;
    boot_ttbr1_l0[GET_L0_INDEX(vaddr)] = ((u64)boot_ttbr1_l1) | IS_TABLE
    | IS_VALID;
    boot_ttbr1_l1[GET_L1_INDEX(vaddr)] = ((u64)boot_ttbr1_l2) | IS_TABLE
    | IS_VALID;

    /* Normal memory: PHYSMEM_START ~ PERIPHERAL_BASE
    * The text section code in kernel should be mapped with flag R/X.
    * The other section and normal memory is mapped with flag R/W.
    * memory layout :
    * | normal memory | kernel text section | kernel data section ... |
    * normal memory |
    */

    boot_ttbr1_l2[GET_L2_INDEX(vaddr)] = ((u64)boot_ttbr1_l3) | IS_TABLE
    | IS_VALID;

    /* the kernel text section was mapped in the first
    * L2 page table in boot_ptd_l1 now.
    */
    BUG_ON((u64)(&_text_end) >= KERNEL_VADDR + SIZE_2M);
    /* _text_start & _text_end should be 4K aligned*/
    BUG_ON((u64)(&_text_start) % SIZE_4K != 0
    || (u64)(&_text_end) % SIZE_4K != 0);

    for (; vaddr < KERNEL_VADDR + SIZE_2M; vaddr += SIZE_4K) {
    boot_ttbr1_l3[GET_L3_INDEX(vaddr)] =
    (vaddr - KERNEL_VADDR) | UXN /* Unprivileged execute
    never */
    | PXN /* Priviledged execute never*/
    | ACCESSED /* Set access flag */
    | INNER_SHARABLE /* Sharebility */
    | NORMAL_MEMORY /* Normal memory */
    | IS_PTE | IS_VALID;
    /* (KERNEL_VADDR + TEXT_START ~ KERNEL_VADDR + TEXT_END) was
    * mapped to physical address (PHY_START ~ PHY_START + TEXT_END)
    * with R/X
    */
    if (vaddr >= (u64)(&_text_start) && vaddr < (u64)(&_text_end)) {
    boot_ttbr1_l3[GET_L3_INDEX(vaddr)] &= ~PXN;
    boot_ttbr1_l3[GET_L3_INDEX(vaddr)] |=
    RDONLY_S; /* Read Only*/
    }
    }

    for (; vaddr < KERNEL_VADDR + PERIPHERAL_BASE; vaddr += SIZE_2M) {
    /* No NG bit here since the kernel mappings are shared */
    boot_ttbr1_l2[GET_L2_INDEX(vaddr)] =
    (vaddr - KERNEL_VADDR) /* high mem, va = kbase + pa */
    | UXN /* Unprivileged execute never */
    | PXN /* Priviledged execute never*/
    | ACCESSED /* Set access flag */
    | INNER_SHARABLE /* Sharebility */
    | NORMAL_MEMORY /* Normal memory */
    | IS_VALID;
    }

    /* Peripheral memory: PERIPHERAL_BASE ~ PHYSMEM_END */
    /* Map with 2M granularity */
    for (vaddr = KERNEL_VADDR + PERIPHERAL_BASE;
    vaddr < KERNEL_VADDR + PHYSMEM_END;
    vaddr += SIZE_2M) {
    boot_ttbr1_l2[GET_L2_INDEX(vaddr)] =
    (vaddr - KERNEL_VADDR) /* high mem, va = kbase + pa */
    | UXN /* Unprivileged execute never */
    | PXN /* Priviledged execute never*/
    | ACCESSED /* Set access flag */
    | DEVICE_MEMORY /* Device memory */
    | IS_VALID;
    }

    /*
    * Local peripherals, e.g., ARM timer, IRQs, and mailboxes
    *
    * 0x4000_0000 .. 0xFFFF_FFFF
    * 1G is enough (for Mini-UART). Map 1G page here.
    */
    vaddr = KERNEL_VADDR + PHYSMEM_END;
    boot_ttbr1_l1[GET_L1_INDEX(vaddr)] = PHYSMEM_END | UXN /* Unprivileged
    execute never
    */
    | PXN /* Priviledged execute
    never*/
    | ACCESSED /* Set access flag */
    | DEVICE_MEMORY /* Device memory */
    | IS_VALID;
    }

结合注释信息可知,这部分代码主要分为两部分:

  • 宏定义与数据结构声明:这部分定义了后面页表配置时相应属性对应的宏以及多级页表中会用到的数据结构;此外,宏定义中还包括内存区域划分与页表大小等信息
  • 页表地址映射:即 init_kernel_pt() 函数,我们的页表映射工作即在此完成,也是我们源码解析的重点所在

宏定义与数据结构声明

这一部分主要介绍代码中的宏定义与页表配置相关的数据结构定义

内存区域划分

如下方代码所示:

1
2
3
4
/* Physical memory address space: 0-1G */
#define PHYSMEM_START (0x0UL)
#define PERIPHERAL_BASE (0x3F000000UL)
#define PHYSMEM_END (0x40000000UL)

这三行代码声明的宏将我们要映射的物理地址(一共 1G)分为了两部分:普通的 RAM 内存区域与外设映射区域,其中 UL 表示 unsigned long

其中前者很好理解,就是内核自身的 RAM 内存,关于后者“外设映射”,可以理解为是在这部分地址开始映射各种硬件外设,例如:

  • GPIO 控制器
  • UART 串口
  • 中断控制器
  • 定时器
  • USB 控制器等

总体的内存结构即如下图所示:

1
2
3
4
5
6
7
8
9
0x00000000 +-----------------+ <- PHYSMEM_START
| |
| 普通内存区域 |
| |
0x3F000000 +-----------------+ <- PERIPHERAL_BASE
| |
| 外设映射区域 |
| |
0x40000000 +-----------------+ <- PHYSMEM_END

页表项数组定义

如下方代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* The number of entries in one page table page */
#define PTP_ENTRIES 512
/* The size of one page table page */
#define PTP_SIZE 4096
#define ALIGN(n) __attribute__((__aligned__(n)))
u64 boot_ttbr0_l0[PTP_ENTRIES] ALIGN(PTP_SIZE);
u64 boot_ttbr0_l1[PTP_ENTRIES] ALIGN(PTP_SIZE);
u64 boot_ttbr0_l2[PTP_ENTRIES] ALIGN(PTP_SIZE);
u64 boot_ttbr0_l3[PTP_ENTRIES] ALIGN(PTP_SIZE);

u64 boot_ttbr1_l0[PTP_ENTRIES] ALIGN(PTP_SIZE);
u64 boot_ttbr1_l1[PTP_ENTRIES] ALIGN(PTP_SIZE);
u64 boot_ttbr1_l2[PTP_ENTRIES] ALIGN(PTP_SIZE);
u64 boot_ttbr1_l3[PTP_ENTRIES] ALIGN(PTP_SIZE);

其中数组部分比较好理解,看名称:ttbrx 即表示页表基址寄存器,lx 表示具体的页表级数(0-3),而数组大小即为定义好的 512,是页表页的入口条数

这里再说说这一行代码:

1
#define ALIGN(n) __attribute__((__aligned__(n)))

这行代码定义了一个对齐属性的宏__attribute__((__aligned__(n))) 是 GCC 编译器的一个特殊属性声明,它告诉编译器将变量或数据结构按照 n 字节边界对齐

例如下面这行声明代码:

1
u64 boot_ttbr0_l0[PTP_ENTRIES] ALIGN(PTP_SIZE);

这里  ALIGN(PTP_SIZE)  其中  PTP_SIZE = 4096,意味着这个数组将被对齐到 4KB 边界

而关于为什么需要对齐,这便涉及到硬件架构要求和性能优化的相关知识了,感兴趣的可以自己多查阅了解阅读

页表控制属性描述符

如下方代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
#define IS_VALID (1UL << 0)
#define IS_TABLE (1UL << 1)
#define IS_PTE (1UL << 1)

#define PXN (0x1UL << 53)
#define UXN (0x1UL << 54)
#define ACCESSED (0x1UL << 10)
#define NG (0x1UL << 11)
#define INNER_SHARABLE (0x3UL << 8)
#define NORMAL_MEMORY (0x4UL << 2)
#define DEVICE_MEMORY (0x0UL << 2)
#define RDONLY_S (0x2UL << 6)

这部分定义了页表的属性描述符,在配置页表的时候,我们可以通过将待配置的地址与之进行或运算(即 | ) 即可

而页表属性的具体含义通常与内存访问权限等相关,具体见下,亦可以自行做更多了解:

  • UXN :  用户模式(非特权态)下不可执行
  • PXN : 特权模式(特权态)下不可执行
  • RDONLY_S : 只读访问
  • INNER_SHARABLE : 内部可共享
  • NORMAL_MEMORY/DEVICE_MEMORY : 内存类型标识
  • NG : 非全局页面标识

提取索引辅助函数

如下方代码所示:

1
2
3
4
#define GET_L0_INDEX(x) (((x) >> (12 + 9 + 9 + 9)) & 0x1ff)
#define GET_L1_INDEX(x) (((x) >> (12 + 9 + 9)) & 0x1ff)
#define GET_L2_INDEX(x) (((x) >> (12 + 9)) & 0x1ff)
#define GET_L3_INDEX(x) (((x) >> (12)) & 0x1ff)

这部分是用于将虚拟地址提取出对应位置的索引的辅助函数,从 L0 到 L3 都有。其中各个数字的含义如下:

  • 12: 页内偏移位数(4KB 页面  = 2^12)
  • 9: 每级页表索引的位数(512 个表项  = 2^9)
  • 0x1ff: 9 位掩码(二进制:111111111),是一个 mask 操作

绝知此事要躬行,我们假设有一个虚拟地址:

1
2
位数:   |   9位   |   9位   |   9位   |   9位   |   12位   |
内容:   | L0索引  | L1索引   | L2索引  | L3索引  | 页内偏移  |

那么各辅助函数的功能即如下所述:

  • GET_L0_INDEX: 右移 39 位,获取最高的 9 位
  • GET_L1_INDEX: 右移 30 位,获取第二个 9 位
  • GET_L2_INDEX: 右移 21 位,获取第三个 9 位
  • GET_L3_INDEX: 右移 12 位,获取第四个 9 位

页表地址映射

工欲善其事,必先利其器。上面的介绍为我们解析配置页表部分的源码扫清了障碍,现在,我们正式进入 init_kernel_pt() 函数,来对 chcore 的页表映射逻辑一窥究竟

浏览代码不难发现,本函数主要分为两大块:低地址映射与高地址映射,前者为用户态,后者为内核态,分别由相应的页表基址寄存器控制。其中各自具体配置手段相似,区别在于内核态配置时需要加上相应的偏移量,否则配置就是乱的

低地址映射

我们将详细讲解这一部分,对于后面的高地址映射,我们将只说明不同的地方,其余大体上是相似的

  • 首先是设置多级页表之间的链接关系
1
2
3
4
5
6
7
8
9
10
u64 vaddr = PHYSMEM_START;

/* TTBR0_EL1 0-1G */
boot_ttbr0_l0[GET_L0_INDEX(vaddr)] = ((u64)boot_ttbr0_l1) | IS_TABLE
| IS_VALID | NG;
boot_ttbr0_l1[GET_L1_INDEX(vaddr)] = ((u64)boot_ttbr0_l2) | IS_TABLE
| IS_VALID | NG;

boot_ttbr0_l2[GET_L2_INDEX(vaddr)] = ((u64)boot_ttbr0_l3) | IS_TABLE
| IS_VALID | NG;

这里将 vaddr 设置为最开始的物理内存起始,然后进行了相应的链接关系配置


graph TD
boot_ttbr0_l0-->boot_ttbr0_l1
boot_ttbr0_l1-->boot_ttbr0_l2
boot_ttbr0_l2-->boot_ttbr0_l3

这里完成了初始化工作后,后面便开始了具体的配置

  • 初始 2M 内存,以 4KB 粒度映射,注意这里包含.init部分,需要做特殊处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* first 2M, including .init section */
for (; vaddr < SIZE_2M; vaddr += SIZE_4K) {
boot_ttbr0_l3[GET_L3_INDEX(vaddr)] =
(vaddr) | UXN /* Unprivileged execute never */
| PXN /* Privileged execute never */
| ACCESSED /* Set access flag */
| NG /* Mark as not global */
| INNER_SHARABLE /* Sharebility */
| NORMAL_MEMORY /* Normal memory */
| IS_PTE | IS_VALID;

/*
* Code in init section(img_start~init_end) should be mmaped as
* RDONLY_S due to WXN
* The boot_cpu_stack is also in the init section, but should
* have write permission
*/
if (vaddr >= (u64)(&img_start) && vaddr < (u64)(&init_end)
&& (vaddr < (u64)boot_cpu_stack
|| vaddr >= ((u64)boot_cpu_stack)
+ PLAT_CPU_NUMBER
* INIT_STACK_SIZE)) {
boot_ttbr0_l3[GET_L3_INDEX(vaddr)] &= ~PXN;
boot_ttbr0_l3[GET_L3_INDEX(vaddr)] |=
RDONLY_S; /* Read Only*/
}
}

主体的配置过程其实就是这样的:

1
相应的页表级[辅助函数获取索引(vaddr)] = (vaddr) | 一大堆属性

这里还要注意一下for循环的末尾,有一个对.init部分内存的特殊设置—— RDONLY_S ,即只读

  • 配置普通 RAM 内存与外设内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Normal memory: PHYSMEM_START ~ PERIPHERAL_BASE */
/* Map with 2M granularity */
for (; vaddr < PERIPHERAL_BASE; vaddr += SIZE_2M) {
boot_ttbr0_l2[GET_L2_INDEX(vaddr)] =
(vaddr) /* low mem, va = pa */
| UXN /* Unprivileged execute never */
| ACCESSED /* Set access flag */
| NG /* Mark as not global */
| INNER_SHARABLE /* Sharebility */
| NORMAL_MEMORY /* Normal memory */
| IS_VALID;
}

/* Peripheral memory: PERIPHERAL_BASE ~ PHYSMEM_END */
/* Map with 2M granularity */
for (vaddr = PERIPHERAL_BASE; vaddr < PHYSMEM_END; vaddr += SIZE_2M) {
boot_ttbr0_l2[GET_L2_INDEX(vaddr)] =
(vaddr) /* low mem, va = pa */
| UXN /* Unprivileged execute never */
| ACCESSED /* Set access flag */
| NG /* Mark as not global */
| DEVICE_MEMORY /* Device memory */
| IS_VALID;
}

不难发现总体上的代码逻辑是相似的,那如何区分不同的内存呢?——通过页表属性即可

在这一部分我们以 2M 的粒度对普通内存+外设内存完成了映射,注意在 2M 粒度下,我们的页表级是 L2

高地址映射

总体上和低地址映射是相似的,不同之处在于二者之间有一个偏移量,因此我们在配置高地址映射的时候需要加上这一部分,它由相应的页表基址寄存器控制。在代码中,即为 KERNEL_VADDR ,定义在 image.h 头文件中

这里我们只举一个例子来说明这一点,就不全部讲解了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* TTBR1_EL1 0-1G */
/* BLANK BEGIN */
vaddr = KERNEL_VADDR + PHYSMEM_START;

...

for (; vaddr < KERNEL_VADDR + PERIPHERAL_BASE; vaddr += SIZE_2M) {
/* No NG bit here since the kernel mappings are shared */
boot_ttbr1_l2[GET_L2_INDEX(vaddr)] =
(vaddr - KERNEL_VADDR) /* high mem, va = kbase + pa */
| UXN /* Unprivileged execute never */
| PXN /* Priviledged execute never*/
| ACCESSED /* Set access flag */
| INNER_SHARABLE /* Sharebility */
| NORMAL_MEMORY /* Normal memory */
| IS_VALID;
}

...

以配置普通 RAM 内存这一段为例,这里初始化 vaddr 时即加上了对应的偏移量,在 for 循环中也有相应的体现,这便是高地址映射时不同的地方

配置本地外设内存映射

注意到 1G-4G 这部分内存还没有用到,这部分是留给配置本地外设用的。在 chcore 中,我们只配置了 1G,但是这是足够的,如下方代码所示,这也是页表映射的最后一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        /*
* Local peripherals, e.g., ARM timer, IRQs, and mailboxes
*
* 0x4000_0000 .. 0xFFFF_FFFF
* 1G is enough (for Mini-UART). Map 1G page here.
*/
vaddr = KERNEL_VADDR + PHYSMEM_END;
boot_ttbr1_l1[GET_L1_INDEX(vaddr)] = PHYSMEM_END | UXN /* Unprivileged
execute never
*/
| PXN /* Priviledged execute
never*/
| ACCESSED /* Set access flag */
| DEVICE_MEMORY /* Device memory */
| IS_VALID;
}

1G 的内存配置是便直接使用 L1 级别的页表了,这也体现了多级页表的特点

至此,页表映射部分的源码解析全部结束,希望对你学习进步有所帮助!


页表映射
http://example.com/2024/12/23/ptm/
作者
思源南路世一劈
发布于
2024年12月23日
许可协议