衔接前文的概览篇,本文开始介绍完整的映射过程:
本部分处理过程在汇编code中,主要介绍流程;
仅关注跳转的最后一个步骤:
/* jump to kernel */
static void boot_linux_from_emmc(const struct app_descriptor *app, void *args)
{
load_image_from_bootimg(KERNEL_LOAD_ADDR, BOOTIMG_IMAGEGZ);//将kernel执行code放到指定位置,虚拟地址为0xFFFF FFF8 0008 0000,对应物理地址为0x800080000;
cleanup_before_linux();
void (*theKernel)(unsigned long fdtAddr);
theKernel = (void (*)(unsigned long))(KERNEL_LOAD_ADDR_PHYS);//指向地址为物理地址0x800080000
theKernel (FDT_LOAD_ADDR_PHYS);//参数为:0x800000000
}
//经过简化后的函数:
void load_image_from_bootimg(uintptr_t load_addr, IMAGE_TYPE_E image_type)
{
unsigned long src_addr = 0;
unsigned long size = 0;
...
src_addr = BOOT_LOAD_ADDR + g_kernel_start_offset;//在lk中load到mem中的boot.img,需要其中kernel空间位置的起始
size = g_kernel_size;
...
memcpy((void *)load_addr, (void *)src_addr, size);//拷贝到load_addr,即上文传入的KERNEL_LOAD_ADDR,对应物理地址为0x800080000
flush_cache(load_addr, size);
}
即上述首先load image到指定地址,然后跳转到该地址执行,也就是Kernel 的入口位置head.S中stext;
我们查看kernel中各个段的地址,一般通过如下两种方式:
入口在head.S的ENTRY(stext) 这里:
ENTRY(stext)
bl preserve_boot_args
bl el2_setup // Drop to EL1, w0=cpu_boot_mode
adrp x23, __PHYS_OFFSET
and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
bl set_cpu_boot_mode_flag
bl __create_page_tables
/*
* The following calls CPU setup code, see arch/arm64/mm/proc.S for
* details.
* On return, the CPU will be ready for the MMU to be turned on and
* the TCR will have been set.
*/
bl __cpu_setup // initialise processor
b __primary_switch
ENDPROC(stext)
处理如下:
则这里我们主要关注的部分就在__create_page_tables:
映射部分这里是用汇编来实现的,本部分主要关注:
通过zcat /proc/config.gz | grep XXX 命令确认平台kernel中的配置情况,比查看config文件更快一些:
CONFIG_ARM64_VA_BITS_39=y 决定了VA的地址范围
在linux文档中可以很清晰的看到上述配置如何影响地址范围:
CONFIG_ARM64_4K_PAGES=y 决定了页面配置大小
CONFIG_PGTABLE_LEVELS=3 决定了level级别
页表level和page size决策页表的划分:
经过上述两个小节的描述,对于则页表的内容相对比较清晰了,具体来看宏转换:
CONFIG_ARM64_4K_PAGES=y ==> #define ARM64_SWAPPER_USES_SECTION_MAPS 1
CONFIG_PGTABLE_LEVELS=3
这里首先来理解 SECTION_MAPS 从code来看,只有4K页表支持它,那他与其他的映射之间的差异在哪里呢?
也就是说最小粒度按照section来映射,最低的两个level 共21位,也就是2M大小,更直白的描述就是L2的每个地址直接指向一个2M大小的region,而不需要先找到L3,再指向一个4K的region;
对于kernel image这样的big block memory region,使用4K的page来mapping有点得不偿失,在这种情况下,可以考虑让level 2的Translation table entry指向一个2M 的memory region,而不是下一级的Translation table
所有这些复杂的宏的计算,实质都是为了满足我们上述页表划分的需求,即实现过程:
具体转换:
#define SECTION_SHIFT PMD_SHIFT
#define SECTION_SIZE (_AC(1, UL) << SECTION_SHIFT) //即size为pmd_shift
#if CONFIG_PGTABLE_LEVELS > 2 // levels 配置为3
#define PMD_SHIFT ARM64_HW_PGTABLE_LEVEL_SHIFT(2) // 即计算第PMD作为level 2 的 shift
#define PMD_SIZE (_AC(1, UL) << PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE-1))
#define PTRS_PER_PMD PTRS_PER_PTE
#endif
/*
* ((4 - n) - 1) * (PAGE_SHIFT - 3) + PAGE_SHIFT
*
* Rearranging it a bit we get :
* (4 - n) * (PAGE_SHIFT - 3) + 3
*/
#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3) //这里计算出来为21,所以section size就是2M
#define SWAPPER_DIR_SIZE (SWAPPER_PGTABLE_LEVELS * PAGE_SIZE) //这里是8K
#define IDMAP_DIR_SIZE (IDMAP_PGTABLE_LEVELS * PAGE_SIZE) // 这里是8K
可以看到都是通过level * page的处理,page size我们知道是4K,那么需要计算的就是level
#define SWAPPER_PGTABLE_LEVELS (CONFIG_PGTABLE_LEVELS - 1) //这里是因为采用4K大小的界面,所以采用了 section 的region,则只需要两个page
则SWAPPER_DIR_SIZE 可以固定到是8K
#define IDMAP_PGTABLE_LEVELS (ARM64_HW_PGTABLE_LEVELS(PHYS_MASK_SHIFT) - 1) //这里是因为采用4K大小的界面,所以采用了 section 的region
关于ARM64_HW_PGTABLE_LEVELS,是通过传入的VA bit来计算所需要的level,即translate level:
公式转化后即宏的定义:
#define ARM64_HW_PGTABLE_LEVELS(va_bits) (((va_bits) - 4) / (PAGE_SHIFT - 3)) //PAGE_SHIFT 12 44/9 = 4
所以这里问题来了,传入的PHYS_MASK_SHIFT应该是多少呢?在定义中为48,根据头文件链接,然后实际va_bits应该为39,这里就有问题了,这里为3?
过程描述的话比较简单:
//注释这里说的很明确,会做两个映射:identity mapping && swapper mapping
__create_page_tables:
mov x28, lr
/*
* Invalidate the idmap and swapper page tables to avoid potential
* dirty cache lines being evicted.
*/
adrp x0, idmap_pg_dir //获取idmap页表基地址,这个是在vmlinux.lds.Szhong定义的
adrp x1, swapper_pg_dir + SWAPPER_DIR_SIZE // 获取页表末尾地址
bl __inval_cache_range //将对应段的cacheline设置为无效,为啥?清除掉数据?
/*
* Clear the idmap and swapper page tables.即清零操作,所有描述符均无效
*/
adrp x0, idmap_pg_dir //开始地址
adrp x6, swapper_pg_dir + SWAPPER_DIR_SIZE //结束地址
1: stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
cmp x0, x6
b.lo 1b
mov x7, SWAPPER_MM_MMUFLAGS
/*
* Create the identity mapping.
*/
adrp x0, idmap_pg_dir
adrp x3, __idmap_text_start // __pa(__idmap_text_start)
#ifndef CONFIG_ARM64_VA_BITS_48
#define EXTRA_SHIFT (PGDIR_SHIFT + PAGE_SHIFT - 3) //这里算出来是39
#define EXTRA_PTRS (1 << (48 - EXTRA_SHIFT)) // 1 << 9
adrp x5, __idmap_text_end
clz x5, x5
cmp x5, TCR_T0SZ(VA_BITS) // default T0SZ small enough?
b.ge 1f // .. then skip additional level
adr_l x6, idmap_t0sz
str x5, [x6]
dmb sy
dc ivac, x6 // Invalidate potentially stale cache line
create_table_entry x0, x3, EXTRA_SHIFT, EXTRA_PTRS, x5, x6 //创建一个中间level的translation table中的描述符;
1:
#endif
create_pgd_entry x0, x3, x5, x6 //创建一个PGD的描述符并创建下一级translation table,完成所有中间level的translation table的创建
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 //该函数就是在tbl指定的Translation table中建立block descriptor以便完成address mapping
/*
* Map the kernel image (starting with PHYS_OFFSET).
*/
adrp x0, swapper_pg_dir
mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text)
add x5, x5, x23 // add KASLR displacement
create_pgd_entry x0, x5, x3, x6 //建立中间level
adrp x6, _end // runtime __pa(_end)
adrp x3, _text // runtime __pa(_text)
sub x6, x6, x3 // _end - _text
add x6, x6, x5 // runtime __va(_end)
create_block_map x0, x7, x3, x5, x6 //建立block descriptor
/*
* Since the page tables have been populated with non-cacheable
* accesses (MMU disabled), invalidate the idmap and swapper page
* tables again to remove any speculatively loaded cache lines.
*/
adrp x0, idmap_pg_dir
adrp x1, swapper_pg_dir + SWAPPER_DIR_SIZE
dmb sy
bl __inval_cache_range
ret x28
ENDPROC(__create_page_tables)
.ltorg
经过这段汇编,映射就建立完成了,建立映射的意义在于,我们可以执行mmu enable那段code了,他就在proc.S中
待补充:
更新自2020.08.13
目录 | 描述 |
---|---|
./bootable/lk/app/xxx_boot/boot_kernel.c | lk跳转kernel部分处理 |
./bootable/lk/platform/xxx/include/platform/upgrade.h | 相关跳转地址部分定义 |
./bootable/lk/platform/xxx/bootimg.c | img处理相关函数 |
./kernel-4.9/arch/arm64/kernel/head.S | kernel 入口文件 |
./kernel-4.9/arch/arm64/kernel/proc.S | setup_cpu enable mmu部分处理 |
./kernel-4.9/arch/arm64/kernel/vmlinux.lds.S | 链接定义,可以看到各段的VA地址 |
./kernel-4.9/arch/arm64/include/asm/kernel-pgtable.h | page table 相关宏的定义 |
./kernel-4.9/arch/arm64/include/asm/pgtable-hwdef.h | page table HW相关宏定义 |
./kernel-4.9/Documentation/arm64/memory.txt | 内存相关结构示意 |
./out/target/product/xxx/obj/KERNEL_OBJ/System.map | kernel各个函数的内存地址 |
./out/target/product/xxx/obj/KERNEL_OBJ/vmlinux | kernel带符号的文件 |
/*
* Macro to create a table entry to the next page.
*
* tbl: page table address
* virt: virtual address
* shift: #imm page table shift
* ptrs: #imm pointers per table page
*
* Preserves: virt
* Corrupts: tmp1, tmp2
* Returns: tbl -> next level table page address
*/
.macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
lsr \tmp1, \virt, #\shift
and \tmp1, \tmp1, #\ptrs - 1 // table index
add \tmp2, \tbl, #PAGE_SIZE
orr \tmp2, \tmp2, #PMD_TYPE_TABLE // address of next table and entry type
str \tmp2, [\tbl, \tmp1, lsl #3]
add \tbl, \tbl, #PAGE_SIZE // next level table page
.endm
create_pgd_entry code中有描述,即创建各个level的translate page,实质通过create_table_entry创建page
/*
* Macro to populate the PGD (and possibily PUD) for the corresponding
* block entry in the next level (tbl) for the given virtual address.
*
* Preserves: tbl, next, virt
* Corrupts: tmp1, tmp2
*/
.macro create_pgd_entry, tbl, virt, tmp1, tmp2
create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2
#if SWAPPER_PGTABLE_LEVELS > 3
create_table_entry \tbl, \virt, PUD_SHIFT, PTRS_PER_PUD, \tmp1, \tmp2
#endif
#if SWAPPER_PGTABLE_LEVELS > 2
create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2
#endif
.endm
映射实际block region对应的物理地址;
/*
* Macro to populate block entries in the page table for the start..end
* virtual range (inclusive).
*
* Preserves: tbl, flags
* Corrupts: phys, start, end, pstate
*/
.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 // table index
orr \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT // table entry
lsr \end, \end, #SWAPPER_BLOCK_SHIFT
and \end, \end, #PTRS_PER_PTE - 1 // table end 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