内存管理之映射第一步:idmap & swapper
导读
衔接前文的概览篇,本文开始介绍完整的映射过程:
- lk 跳转到kernel image
- idmap与swapper的映射,提供mmu enable之后kernel image空间的访问需求;
本部分处理过程在汇编code中,主要介绍流程;
1. lk 跳转
仅关注跳转的最后一个步骤:
- 从emmc中load boot.img中kernel到0x800080000(物理地址);
- 定义一个指针变量theKernel,让其指向KERNEL_LOAD_ADDR_PHYS地址,即调用theKernel时,执行KERNEL_LOAD_ADDR_PHYS地址的内容;
- 调用theKernel,并传参数进去:FDT_LOAD_ADDR_PHYS;
/* 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;
1.1 一个小疑问
我们查看kernel中各个段的地址,一般通过如下两种方式:
- System.map 查看地址
- objdump -S vmlinux > vm.S 查看地址
- vmlinux.lds.S 查看SECTIONS地址
通过上述三种方式查看到的head.text地址均为0xFFFF FFF8 0808 0000,与lk跳转地址有128M的偏移;
这里理解错误的点在于:这里看到的地址为kernel 映射完成后的虚拟地址,而lk跳转的时候,是执行的物理地址(ps,在lk的虚拟地址为0xFFFF FFF8 0008 0000)
所以其实不是一回事,只要记住lk跳转时为物理地址即可,与后续的虚拟地址无关!!!
2. Kernel 入口
入口在head.S的ENTRY(stext) 这里:
ENTRY(stext)
bl preserve_boot_args
bl el2_setup // Drop to EL1, w0=cpu_boot_mode