Zircon - Fuchsia 内核分析 - 启动(平台初始化)
简介
Zircon 是 Google 新操作系统 Fuchsia 的内核,基于 LK - Little Kernel 演变而来。而 Little Kernel 前面一直作为 Android 系统的 Bootloader 的核心而存在。Zircon 在此基础上增加了 MMU,System Call 等功能。
Zircon 目前支持 X86/X64 和 ARM 两种 CPU 平台,下面我将以 ARM64 为例,一行行分析 Zircon 内核的早期启动过程,看一下 Zircon 和 ARM64 是如何完成平台初始化的,这部分由汇编实现。
需要事先声明的是,本人平时从事的是 Android 开发,对于 ARM 了解有限,此次源码阅读也会参考一些其他资料,其中难免会有一些错误,望广大读者谅解。
带注释的 Zircon 内核源码(未完成):https://github.com/ganyao114/zircon/tree/doc
ARM64
首先需要简单过一下涉及到的 ARM64 背景,以前虽有简单接触过嵌入式的 ARM,但相比之下 ARM64 确实要复杂很多很多。。。
特权模式/异常等级
在 ARM32 中,我们使用 SVC 等 7 种特权模式来区分 CPU 的工作模式,操作系统等底层程序会运行在高特权模式,而普通用户程序则运行在低特权的用户模式。
而在 ARM64 中,其实也类似,不过在 ARM64 中统一成了 4 个异常等级 EL-0/1/2/3
EL 架构:
- 特权等级 EL3 > EL2 > EL1 > EL0 , EL0 为非特权执行等级
- EL2 无 Secure State,有 None-Secure State。EL3 只有 Secure-State,并且控制 EL0和EL1 在两种模式间切换
- EL0 和 EL1 必须实现,EL2 和 EL3 是可选的
关于 4 个特权等级在系统软件中的实际使用:
EL | 用途 |
---|---|
EL0 | 运行用户程序 |
EL1 | 操作系统内核 |
EL2 | Hypervisor (可以理解为上面跑多个虚拟内核) |
EL3 | Secure Monitor(ARM Trusted Firmware) |
Secure State 的影响:
State | 影响 |
---|---|
Non-Secure | EL0/1/2 只能访问 Non-Secure Memory |
Secure | EL0/1/3, 可以访问Non-secure memory & Secure memory,可起到物理屏障安全隔离作用 |
多核心
在多核心处理器下,ID = 0 的 CPU 内核为 prime 核心,或者被称为 BSP 引导处理器 - bootstrap processor,其他处理器则为 non - prime 核心,或者 AP 核心 - Application Processor,开机和内核初始化由 prime 核心完成,AP 核心只要完成自身的配置就可以了。
ARM MP 架构图:
- CPU 内核间通过核间中断 - IPI 彼此通讯
- 每个 CPU 内核都能看到同样的内存总线和数据,一般 L1 缓存每个内核独享,L2/L3 为所有内核共享
- 所有 CPU 内核共享同一个 I/O 周边与中断控制器,中断控制器会根据配置将中断分发到合适的 CPU 内核
寄存器
仅说明本文所涉及的
寄存器 | ---- | 位宽 | 用途 |
---|---|---|---|
X0 - X30 | 64 | 通用寄存器 | |
X0 - X7 | 64 | 用于传递子程序参数和结果,使用时不需要保存,多余参数采用堆栈传递,64位返回结果采用X0表示,128位返回结果采用X1:X0表示 | |
X8 | 64 | 保存子程序返回地址 | |
X9 - X15 | 64 | 临时寄存器,使用时不需要保存 | |
X16 - X17 | 64 | 子程序内部调用寄存器,使用时不需要保存,尽量不要使用 | |
X18 | 64 | 平台寄存器,用于 ABI 调用 | |
X19 - X28 | 64 | 临时寄存器,使用时必须保存 | |
X29 | 64 | 帧指针寄存器,用于连接栈帧,使用时需要保存 | |
X30(PLR) | 64 | 链接寄存器LR | |
WZR / XZR | 64 | 零寄存器,没什么意义,它不是一个实际存在的寄存器,其实相当于一个 0 常量 | |
MPIDR_EL1 | 64 | 多核标志处理器,我们只要关心其中 AFF0 和 AFF1 位。AFF0 [0 - 7 bit] 位,表示当前 CPU 内核的当前的线程编号,由于直到最新的 A76 都不支持超线程技术,所以这个默认为 0,无需关心;AFF1 [8-10 bit] 当前 CPU 簇中当前 CPU 内核的编号,0 号就是 prime CPU 内核 | |
PSTATE | 程序状态寄存器,PSTATE不是一个寄存器,是保存当前PE状态的一组寄存器统称,其中可访问寄存器有:PSTATE.{NZCV,DAIF,CurrentEL,SPSel},属于ARMv8新增内容,64bit下代替CPSR | ||
CurrentEL | 当前所处的异常等级, 只读 | ||
SCR_EL3 | 32 | Secure 配置寄存器,用于配置上文的 Secure State,这里用到了里面的 3 个 Flag 位,1. SCR_EL3_NS:EL0/EL1 的 Secure State,EL0/EL1 是否能访问 Secure Memory; 2.SCR_EL3_HCE:开关 HVC 指令;3.SCR_EL3_RW:设置 EL1/EL2 使用 AARCH32 还是 AARCH64。 | |
SP_ELX(X = 0-3) | 64 | 对应异常等级下的栈指针寄存器 | |
SPSR_ELX(X = 1-3) | 32 | 程序状态保存寄存器,保存进入 ELX 异常等级时的 PSTATE 状态,用于等级降下来的时候恢复状态,由于不会有异常把系统状态迁移到EL0,因此也就不存在 SPSR_EL0了。 | |
ELR_ELX(X = 1-3) | 64 | 异常链接寄存器,用于保存异常进入ELX的异常地址,在返回异常现场的时候,可以使用 ELR_ELX(x = 1/2/3) 来恢复PC值, 异常迁移到哪一个 exception level 就使用哪一个 ELR 同样的,由于不会有异常把系统状态迁移到EL0,因此也就不存在ELR_EL0了。 | |
VBAR_ELX(X = 0-3) | 64 | 保存任意异常进入 ELX 的跳转向量基地址 | |
HCR_EL2 | 64 | HCR_EL2.{TEG,AMO,IMO,FMO,RW}控制 EL0/EL1 的异常路由 逻辑1允许 | |
VTTBR_EL2 | 64 | 保存了转换表的基地址,负责在 EL2 下进行 EL0 -> EL1 的非安全存储器访问的转换 |
内核代码中的通用范例
有了上文对 ARM64 的简单介绍,我们就可以看懂代码中的一些代码了
以下是比较通用的代码
判断是否是 prime CPU 内核
mrs cpuid, mpidr_el1
ubfx cpuid, cpuid, #0, #15 /* mask Aff0 and Aff1 fields */ //aff0 记录 cpu ID,aff1 记录是否支持超线程
cbnz cpuid, .Lno_save_bootinfo //如果不是 prim 核心(0 号核心),则不需要启动内核,也就不需要准备内核启动参数,直接执行核心初始化工作
前两行取出 mpidr_el1 的 AFF01 放入 cpuid
第三行如果 cpuid = 0 则代表是 prime cpu 内核,并且也是第一个线程,虽然现在超线程没有实现就是了
取标签/数据地址
这里需要解释一下,因为 kernel 在链接的时候是根据虚拟地址来的。而在内核引导的早期阶段,也就是本文所介绍的这个过程中,MMU 是处于关闭状态的,这段时间内核实际是跑在物理地址上的。
那么,这段代码就必须是 PIC 位置无关代码,除了尽量使用寄存器,在不得不访问内存时,这段代码还不能依赖链接器所给的地址,那么如果在这段代码中需要取到内存中的地址只能使用指令计算数据/Label的实际地址。
Zircon 将这一操作简化成了一个宏:
.macro adr_global reg, symbol
//得到包含 symbol 4K 内存页的基地址
adrp \reg, \symbol
//第一个全局变量的地址
add \reg, \reg, #:lo12:\symbol
.endm
第一行得到得到包含 symbol 4K 内存页的基地址
第二行在基地址上加上偏移就是 symbol 的实际地址
判断当前所在的 EL
mrs x9, CurrentEL
cmp x9, #(0b01 << 2)
//不等于 0 时,说明不是在异常级别 1,跳转到 notEL1 代码
bne .notEL1
循环遍历
str xzr, [page_table1, tmp, lsl #3]
add tmp, tmp, #1
cmp tmp, #MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP
bne .Lclear_top_page_table_loop
等价于
for(int tmp = 0;tmp < MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP;tmp++) {page_table1[tmp] = 0;
}
启动过程概述
启动早期,即内核在进入 C++ 世界之前,主要分为以下几步
- 初始化各个 EL1 - EL3 下的异常配置
- 创建启动阶段页表
- 为打开 MMU 做准备
- 打开 MMU
- 配置栈准备进入 C 世界
启动时序与代码
在多核处理器架构中,很多初始化代码仅需要由 prime 处理器完成,其他处理器完成各自的配置即可。
prime 核心 | 其他核心 |
保存内核启动参数 | 跳过 |
初始化 EL1 - EL3 的异常配置 | |
初始化缓存 | |
修复 kernel base 地址 | 跳过 |
检查并等待 .bss 段数据清除 | 跳过 |
创建启动阶段的页表 | 自旋等待页表创建完成 |
打开 MMU 之前的准备工作 | |
打开 MMU (以上代码运行在物理地址,以下代码运行在虚拟地址) | |
重新配置内核栈指针 | 配置其他 CPU 的栈指针 |
跳转到 C 世界继续初始化 | 休眠等待唤醒 |
保存内核启动参数
start.S - _start
mrs cpuid, mpidr_el1 //aff0 记录 cpu ID,aff1 记录是否支持超线程cbnz cpuid, .Lno_save_bootinfo //如果不是 prim 核心(0 号核心),则不需要启动内核,也就不需要准备内核启动参数,直接执行核心初始化工作/* save x0 in zbi_paddr *///prim 核心走这里,准备并保存内核启动参数//计算 zbi_paddr 段中数据的地址,保存在 x0 中,下同adrp tmp, zbi_paddrstr x0, [tmp, #:lo12:zbi_paddr]/* save entry point physical address in kernel_entry_paddr */adrp tmp, kernel_entry_paddradr tmp2, _startstr tmp2, [tmp, #:lo12:kernel_entry_paddr]adrp tmp2, arch_boot_elmrs x2, CurrentELstr x2, [tmp2, #:lo12:arch_boot_el]//总之,x0 - x4 现在保存了核心初始化需要的参数,为跳转到 C 世界作准备。
初始化 EL1 - EL3
asm.S - arm64_elX_to_el1
对各个 EL 的配置,需要 CPU 在对应的 EL 状态下才能配置
EL1
EL1 不需要配置直接返回
//读取现在的异常级别mrs x9, CurrentEL cmp x9, #(0b01 << 2)//不等于 0 时,说明不是在异常级别 1,跳转到 notEL1 代码bne .notEL1 /* Already in EL1 *///EL1 直接返回ret
EL2
EL2 状态下主要配置了:
- 配置 EL2 的异常向量表
- 配置时钟
- 清除 EL2 的转换表寄存器
- 配置 SPSR 和 ELR 寄存器,这两个看上面的寄存器介绍
实际上 EL2 在 Zircon 中还没有具体用处,所以此处初始化基本上就是设一些空值。
/* Setup the init vector table for EL2. *///计算EL2的异常向量表的基地址adr_global x9, arm64_el2_init_table//设定EL2的异常向量表的基地址msr vbar_el2, x9/* Ensure EL1 timers are properly configured, disable EL2 trapping ofEL1 access to timer control registers. Also clear virtual offset.*///检查并配置时钟mrs x9, cnthctl_el2orr x9, x9, #3msr cnthctl_el2, x9msr cntvoff_el2, xzr/* clear out stage 2 translations *///清除 vttbr_el2 寄存器,vttbr_el2 保存了转换表的基地址,负责在 EL2 下进行 EL0 -> EL1 的非安全存储器访问的转换msr vttbr_el2, xzr //http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100403_0200_00_en/lau1457340777806.html//当系统发生了异常并进入EL2,SPSR_EL2,Saved Program Status Register (EL2)会保存处理器状态,ELR_EL2,Exception Link Register (EL2)会保存返回发生exception的现场的返回地址。//这里是设定SPSR_EL2和ELR_EL2的初始值。adr x9, .Ltargetmsr elr_el2, x9//ELR 定义看上面mov x9, #((0b1111 << 6) | (0b0101)) /* EL1h runlevel */msr spsr_el2, x9
EL3
EL3 状态的主要任务就是配置 EL0/EL1 的 Secure State/HVC/运行指令集,其他的也是上面 EL2 一样付空值。
- 设置 EL0/EL1 为 non-Secure State
- 开启 HVC 指令
- 使用 AARCH64 指令
cmp x9, #(0b10 << 2)//当前为异常级别 2,跳转到 inEL2beq .inEL2//当不在 EL2 状态时,则为 EL3/* set EL2 to 64bit and enable HVC instruction *///scr_el3 控制EL0/EL1/EL2的异常路由 逻辑1允许//http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100403_0200_00_en/lau1457340777806.html//若SCR_EL3.RW == 1,则决定 EL2/EL1 是使用AArch64,否则AArch32mrs x9, scr_el3//打开 EL0/EL1 的非安全状态,EL0/EL1 无法访问安全内存 orr x9, x9, #SCR_EL3_NS//开启 HVC 指令//关于 HVC,看 http://www.wowotech.net/armv8a_arch/238.htmlorr x9, x9, #SCR_EL3_HCE//设置 SCR_EL3.RW == 1,EL2/EL1 是使用AArch64orr x9, x9, #SCR_EL3_RW msr scr_el3, x9//ELR 寄存器 Exception Link Register,用于保存异常进入ELX的异常地址,在返回异常现场的时候,可以使用 ELR_ELX(x = 1/2/3) 来恢复PC值, 异常迁移到哪一个exception level就使用哪一个ELR//同样的,由于不会有异常把系统状态迁移到EL0,因此也就不存在ELR_EL0了。adr x9, .Ltarget//这里异常进入地址为 Ltargetmsr elr_el3, x9//设定 spsr_el3mov x9, #((0b1111 << 6) | (0b0101)) /* EL1h runlevel */msr spsr_el3, x9//配置 EL1 并准备进入 EL1 *b .confEL1
从 EL3 返回 EL1
/* disable EL2 coprocessor traps */mov x9, #0x33ffmsr cptr_el2, x9/* set EL1 to 64bit *///设置 EL1 的异常处理为 AARCH64 指令,同上mov x9, #HCR_EL2_RWmsr hcr_el2, x9/* disable EL1 FPU traps */mov x9, #(0b11<<20)msr cpacr_el1, x9/* set up the EL1 bounce interrupt *///配置 EL1 栈指针mov x9, sp msr sp_el1, x9isb//模拟异常返回,执行该指令会使得CPU返回EL1状态eret
初始化缓存
//使缓存失效 *bl arch_invalidate_cache_all/* enable caches so atomics and spinlocks work *///启用缓存,使原子操作和自旋锁生效mrs tmp, sctlr_el1//打开指令缓存orr tmp, tmp, #(1<<12) /* Enable icache *///打开数据缓存orr tmp, tmp, #(1<<2) /* Enable dcache/ucache */msr sctlr_el1, tmp
修复重定向的 Kernel Base 地址
此工作由 prime cpu 完成,其他 cpu 开始进入自旋等待
//加载 kernel_relocated_base 段地址//内核重定向的基地址,即内核开始的虚拟地址adr_global tmp, kernel_relocated_base//负值给 kernel_vaddrldr kernel_vaddr, [tmp]// Load the base of the translation tables.//貌似 Zircon 中 1GB 物理内存由一个 translation_table 维护,所以这里 tt_trampoline 相当于一级页表?adr_global page_table0, tt_trampoline//虚拟地址内存页地址转换表adr_global page_table1, arm64_kernel_translation_table// Send secondary cpus over to a waiting spot for the primary to finish.//如果不是 prim CPU 内核,则跳转到 Lmmu_enable_secondary 后等待 prim 内核运行完下面代码cbnz cpuid, .Lmmu_enable_secondary//下面的代码只有 prim CPU 内核执行// The fixup code appears right after the kernel image (at __data_end in// our view). Note this code overlaps with the kernel's bss! It// expects x0 to contain the actual runtime address of __code_start.//将内核代码开始的虚拟地址保存到 x0 中mov x0, kernel_vaddr//跳转到 __data_end *//__data_end 指向 image.S - apply_fixups 方法bl __data_end
FUNCTION(apply_fixups)// This is the constant address the kernel was linked for.movlit x9, KERNEL_BASEsub x0, x0, x9// The generated kernel-fixups.inc invokes this macro for each run of fixups.
.macro fixup addr, n, strideadr x9, FIXUP_LOCATION(\addr)
.if \n >= 4 && \stride == 8// Do a loop handling adjacent pairs.mov x16, #(\n / 2)
0: fixup_pairsubs x16, x16, #1b.ne 0b.if \n % 2// Handle the odd remainder after those pairs.fixup_single 8.endif
.elseif \n >= 2 && \stride == 8// Do a single adjacent pair.fixup_pair.if \n == 3// Do the third adjacent one.fixup_single 8.endif
.elseif \n > 1// Do a strided loop.mov x16, #\n
0: fixup_single \stridesubs x16, x16, #1b.ne 0b
.else// Do a singleton.fixup_single 8
.endif
.endm.macro fixup_pairldp x10, x11, [x9]add x10, x10, x0add x11, x11, x0stp x10, x11, [x9], #16
.endm.macro fixup_single strideldr x10, [x9]add x10, x10, x0str x10, [x9], #\stride
.endm#include "kernel-fixups.inc"retDATA(apply_fixups_end)
END_FUNCTION(apply_fixups)
检查等待清除 .bss 段
//检查内核 bss 段是否被清除,猜测是因为前面 bss 段所在内存已经被操作过
.Ldo_bss://见 kernel.ld//计算保存内核 .bss 段开始地址adr_global tmp, __bss_start//计算保存内核 .bss 段结束地址adr_global tmp2, _end//计算 .bss 段大小sub tmp2, tmp2, tmp//.bss 段大小为 0 则跳转 Lbss_loop_donecbz tmp2, .Lbss_loop_done//不为 0 则循环等待
.Lbss_loop:sub tmp2, tmp2, #16stp xzr, xzr, [tmp], #16cbnz tmp2, .Lbss_loop
.Lbss_loop_done:
创建启动阶段的页表
首先要把也表中的内存清除:
//清除内核虚地址转换表
.Lclear_top_page_table_loop://遍历转换表中的所有条目并设置 0/**等价于for(int tmp = 0;tmp < MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP;tmp++) {page_table1[tmp] = 0;}关于 xzr 寄存器 https://community.arm.com/processors/f/discussions/3185/wzr-xzr-register-s-purpose**/str xzr, [page_table1, tmp, lsl #3]add tmp, tmp, #1cmp tmp, #MMU_KERNEL_PAGE_TABLE_ENTRIES_TOPbne .Lclear_top_page_table_loop
在初始化阶段,需要映射三段地址:
- 第一段是identity mapping,其实就是把物理地址mapping到物理地址上去,在打开MMU的时候需要这样的mapping(ARM ARCH强烈推荐这么做的)
- 第二段是kernel image mapping,内核代码欢快的执行当然需要将kernel running需要的地址(kernel txt、dernel rodata、data、bss等等)进行映射了
- 第三段是blob memory对应的mapping
因为页表映射调用到了 C 方法,所以需要提前为 CPU 配置 SP 指针:
.Lbss_loop_done:/* set up a functional stack pointer *///设定内核栈地址,准备调用 C 代码adr_global tmp, boot_cpu_kstack_endmov sp, tmp/* make sure the boot allocator is given a chance to figure out where* we are loaded in physical memory. */bl boot_alloc_init/* save the physical address the kernel is loaded at *///保存内核开始地址到 kernel_base_phys 全局变量adr_global x0, __code_startadr_global x1, kernel_base_physstr x0, [x1]/* set up the mmu according to mmu_initial_mappings *//* clear out the kernel translation table */mov tmp, #0
映射物理内存
//准备调用 C 函数 arm64_boot_map//1.该函数任务是帮内核映射物理内存//先准备 5 个参数 x0-x4 寄存器保存函数参数/* void arm64_boot_map(pte_t* kernel_table0, vaddr_t vaddr, paddr_t paddr, size_t len, pte_t flags); *//* map a large run of physical memory at the base of the kernel's address space */mov x0, page_table1mov x1, KERNEL_ASPACE_BASEmov x2, 0mov x3, ARCH_PHYSMAP_SIZEmovlit x4, MMU_PTE_KERNEL_DATA_FLAGS//调用 arm64_boot_map *bl arm64_boot_map
映射内核运行内存
//2.映射内核的地址/* map the kernel to a fixed address *//* note: mapping the kernel here with full rwx, this will get locked down later in vm initialization; */mov x0, page_table1mov x1, kernel_vaddradr_global x2, __code_startadr_global x3, _endsub x3, x3, x2mov x4, MMU_PTE_KERNEL_RWX_FLAGSbl arm64_boot_map
通知页表配置完毕
//标记页表已经设置完毕,通知其他 CPU 内核可以继续往下跑了adr_global tmp, page_tables_not_readystr wzr, [tmp]//prime CPU 内核跳入 Lpage_tables_readyb .Lpage_tables_ready
准备打开 MMU
打开 MMU 之前需要做一些配置
清理垃圾数据
需要重置一下 MMU 和 Cache 的状态以清除里面的残余数据,在进入 Kernel 代码之前,Bootloader 可能使用过 MMU 和 Cache,所以 ICache 和 TLB 中可能还有前面留下来的残余垃圾数据。
//使 TLB 失效以清除数据/* Invalidate TLB */tlbi vmalle1is
初始化 Memory attributes 配置
Memory attributes 简单来说就是将 Memory 加上了几种属性,每种属性都会影响 Memory 的读写策略。
因为 Memory 读写策略是非常复杂的,比如一段内存区域指向的是一个 FIFO 设备,对内存的读写有严格的时序要求,则需要配置 Memory attributes 来禁止 CPU 读写重排,Cache 等等优化,因为这些对于这段 Memory 没有意义,还会影响数据的读写的正确性。
movlit tmp, MMU_MAIR_VALmsr mair_el1, tmp/* Initialize TCR_EL1 *//* set cacheable attributes on translation walk *//* (SMP extensions) non-shareable, inner write-back write-allocate */movlit tmp, MMU_TCR_FLAGS_IDENTmsr tcr_el1, tmp
看一下 Zircon 的默认 Memory Attribute 配置:
/* Default configuration for main kernel page table:* - do cached translation walks*//* Device-nGnRnE memory */
#define MMU_MAIR_ATTR0 MMU_MAIR_ATTR(0, 0x00)
#define MMU_PTE_ATTR_STRONGLY_ORDERED MMU_PTE_ATTR_ATTR_INDEX(0)/* Device-nGnRE memory */
#define MMU_MAIR_ATTR1 MMU_MAIR_ATTR(1, 0x04)
#define MMU_PTE_ATTR_DEVICE MMU_PTE_ATTR_ATTR_INDEX(1)/* Normal Memory, Outer Write-back non-transient Read/Write allocate,* Inner Write-back non-transient Read/Write allocate*/
#define MMU_MAIR_ATTR2 MMU_MAIR_ATTR(2, 0xff)
#define MMU_PTE_ATTR_NORMAL_MEMORY MMU_PTE_ATTR_ATTR_INDEX(2)/* Normal Memory, Inner/Outer uncached, Write Combined */
#define MMU_MAIR_ATTR3 MMU_MAIR_ATTR(3, 0x44)
#define MMU_PTE_ATTR_NORMAL_UNCACHED MMU_PTE_ATTR_ATTR_INDEX(3)#define MMU_MAIR_ATTR4 (0)
#define MMU_MAIR_ATTR5 (0)
#define MMU_MAIR_ATTR6 (0)
#define MMU_MAIR_ATTR7 (0)#define MMU_MAIR_VAL (MMU_MAIR_ATTR0 | MMU_MAIR_ATTR1 | \MMU_MAIR_ATTR2 | MMU_MAIR_ATTR3 | \MMU_MAIR_ATTR4 | MMU_MAIR_ATTR5 | \MMU_MAIR_ATTR6 | MMU_MAIR_ATTR7 )#define MMU_TCR_IPS_DEFAULT MMU_TCR_IPS(2) /* TODO: read at runtime, or configure per platform *//* Enable cached page table walks:* inner/outer (IRGN/ORGN): write-back + write-allocate*/
#define MMU_TCR_FLAGS1 (MMU_TCR_TG1(MMU_TG1(MMU_KERNEL_PAGE_SIZE_SHIFT)) | \MMU_TCR_SH1(MMU_SH_INNER_SHAREABLE) | \MMU_TCR_ORGN1(MMU_RGN_WRITE_BACK_ALLOCATE) | \MMU_TCR_IRGN1(MMU_RGN_WRITE_BACK_ALLOCATE) | \MMU_TCR_T1SZ(64 - MMU_KERNEL_SIZE_SHIFT))
#define MMU_TCR_FLAGS0 (MMU_TCR_TG0(MMU_TG0(MMU_USER_PAGE_SIZE_SHIFT)) | \MMU_TCR_SH0(MMU_SH_INNER_SHAREABLE) | \MMU_TCR_ORGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \MMU_TCR_IRGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \MMU_TCR_T0SZ(64 - MMU_USER_SIZE_SHIFT))
#define MMU_TCR_FLAGS0_IDENT \(MMU_TCR_TG0(MMU_TG0(MMU_IDENT_PAGE_SIZE_SHIFT)) | \MMU_TCR_SH0(MMU_SH_INNER_SHAREABLE) | \MMU_TCR_ORGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \MMU_TCR_IRGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \MMU_TCR_T0SZ(64 - MMU_IDENT_SIZE_SHIFT))
#define MMU_TCR_FLAGS_IDENT (MMU_TCR_IPS_DEFAULT | MMU_TCR_FLAGS1 | MMU_TCR_FLAGS0_IDENT)
打开 MMU
这里打开 MMU 非常简单,打开之前,以上代码都在物理地址下运行,打开之后则在虚拟地址下运行了。
//内存栅栏 isb//保存 EL1 状态的异常向量表/* Read SCTLR */mrs tmp, sctlr_el1//打开 MMU/* Turn on the MMU */orr tmp, tmp, #0x1//恢复 EL1 状态的异常向量表/* Write back SCTLR */msr sctlr_el1, tmp
这里注意备份还原异常向量表
内存栅栏防止打开 MMU 前后代码乱序执行,造成逻辑错误
准备跳入 C 世界
重新设置栈指针
在此之前,重新设置栈指针,因为此时已经变成虚拟地址
//重新设置 prime CPU 的内核栈指针,因为现在 MMU 已经打开,需要使用虚拟地址// set up the boot stack for realadr_global tmp, boot_cpu_kstack_endmov sp, tmp
设置栈溢出异常
配置 Stack Guard,其实就是在栈末尾设置一个页中断,如果程序读写到这里,代表栈溢出,触发异常。
防止编译期间没有启用栈保护
//配置 Stack Guard,其实就是在栈末尾设置一个页中断,如果程序读写到这里,代表栈溢出,触发异常adr_global tmp, boot_cpu_fake_thread_pointer_locationmsr tpidr_el1, tmp// set the per cpu pointer for cpu 0adr_global x18, arm64_percpu_array// Choose a good (ideally random) stack-guard value as early as possible.bl choose_stack_guardmrs tmp, tpidr_el1str x0, [tmp, #ZX_TLS_STACK_GUARD_OFFSET]// Don't leak the value to other code.mov x0, xzr
其他 CPU 设置栈,初始化, 并且进入休眠
.Lsecondary_boot://配置其他 CPU 内核的栈指针bl arm64_get_secondary_spcbz x0, .Lunsupported_cpu_trapmov sp, x0msr tpidr_el1, x1bl arm64_secondary_entry.Lunsupported_cpu_trap://其他 CPU 内核初始化完毕wfeb .Lunsupported_cpu_trap
prime 内核进入 C 世界
//跳转到内核 C 代码入口bl lk_mainb
关于内核初始化后期
这部分大部分在 C/C++ 中完成,下一篇分析。
下一篇:https://blog.csdn.net/ganyao939543405/article/details/86220466
Zircon - Fuchsia 内核分析 - 启动(平台初始化)相关推荐
- kali2020.3 vm版本内核是多少_Zircon Fuchsia 内核分析 启动(内核初始化)
相关阅读: Zircon - Fuchsia 内核分析 - 启动(平台初始化) 简介 前面已经介绍了 Zircon 内核启动的汇编代码部分,主要是一些 CPU 的初始化. 现在 prime CPU 已 ...
- linux内核调用( )为进程创建虚存区_Linux内核分析-总结篇(九)
本次内容作为Linux内核的总结内容,主要涉及对Linux系统的总体的一些理解,同时将之前的一些总结贴出来作为大家的一个索引,希望笔者一样的菜鸟有一些帮助和入门的作用.从一个初学者的角度对Linux有 ...
- 互联网直播点播平台EasyDSS v3.2.4内核无法启动的原因分析及解决办法
EasyDSS视频直播点播平台集视频直播.点播.转码.管理.录像.检索.时移回看等功能于一体,可提供音视频采集.视频推拉流.播放H.265编码视频.存储.分发等视频能力服务,在应用场景上,平台可以运用 ...
- Fuchsia X86 kernel启动代码分析
Google整Fuchsia代码整了好些年了,近期是有看到说Fuchsia可能会正式商用了,所以抽了空把Fuchsia代码下了下来,想从kernel起好好捋一捋代码,想从根本上理解其kernel部分的 ...
- Linux内核分析实验3——分析linux内核启动过程
本文大量内容引用自孟宁老师在<LINUX操作系统分析>课程中的内容 <Linux内核分析>MOOC课程 http://www.xuetangx.com/courses/cour ...
- 通过gdb调试分析Linux内核的启动过程
作者:吴乐 山东师范大学 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验流程 1.打开环境 执 ...
- 实验三:跟踪分析Linux内核的启动过程
Ubuntu 16.04下搭建MenuOS的过程: 1.下载内核源代码编译内核 1 # 下载内核源代码编译内核2 cd ~/LinuxKernel/3 wget https://www.kernel. ...
- 实验三:跟踪分析Linux内核的启动过程 ----- 20135108 李泽源
实验要求: 使用gdb跟踪调试内核从start_kernel到init进程启动 详细分析从start_kernel到init进程启动的过程并结合实验截图撰写一篇署名博客,并在博客文章中注明" ...
- linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程
贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...
最新文章
- 何晓冬:做科研与其各拿十块“铜牌”,不如合力做一块“金牌”
- 用prop还是attr
- GCC 中文手册 - 摘自纯C论坛
- Java IO ---学习笔记(数据流)
- 如何修改服务器上的端口号,如何修改远程服务器端口号
- 转-Android仿微信气泡聊天界面设计
- python图像压缩主成分分析实例_python机器学习API介绍13: 数据降维及主成分分析...
- python是什么类型的编程语言-python是一种什么类型的语言
- Java多线程之JUC包:CountDownLatch源码学习笔记
- 【PID优化】基于matlab遗传算法PID控制器优化设计【含Matlab源码 1144期】
- 项目经理要具备的三种能力
- 感性电路电流计算_三相交流电怎么计算电功率?三相交流电功率计算公式
- 故障树最小割集程序化设计方案
- 再来学习一下RT-Thread的软件架构 | 文末赠书5本《软件架构实践》
- windows下服务或SYSTEM权限读取当前用户注册表HKEY_CURRENT_USER
- 团队问卷调查结果报告
- 读《当众讲话诀窍》-殷亚敏 (2)
- Git分支 查看branch 创建 切换checkout 合并merge(先切回主分支) 删除branch -d 推送push
- 面试题:进程间通信的方式
- [ZT]把IM做成避风塘
热门文章
- 惠州电子计算机职业学校,惠州市十大中专学校排名
- 螃蟹保存方法保存时间_活螃蟹如何保存才能活得时间久(这几个方法简单实用)...
- 游戏建模的工具主要有哪些?
- 股票入门:股票交易手续费用怎么算(转)
- 天齐锂业通过聆讯:将实现“A+H”两地上市,募资主要用来还债
- 揭秘可变剪切研究的本质
- 计算机管理上移动硬盘显示其他设备,移动硬盘坏了插上之后电脑会显示有新设备接入而且设备运转正常,但我 爱问知识人...
- 小米 Redmi G Pro 游戏本锐龙版 评测
- 3D人体姿态估评估指标
- /deep/ 在谷歌浏览器89+版本失效问题解决