本文对应 https://www.cnblogs.com/zhangzhiwei122/p/16085238.html 中的“七、看到内存” 。
了解到了当前的物理内存的布局,但是内核仍然只是能够访问部分内存(kernel image mapping和DTB那两段内存,上图中黄色block),大部分的内存仍然处于黑暗中,等待光明的到来,也就是说需要创建这些内存的地址映射。
当前物理内存的布局的建立,见 前一篇: https://www.cnblogs.com/zhangzhiwei122/p/16086677.html
本文描述, 创建这些内存的地址映射。
arch/arm64/mm/mmu.c
709void __init paging_init(void) 710{ 711 pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir)); 712 713 map_kernel(pgdp); 714 map_mem(pgdp); 715 716 pgd_clear_fixmap(); 717 718 cpu_replace_ttbr1(lm_alias(swapper_pg_dir)); 719 init_mm.pgd = swapper_pg_dir; 720 721 memblock_free(__pa_symbol(init_pg_dir), 722 __pa_symbol(init_pg_end) - __pa_symbol(init_pg_dir)); 723 724 memblock_allow_resize(); 725}
711 行,pgd_set_fixmap ,使用 fixmap 中的 pgd idx 对应的 虚拟地址 来映射 __pa_symbol(swapper_pg_dir)) 这个物理地址。 这个物理地址里面存放 pgd 页表。
713 行,同文件中的 static 函数, create fine-grained mappings for the kernel 。 即对 内核镜像 占用的区域,创建 属性合适的 (只读,读写、执行)映射。
714 行,同文件中的 static 函数, map_mem( pgd table pointer )
472static void __init map_mem(pgd_t *pgdp) 473{ 474 phys_addr_t kernel_start = __pa_symbol(_text); 475 phys_addr_t kernel_end = __pa_symbol(__init_begin); 476 phys_addr_t start, end; 477 int flags = 0; 478 u64 i; 480 if (rodata_full || debug_pagealloc_enabled()) 481 flags = NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS; 482 483 /* 484 * Take care not to create a writable alias for the 485 * read-only text and rodata sections of the kernel image. 486 * So temporarily mark them as NOMAP to skip mappings in 487 * the following for-loop 488 */ 489 memblock_mark_nomap(kernel_start, kernel_end - kernel_start); 495 496 /* map all the memory banks */ 497 for_each_mem_range(i, &start, &end) { 498 if (start >= end) 499 break; 500 /* 501 * The linear map must allow allocation tags reading/writing 502 * if MTE is present. Otherwise, it has the same attributes as 503 * PAGE_KERNEL. 504 */ 505 __map_memblock(pgdp, start, end, PAGE_KERNEL_TAGGED, flags); 506 } 507 508 /* 509 * Map the linear alias of the [_text, __init_begin) interval 510 * as non-executable now, and remove the write permission in 511 * mark_linear_text_alias_ro() below (which will be called after 512 * alternative patching has completed). This makes the contents 513 * of the region accessible to subsystems such as hibernate, 514 * but protects it from inadvertent modification or execution. 515 * Note that contiguous mappings cannot be remapped in this way, 516 * so we should avoid them here. 517 */ 518 __map_memblock(pgdp, kernel_start, kernel_end, 519 PAGE_KERNEL, NO_CONT_MAPPINGS); 520 memblock_clear_nomap(kernel_start, kernel_end - kernel_start); 521 536} 537
kernel_start ~ kernel_end 这段区域,需要 建立的页表属性为 read only ,其他的 页表属性为 通常 为 reading & writing 【 例外情况就是 480 ~ 481 行的, 配置了 readonly full 或者 debug page alloc 】
489 行,将 kernel_start ~ kernel_end 这段区域 设置为 no map, 防止 下面的for 循环对其 建立映射页表。
497 ~ 506 行,对 memblock 中的内存,建立映射页表。flags 使用 0 或者 480 ~ 481 行设置的值。
518 行,对 kernel_start ~ kernel_end 这段区域 建立页表,使用 NO_CONT_MAPPINGS
519 行,使用memblock 函数,clear no map ,清掉 489 行设置的 no map 属性。
使用到的函数,__map_memblock , 利用 early_pgtable_alloc 这个 辅助函数,建立页表。如下
455static void __init __map_memblock(pgd_t *pgdp, phys_addr_t start, 456 phys_addr_t end, pgprot_t prot, int flags) 457{ 458 __create_pgd_mapping(pgdp, start, __phys_to_virt(start), end - start, 459 prot, early_pgtable_alloc, flags); 460}
创建映射,需要 分配空间时,使用 early_pgtable_alloc 来分配。 这个函数也在 同文件中。
97 行,使用 memblock 函数,找到一页空闲的物理内存,地址是 phys
106 行,将97 行找到的物理地址,映射到 fixmap 总 pte idx 对应的 VA 地址处。
108 行,使用 memset 函数进行填充。
116 行返回物理地址。
92static phys_addr_t __init early_pgtable_alloc(int shift) 93{ 94 phys_addr_t phys; 95 void *ptr; 96 97 phys = memblock_phys_alloc(PAGE_SIZE, PAGE_SIZE); 98 if (!phys) 99 panic("Failed to allocate page table page\n"); 100 101 /* 102 * The FIX_{PGD,PUD,PMD} slots may be in active use, but the FIX_PTE 103 * slot will be free, so we can (ab)use the FIX_PTE slot to initialise 104 * any level of table. 105 */ 106 ptr = pte_set_fixmap(phys); 107 108 memset(ptr, 0, PAGE_SIZE); 109 110 /* 111 * Implicit barriers also ensure the zeroed page is visible to the page 112 * table walker 113 */ 114 pte_clear_fixmap(); 115 116 return phys; 117}
背景说明:
1、fixmap 中,pgd pud pmd 对应的 虚拟地址可能正在被使用,但是 pte 对应的 虚拟地址 肯定是 可以被 覆盖的。
因为 修改地址转换页表时,需要 使用 虚拟地址,因为已经使能了 MMU ,CPU 操作的地址都时VA 地址。
但是 ,修改完成后,MMU工作时,就仅仅依赖页表里面填写的 物理地址了。
所以,FIXMAP 中的 pte 这个 VA 地址,哪里需要就 映射到哪里,然后就 可以使用这个VA 地址,填充里面的值。
示例:
__create_pgd_mapping 传入的参数 pgd 页表,基地址 pgdir , 查找到 页表 项 pgdp . 这个页表 项 VA 地址是可以访问的。
因为这个 pgdir 就是 paging_init 的 711 行,传入的 pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir)); fixmap 的 pgd idx 对应VA 映射到 swapper_pg_dir PA 上面。
349static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys, 350 unsigned long virt, phys_addr_t size, 351 pgprot_t prot, 352 phys_addr_t (*pgtable_alloc)(int), 353 int flags) 354{ 355 unsigned long addr, end, next; 356 pgd_t *pgdp = pgd_offset_pgd(pgdir, virt); 369 do { 371 alloc_init_pud(pgdp, addr, next, phys, prot, pgtable_alloc, 372 flags); 374 } while (pgdp++, addr = next, addr != end); 375}
由于 349 行传入的 pgdir 已经是 711 行建立过映射的,所以 356 行的 pgdp 可以被访问,这个地址在371行传入 alloc_init_pud 函数,在这个函数里面会 检查和 填充这个 pgdp 指针指向的 页表项
297 298static void alloc_init_pud(pgd_t *pgdp, unsigned long addr, unsigned long end, 299 phys_addr_t phys, pgprot_t prot, 300 phys_addr_t (*pgtable_alloc)(int), 301 int flags) 302{ 303 unsigned long next; 304 pud_t *pudp; 305 p4d_t *p4dp = p4d_offset(pgdp, addr); 306 p4d_t p4d = READ_ONCE(*p4dp); 307 308 if (p4d_none(p4d)) { 311 pud_phys = pgtable_alloc(PUD_SHIFT); 312 __p4d_populate(p4dp, pud_phys, PUD_TYPE_TABLE); 313 p4d = READ_ONCE(*p4dp); 314 } 316 317 pudp = pud_set_fixmap_offset(p4dp, addr); 318 do { 326 if (use_1G_block(addr, next, phys) && 336 } else { 337 alloc_init_cont_pmd(pudp, addr, next, phys, prot, 338 pgtable_alloc, flags); 339 342 } 344 } while (pudp++, addr = next, addr != end); 345
305 行由 pgdp, addr 得到 p4dp , 可以简单理解为 p4dp = pgdp .
308 行就访问到 p4dp 这个地址了,幸好它在 711 行建立 了映射,所以现在可以被 read & check , 312 行,对其进行 write 操作。
312行,写入 p4dp 页表 项的内容是 pud 表 物理 起始地址,通过 pgtable_alloc 分配(也就是 92 行的 early_pgtable_alloc ,分配一页),物理地址填入 p4dp 页表项。
317 行, pud_set_fixmap_offset(p4dp, addr ) 这个函数,读出 p4dp 页表项里面的物理地址(312 行刚填写进入的),结合 addr,找到 pud 页表里面的 表项的物理地址, 然后将这个物理地址,映射到 fixmap 的 PUD IDX 对应的slot 上面
pudp 这个地址就是 映射后的 VA 地址了,可以访问,将它填入到 337 行的 alloc_init_cont_pmd 里面,在它里面,会 访问 pudp 这个指针指向的内存。
249static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr, 250 unsigned long end, phys_addr_t phys, 251 pgprot_t prot, 252 phys_addr_t (*pgtable_alloc)(int), int flags) 253{ 255 pud_t pud = READ_ONCE(*pudp); 261 if (pud_none(pud)) { 264 pmd_phys = pgtable_alloc(PMD_SHIFT); 265 __pud_populate(pudp, pmd_phys, PUD_TYPE_TABLE); 267 } 270 do { 276 if ((((addr | next | phys) & ~CONT_PMD_MASK) == 0) && 277 (flags & NO_CONT_MAPPINGS) == 0) 280 init_pmd(pudp, addr, next, phys, __prot, pgtable_alloc, flags); 281 282 phys += next - addr; 283 } while (addr = next, addr != end); 284}
255 行就 read 这个地址,读出 页表项 内容,pud
261 行检查,如果为空,就在264 行申请 物理地址,然后265 行,写入 pud 页表 表项里面。
280 行,继续将 pudp 指针,传递给 init_pmd 函数。
212static void init_pmd(pud_t *pudp, unsigned long addr, unsigned long end, 213 phys_addr_t phys, pgprot_t prot, 214 phys_addr_t (*pgtable_alloc)(int), int flags) 215{ 219 pmdp = pmd_set_fixmap_offset(pudp, addr); 220 do { 221 pmd_t old_pmd = READ_ONCE(*pmdp); 222 226 if (((addr | next | phys) & ~SECTION_MASK) == 0 && 227 (flags & NO_BLOCK_MAPPINGS) == 0) { 236 } else { 237 alloc_init_cont_pte(pmdp, addr, next, phys, prot, 238 pgtable_alloc, flags); 242 } 243 phys += next - addr; 244 } while (pmdp++, addr = next, addr != end); 247} 248
219 行,pmd_set_fixmap_offset 函数,使用 pudp 页表项里面的物理地址,前面 264 行分配,265行填入的,结合 addr,找到 pmd 页表 里面对应的 表项 的物理地址,然后将这个物理地址映射到 fixmap 的 PMD IDX slot 对应的 VA 地址上面
221 就立刻使用了 219 行映射的 VA 地址, read once .
237 行,将映射后的VA 地址,交给 alloc_init_cont_pte 函数,它里面会访问 pmdp 这个VA 地址,读,检查,写,类似 249 行, alloc_init_cont_pmd 函数里面。
177static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr, 178 unsigned long end, phys_addr_t phys, 179 pgprot_t prot, 180 phys_addr_t (*pgtable_alloc)(int), 181 int flags) 182{ 184 pmd_t pmd = READ_ONCE(*pmdp); 185 187 if (pmd_none(pmd)) { 190 pte_phys = pgtable_alloc(PAGE_SHIFT); 191 __pmd_populate(pmdp, pte_phys, PMD_TYPE_TABLE); 192 pmd = READ_ONCE(*pmdp); 193 } 195 196 do { 202 if ((((addr | next | phys) & ~CONT_PTE_MASK) == 0) && 203 (flags & NO_CONT_MAPPINGS) == 0) 206 init_pte(pmdp, addr, next, phys, __prot); 207 209 } while (addr = next, addr != end); 210}
类似249 行, alloc_init_cont_pmd 函数,187 ~ 193 行,检查,如果为空,就设置。206 行,将pmdp VA地址传入 init_pte 函数
153static void init_pte(pmd_t *pmdp, unsigned long addr, unsigned long end, 154 phys_addr_t phys, pgprot_t prot) 155{ 158 ptep = pte_set_fixmap_offset(pmdp, addr); 159 do { 162 set_pte(ptep, pfn_pte(__phys_to_pfn(phys), prot)); 163 172 } while (ptep++, addr += PAGE_SIZE, addr != end); 173 174 pte_clear_fixmap(); 175}
158 行,类似前面 219 行。 从pmdp 页表 项中取出物理地址,这个物理地址时 191 行填入,然后依据 addr 找到 pte 页表中,表项的 物理地址,然后将物理地址,映射到 fixmap 的 PTE IDX slot 上面
163 行,就使用刚才映射的 ptep VA 地址。
这样,使用fixmap 的 pgd pud pmp pte 4个 slot ,和 early_pgtable_alloc 函数(使用 memblock 分 一页 物理地址), 就可以 对 memblock 中所有 记录的 内存进行映射表的创建和填写了。