内存管理(三):内核上电启动阶段的页表映射 您所在的位置:网站首页 linux内存初始化过程 内存管理(三):内核上电启动阶段的页表映射

内存管理(三):内核上电启动阶段的页表映射

2024-07-16 18:15| 来源: 网络整理| 查看: 265

Linux版本:4.14.74

目录 1 启动阶段所需页表2 创建过程2.1启动阶段的页表大小2.2创建描述符函数2.3 创建中间页表2.4 section mapping2.5 映射的整体流程

1 启动阶段所需页表

在kernel启动阶段,会创建两次地址映射

Identity mappingKernel image mapping

在BootLoader以及uboot中,mmu功能是关闭的,操作的都是物理地址。为了提高性能,加快初始化速度,我们必须某个阶段(越早越好)打开MMU和cache,打开MMU之后操作的就是虚拟地址,为了从物理地址(Physical Address,简称PA)转换到虚拟地址(Virtual Address,简称VA)的平滑过渡,ARM推荐创建VA和PA相等的一段映射(例如:虚拟地址addr通过页表查询映射的物理地址也是addr)。这段映射在linux中称为identity mapping。 而为了执行kernel image,自然需要映射kernel image。 在这里插入图片描述 turn on MMU相关的代码被放入到一个特别的section,名字是.idmap.text,实际上对应上图中物理地址空间的IDMAP_TEXT这个block。这个区域的代码被mapping了两次,做为kernel image的一部分,它被映射到了__idmap_text_start开始的虚拟地址上去,此外,假设IDMAP_TEXT block的物理地址是A地址,那么它还被映射到了A地址开始的虚拟地址上去。 通过System.map就可以查看哪些函数被放在".idmap.text"段。

在这里插入图片描述

2 创建过程 2.1启动阶段的页表大小

在内存管理(二):页表映射过程我们描述了虚拟地址到物理地址的映射过程,我们仍然以虚拟地址宽度为39bit,页大小为4K来描述。有3级页表,PGD–>PMD–>PTE,PTE映射的大小是4K。但是在启动阶段,系统并不会以4K为单位进行映射,因为在启动阶段的目的就是能尽快的读取到kernel image,而以4K进行映射,假如kernel的大小为16M,映射kernel image就需要4096次。所以在启动流程中会使用section map,页表减少一级,变为PGD–>PMD–>offset,PMD映射大小是2M,对于16M大小的kernel image,最少只需要8次就能映射完成。 现在已经明确在启动阶段需要两级页表,PGD和PMD,并且需要identify mapping和kernel mapping两份页表,也就是需要4个页大小来存储启动阶段的页表,这块地址空间是什么分配的呢?是在链接脚本中vmlinux.lds.S

1. BSS_SECTION(0, 0, 0) 2. 3. . = ALIGN(PAGE_SIZE); 4. idmap_pg_dir = .; 5. . += IDMAP_DIR_SIZE; 6. swapper_pg_dir = .; 7. . += SWAPPER_DIR_SIZE; 8. idmap_full_pg_dir = .; 9. . += IDMAP_FULL_DIR_SIZE;

从链接脚本中可以看到预留了两份地址存储页表项。紧跟在bss段后面。idmap_pg_dir是identity mapping使用的页表。swapper_pg_dir是kernel image mapping初始阶段使用的页表。请注意,这里的内存是一段连续内存。也就是说页表(PGD/ PMD)都是连在一起的,地址相差PAGE_SIZE(4k) 接下来就是对两份页表进行填充,这部分用到三个函数

create_table_entrycreate_pgd_entrycreate_block_map

下面我们依次来看看这三个函数

2.2创建描述符函数

create_table_entry这个宏定义主要是用来创建一个中间level的translation table中的描述符。如果用linux的术语,就是创建PGD、PUD或者PMD的描述符。如果用ARM64术语,就是创建L0、L1或者L2的描述符。具体创建哪一个level的Translation table descriptor是由tbl参数指定的

1. [arch/arm64/kernel/head.S] 2. 3. /* 4. * Macro to create a table entry to the next page. 5. * 6. * tbl: page table address 7. * virt: virtual address 8. * shift: #imm page table shift 9. * ptrs: #imm pointers per table page 10. * 11. * Preserves: virt 12. * Corrupts: tmp1, tmp2 13. * Returns: tbl -> next level table page address 14. */ 15. .macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2 16. lsr \tmp1, \virt, #\shift /****1****/ 17. and \tmp1, \tmp1, #\ptrs - 1 /****1****/ // table index 18. add \tmp2, \tbl, #PAGE_SIZE /*****2***/ 19. orr \tmp2, \tmp2, #PMD_TYPE_TABLE /****3***/// address of next table and entry type 20. str \tmp2, [\tbl, \tmp1, lsl #3] /*****4******/ 21. add \tbl, \tbl, #PAGE_SIZE /*****5****/ // next level table page 22. .endm 参数tbl表示页表的地址,virt表示要映射的虚拟地址,shift表示这一级页表的在虚拟地址中的偏移,ptr表示这一级页表是几位的。tmp1和tmp2是两个临时变量(1)取出虚拟地址中对应的本级页表的那几位(2)取出下一级页表的地址,初始阶段的页表(PGD/PUD/PMD/PTE)都是排列在一起的,每一个占用一个page。也就是说,如果create_table_entry当前操作的是PGD,那么tmp2这时候保存了下一个level的页表,也就是PUD了(3)页表描述符的前两个bit表示该描述符是否有效,这里或上0x11,到这里,页表项的数据填充完毕(4)把页表项内容放到指定的页表项当中(5)结束的时候tbl会加上一个PAGE_SIZE,也就是tbl变成了下一级页表的地址 2.3 创建中间页表

下面的代码是create_pgd_entry,名称有点迷惑性,这个宏的作用并不仅仅是创建pgd,除了最末级页表PMD外,其他页表都会创建。当然在39bit情况下,只会创建PGD一个表项。

1. /* 2. * Macro to populate the PGD (and possibily PUD) for the corresponding 3. * block entry in the next level (tbl) for the given virtual address. 4. * 5. * Preserves: tbl, next, virt 6. * Corrupts: tmp1, tmp2 7. */ 8. .macro create_pgd_entry, tbl, virt, tmp1, tmp2 9. create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2 10. #if SWAPPER_PGTABLE_LEVELS > 3 11. create_table_entry \tbl, \virt, PUD_SHIFT, PTRS_PER_PUD, \tmp1, \tmp2 12. #endif 13. #if SWAPPER_PGTABLE_LEVELS > 2 14. create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2 15. #endif 16. .endm 17. 参数tbl表示页表的地址,virt表示要映射的虚拟地址。tmp1和tmp2是两个临时变量create_table_entry前面已经介绍过了,就是填充该页表的页表项(只填充一个)SWAPPER_PGTABLE_LEVELS:对于页大小为4K的情况,由于使用了section mapping,SWAPPER_PGTABLE_LEVELS会比页表LEVEL小1,其他页大小这个值与页表LEVEL一致

例子1:当虚拟地址是48个bit,4k page size,这时候page level等于4,映射关系是PGD(L0)—>PUD(L1)—>PMD(L2)—>Page table(L3)—>page,但是如果采用了section mapping(4k的page一定会采用section mapping),映射关系是PGD(L0)—>PUD(L1)—>PMD(L2)—>section。在create_pgd_entry函数中将创建PGD和PUD这两个中间level。

例子2:当虚拟地址是48个bit,16k page size(不能采用section mapping),这时候page level等于4,映射关系是PGD(L0)—>PUD(L1)—>PMD(L2)—>Page table(L3)—>page。在create_pgd_entry函数中将创建PGD、PUD和PMD这三个中间level。

例子3:当虚拟地址是39个bit,4k page size,这时候page level等于3,映射关系是PGD(L1)—>PMD(L2)—>Page table(L3)—>page。由于是4k page,因此采用section mapping,映射关系是PGD(L1)—>PMD(L2)—>section。在create_pgd_entry函数中将创建PGD这一个中间level。

2.4 section mapping

create_block_map则是以2M为大小进行映射

1. .macro create_block_map, tbl, flags, phys, start, end 2. lsr \phys, \phys, #SWAPPER_BLOCK_SHIFT 3. lsr \start, \start, #SWAPPER_BLOCK_SHIFT 4. and \start, \start, #PTRS_PER_PTE - 1 // table index 5. orr \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT // table entry 6. lsr \end, \end, #SWAPPER_BLOCK_SHIFT 7. and \end, \end, #PTRS_PER_PTE - 1 // table end index 8. : str \phys, [\tbl, \start, lsl #3] // store the entry 9. add \start, \start, #1 // next entry 10. add \phys, \phys, #SWAPPER_BLOCK_SIZE // next block 11. cmp \start, \end 12. b.ls 9999b 13. .endm 参数tbl页表地址,参数flags表示当前页表项指示的是block还是page。phys表示要映射的物理地址的起始地址,start和end分表表示物理地址要映射到的虚拟地址的开始和结束前6行当中,phys和flag计算得到页表项的内容,通过start得到页表的index开始,通过end得到页表的计数。9999循环映射从phys开始的地址映射到start—end的区域 2.5 映射的整体流程

现在我们来看看创建启动阶段页表的整体流程

14. __create_page_tables: 15. mov x28, lr 16. 17. /* 18. * Invalidate the idmap and swapper page tables to avoid potential 19. * dirty cache lines being evicted. 20. */ 21. /*******1*******/ 22. adrp x0, idmap_pg_dir 23. ldr x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE) 24. bl __inval_dcache_area 25. 26. /* 27. * Clear the idmap and swapper page tables. 28. */ 29. /***********2***********/ 30. adrp x0, idmap_pg_dir 31. ldr x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE) 32. 1: stp xzr, xzr, [x0], #16 33. stp xzr, xzr, [x0], #16 34. stp xzr, xzr, [x0], #16 35. stp xzr, xzr, [x0], #16 36. subs x1, x1, #64 37. b.ne 1b 38. 39. mov x7, SWAPPER_MM_MMUFLAGS 40. 41. /* 42. * Create the identity mapping. 43. */ 44. /******3*****/ 45. adrp x0, idmap_pg_dir 46. adrp x3, __idmap_text_start // __pa(__idmap_text_start) 47. 48. create_pgd_entry x0, x3, x5, x6 49. mov x5, x3 // __pa(__idmap_text_start) 50. adr_l x6, __idmap_text_end // __pa(__idmap_text_end) 51. create_block_map x0, x7, x3, x5, x6 52. 53. /* 54. * Map the kernel image (starting with PHYS_OFFSET). 55. */ 56. /*****4******/ 57. adrp x0, swapper_pg_dir 58. mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text) 59. add x5, x5, x23 // add KASLR displacement 60. create_pgd_entry x0, x5, x3, x6 61. adrp x6, _end // runtime __pa(_end) 62. adrp x3, _text // runtime __pa(_text) 63. sub x6, x6, x3 // _end - _text 64. add x6, x6, x5 // runtime __va(_end) 65. create_block_map x0, x7, x3, x5, x6 66. 67. /* 68. * Since the page tables have been populated with non-cacheable 69. * accesses (MMU disabled), invalidate the idmap and swapper page 70. * tables again to remove any speculatively loaded cache lines. 71. */ 72. adrp x0, idmap_pg_dir 73. ldr x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE) 74. dmb sy 75. bl __inval_dcache_area 76. 77. ret x28 78. ENDPROC(__create_page_tables) (1)对identity mapping和kernel mapping对应的区域invalid cache操作(2)请identity和swapper的页表区域清零操作(3)对identity区域进行映射,映射的过程就是调用create_pgd_entry和create_block_map的过程(4)对kernel区域进行映射,映射的过程就是调用create_pgd_entry和create_block_map的过程 上电映射页表的过程可简单总结为下图 在这里插入图片描述 通过create_pgd_entry创建中间页表,对于39bit虚拟地址宽度的情况,只用创建PGD中一个页表项。 根据identity mapping和kernel mappig的区域,以2M为单位进行section mapping的映射,直到把两个区域映射完毕。 好了,到这个阶段,我们就可以读取kerne image的数据了,可以进行kernel了。 在这里插入图片描述


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有