鸿蒙内核源码分析 (内核启动篇) | 从汇编到 main ()

这应该是系列篇最难写的一篇,全是汇编代码,需大量的底层知识,涉及协处理器,内核镜像重定位,创建内核映射表,初始化 CPU 模式栈,热启动,到最后熟悉的 main() 。

内核入口

在链接文件 liteos.ld 中可知内核的入口地址为 ENTRY(reset_vector) , 分别出现在reset_vector_mp.S (多核启动) 和 reset_vector_up.S(单核启动),系列篇研究多核启动的情况。代码可结合 (协处理器篇) 看更容易懂。

reset_vector: //鸿蒙开机代码
    /* clear register TPIDRPRW */
    mov     r0, #0					//r0 = 0
    mcr     p15, 0, r0, c13, c0, 4	//复位线程标识符寄存器TPIDRPRW , 不复位将导致系统不能启动
    /* do some early cpu setup: i/d cache disable, mmu disabled */
    mrc     p15, 0, r0, c1, c0, 0	//System Control Register-SCTLR | 读取系统控制寄存器内容
    bic     r0, #(1<<12)			//禁用指令缓存功能
    bic     r0, #(1<<2 | 1<<0)		//禁用数据和TLB的缓存功能(bit2) | mmu功能(bit0)
    mcr     p15, 0, r0, c1, c0, 0	//写系统控制寄存器

    /* enable fpu+neon 一些系统寄存器的操作
    | 使能浮点运算(floating point unit)和 NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,
    NEON结合了64-bit和128-bit的SIMD指令集,提供128-bit宽的向量运算(vector operations)*/
#ifndef LOSCFG_TEE_ENABLE        //Trusted Execution Environment   可信执行环境
    MRC    p15, 0, r0, c1, c1, 2 //非安全模式访问寄存器 (Non-Secure Access Control Register - NSACR)
    ORR    r0, r0, #0xC00        //使能安全和非安全访问协处理器10和11(Coprocessor 10和11)
    BIC    r0, r0, #0xC000       //设置bit15为0,不会影响修改CPACR.ASEDIS寄存器位(控制Advanced SIMD功能)| bit14 reserved
    MCR    p15, 0, r0, c1, c1, 2

    LDR    r0, =(0xF << 20)      //允许在EL0和EL1下,访问协处理器10和11(控制Floating-point和Advanced SIMD特性)
    MCR    p15, 0, r0, c1, c0, 2
    ISB
#endif
    MOV    r3, #0x40000000	    //EN, bit[30] 设置FPEXC的EN位来使能FPU
    VMSR   FPEXC, r3			//浮点异常控制寄存器 (Floating-Point Exception Control register | B4.1.57) 

    /* r11: delta of physical address and virtual address | 计算虚拟地址和物理地址之间的差值,目的是为了建立映射关系表 */
    adr     r11, pa_va_offset //获取pa_va_offset变量物理地址,由于这时候mmu已经被关闭,所以这个值就表示pa_va_offset变量的物理地址。
                              /*adr 是一条小范围的地址读取伪指令,它将基于PC的相对偏移的地址值读到目标寄存器中。
                               *编译源程序时,汇编器首先计算当前PC值(当前指令位置)到exper的距离,然后用一条ADD或者SUB指令替换这条伪指令,
                               *例如:ADD register,PC,#offset_to_exper 注意,标号exper与指令必须在同一代码段
                               */
    ldr     r0, [r11]		  //r0 = *r11 获取pa_va_offset变量虚拟地址
    sub     r11, r11, r0	  //物理地址-虚拟地址 = 映射偏移量 放入r11

    mrc     p15, 0, r12, c0, c0, 5      /* Multiprocessor Affinity Register-MPIDR */
    and     r12, r12, #MPIDR_CPUID_MASK //掩码过滤
    cmp     r12, #0	                    //主控核0判断
    bne     secondary_cpu_init	        //初始化CPU次核
	/*
	 * adr是小范围的地址读取伪指令,它将基于PC寄存器相对偏移的地址值读取到寄存器中,
	 * 例如: 0x00000004 	 : adr     r4, __exception_handlers
	 * 则此时PC寄存器的值为: 0x00000004 + 8(在三级流水线时,PC和执行地址相差8),
     * adr指令和标识__exception_handlers的地址相对固定,二者偏移量若为offset,
	 * 最后r4 = (0x00000004 + 8) + offset
	*/

    /* if we need to relocate to proper location or not | 如果需要重新安装到合适的位置*/
    adr     r4, __exception_handlers            /* r4: base of load address | 加载基址*/
    ldr     r5, =SYS_MEM_BASE                   /* r5: base of physical address | 物理基址*/
    subs    r12, r4, r5                         /* r12: delta of load address and physical address | 二者偏移量*/
    beq     reloc_img_to_bottom_done            /* if we load image at the bottom of physical address | 不相等就需要重定位 */

    /* we need to relocate image at the bottom of physical address | 需要知道拷贝的大小*/
    ldr     r7, =__exception_handlers           /* r7: base of linked address (or vm address) | 链接地址基地址*/
    ldr     r6, =__bss_start                    /* r6: end of linked address (or vm address),由于目前阶段有用的数据是中断向量表+代码段+只读数据段+数据段,
											       所以只需复制[__exception_handlers,__bss_start]这段数据到内存基址处 */
    sub     r6, r7                              /* r6: delta of linked address (or vm address) | 内核镜像大小 */
    add     r6, r4                              /* r6: end of load address | 说明需拷贝[ r4,r4+r6 ] 区间内容到 [ r5,r5+r6 ]*/

reloc_img_to_bottom_loop://重定位镜像到内核物理内存基地址,将内核从加载地址拷贝到内存基址处
    ldr     r7, [r4], #4	// 类似C语言 *r5 = *r4 , r4++ , r5++ 
    str     r7, [r5], #4	// #4 代表32位的指令长度,此时在拷贝内核代码区内容
    cmp     r4, r6          /* 拷贝完成条件. r4++ 直到等于r6 (加载结束地址) 完成拷贝动作 */
    bne     reloc_img_to_bottom_loop
    sub     pc, r12                             /* 重新校准pc寄存器, 无缝跳到了拷贝后的指令地址处执行 r12是重定位镜像前内核加载基地址和内核物理内存基地址的差值 */
    nop		// 注意执行完成sub       pc, r12后,新的PC寄存器也指向了 	nop ,nop是伪汇编指令,等同于 mov r0 r0 通常用于控制时序的目的,强制内存对齐,防止流水线灾难,占据分支指令延迟						
    sub     r11, r11, r12                       /* r11: eventual address offset | 最终地址映射偏移量, 用于构建MMU页表 */
//内核总大小 __bss_start - __exception_handlers
reloc_img_to_bottom_done:
#ifdef LOSCFG_KERNEL_MMU 
    ldr     r4, =g_firstPageTable               /* r4: physical address of translation table and clear it
												   内核页表是用数组g_firstPageTable存储 见于los_arch_mmu.c */
    add     r4, r4, r11                         //计算g_firstPageTable页表物理地址
    mov     r0, r4								//因为默认r0 将作为memset_optimized的第一个参数
    mov     r1, #0								//第二个参数,清0
    mov     r2, #MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS //第三个参数是L1表的长度
    bl      memset_optimized                    /* optimized memset since r0 is 64-byte aligned | 将内核页表空间清零*/

    ldr     r5, =g_archMmuInitMapping	        //记录映射关系表
    add     r5, r5, r11                         //获取g_archMmuInitMapping的物理地址
init_mmu_loop:	                                //初始化内核页表
    ldmia   r5!, {r6-r10}                       /* r6 = phys, r7 = virt, r8 = size, r9 = mmu_flags, r10 = name | 传参: 物理地址、虚拟地址、映射大小、映射属性、名称*/
    cmp     r8, 0                               /* if size = 0, the mmu init done | 完成条件 */
    beq     init_mmu_done		                //标志寄存器中Z标志位等于零时跳转到 	init_mmu_done处执行
    bl      page_table_build	                //创建页表
    b       init_mmu_loop						//循环继续
init_mmu_done:
    orr     r8, r4, #MMU_TTBRx_FLAGS            /* r8 = r4 and set cacheable attributes on translation walk | 设置缓存*/
    ldr     r4, =g_mmuJumpPageTable             /* r4: jump pagetable vaddr | 页表虚拟地址*/
    add     r4, r4, r11				
    ldr     r4, [r4]
    add     r4, r4, r11                         /* r4: jump pagetable paddr | 页表物理地址*/

    /* build 1M section mapping, in order to jump va during turing on mmu:pa == pa, va == pa */
    /* 从当前PC开始建立1MB空间的段映射,分别建立物理地址和虚拟地址方式的段映射页表项
     * 内核临时页表在系统 使能mmu -> 切换到虚拟地址运行 这段时间使用
     */
    mov     r6, pc
    mov     r7, r6                              /* r7: pa (MB aligned)*/
    lsr     r6, r6, #20                         /* r6: pa l1 index */
    ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
    add     r12, r10, r6, lsl #20               /* r12: pa |flags */
    str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[paIndex] = pt entry */
    rsb     r7, r11, r6, lsl #20                /* r7: va */
    str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[vaIndex] = pt entry */

    bl      mmu_setup                           /* set up the mmu | 内核映射表已经创建好了,此时可以启动MMU工作了*/
#endif
    /* clear out the interrupt and exception stack and set magic num to check the overflow 
    |exc_stack|地址高位
    |svc_stack|地址低位
	清除中断和异常堆栈并设置magic num检查溢出 */
    ldr     r0, =__svc_stack	    //stack_init的第一个参数 __svc_stack表示栈顶
    ldr     r1, =__exc_stack_top	//stack_init的第二个参数 __exc_stack_top表示栈底, 这里会有点绕, top表高地址位
    bl      stack_init              //初始化各个cpu不同模式下的栈空间
	//设置各个栈顶魔法数字
    STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD //中断栈底设成"烫烫烫烫烫烫"
    STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD     //异常栈底设成"烫烫烫烫烫烫"

warm_reset: //热启动 Warm Reset, warm reboot, soft reboot, 在不关闭电源的情况,由软件控制重启计算机
    /* initialize CPSR (machine state register) */
    mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE) /* 禁止IRQ中断 | 禁止FIQ中断 | 管理模式-操作系统使用的保护模式 */
    msr    cpsr, r0	//设置CPSR寄存器

    /* Note: some functions in LIBGCC1 will cause a "restore from SPSR"!! */
    msr    spsr, r0 //设置SPSR寄存器

    /* get cpuid and keep it in r12 */
    mrc     p15, 0, r12, c0, c0, 5		//R12保存CPUID 
    and     r12, r12, #MPIDR_CPUID_MASK //掩码操作获取当前cpu id

    /* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack | 设置 SVC栈 */
    ldr    r0, =__svc_stack_top //注意这是栈底,高地址位
    mov    r2, #OS_EXC_SVC_STACK_SIZE //栈大小
    mul    r2, r2, r12 
    sub    r0, r0, r2                   /* 算出当前core的中断栈栈顶位置,写入所属core的sp */
    mov    sp, r0

    LDR    r0, =__exception_handlers    
    MCR    p15, 0, r0, c12, c0, 0       /* Vector Base Address Register - VBAR */

    cmp    r12, #0						//CPU是否为主核
    bne    cpu_start                    //不相等就跳到从核处理分支

clear_bss:	                            //主核处理.bss段清零
    ldr    r0, =__bss_start
    ldr    r2, =__bss_end
    mov    r1, #0
    sub    r2, r2, r0
    bl     memset
#if defined(LOSCFG_CC_STACKPROTECTOR_ALL) || \
    defined(LOSCFG_CC_STACKPROTECTOR_STRONG) || \
    defined(LOSCFG_CC_STACKPROTECTOR)
    bl     __stack_chk_guard_setup
#endif

#ifdef LOSCFG_GDB_DEBUG
    /* GDB_START - generate a compiled_breadk,This function will get GDB stubs started, with a proper environment */
    bl     GDB_START
    .word  0xe7ffdeff
#endif

    bl     main                //带LR的子程序跳转, LR = pc - 4, 执行C层main函数    

解读

  • 第一步: 操作 CP15 协处理器 TPIDRPRW 寄存器,它被 ARM 设计保存当前运行线程的 ID值,在ARMv7 架构中才新出现,需PL1权限以上才能访问,而硬件不会从内部去改变它的值,也就是说这是一个直接暴露给工程师操作维护的一个寄存器,在鸿蒙内核中被用于记录线程结构体的开始地址,可以搜索 OsCurrTaskSet 来跟踪哪些地方会切换当前任务以便更好的理解内核。

  • 第二步: 系统控制寄存器(SCTLR),B4.1.130 SCTLR, System Control Register 它提供了系统的最高级别控制,高到了玉皇大帝级别,代码中将 0212位写 0。对应关闭 MMU 、数据缓存 、指令缓存 功能。

  • 第三步: 对浮点运算FPU的设置,在安全模式下使用FPU,须定义NSACRCPACRFPEXC 三个寄存器

  • 第四步: 计算虚拟地址和物理地址的偏移量,为何要计算它呢 ? 主要目的是为了建立虚拟地址和物理地址的映射关系,因为在 MMU启动之后,运行地址(PC寄存器指向的地址)将变成虚拟地址,使用虚拟地址就离不开映射表,所以两个地址的映射关系需要在MMU启动前就创建好,而有了偏移量就可以创建映射表。但需先搞清楚 链接地址 和 运行地址 两个概念。

    • 链接地址 由链接器确定,链接器会将所有输入的 .o 文件链接成一个格式的 .bin 文件,它们都是ELF格式, 链接器给每条指令/数据都赋与一个地址,这个地址叫链接地址,它可以是相对的也可以是绝对的。但它们之间的内部距离是固定的,链接具体过程可翻看 (重定位篇) 和 (链接脚本篇)
    • 运行地址 由加载器确定,内核镜像首先通过烧录工具将内核烧录到 flash 指定的位置,开机后由boot loader工具,例如uboot,将内核镜像加载到指定地址后开始执行真正的内核代码,这个地址叫运行地址

    两个地址往往不一样,而内核设计者希望它们是一样的,那有没有办法检测二者是否一样呢? 答案是 : 当然有的 ,通过一个变量在链接时将其链接地址变成变量的内容 ,无论中间怎么加载变量的内容是不会变的,而获取运行地址是很容易获取的,其实就是PC寄存器的地址,二者一减,加载偏了多少不就出来了

    pa_va_offset:	
      .word   . //定义一个4字节的pa_va_offset 变量, 链接器生成一个链接地址, . 表示 pa_va_offset = 链接地址 举例: 在地址 0x17321796 中保存了 0x17321796 值

    adr     r11, pa_va_offset //代码已执行至此,指令将获取 pa_va_offset 的运行地址(可能不是`0x17321796`) 给r11
    ldr     r0, [r11] // [r11]中存的是链接地址 `0x17321796`, 它不会随加载器变化的
    sub     r11, r11, r0 // 二者相减得到了偏移地址
  • 第五步: 将内核代码从 __exception_handlers 处移到 SYS_MEM_BASE处,长度是 __bss_start - __exception_handlers , __exception_handlers是加载后的开始地址, 由加载器决定, 而SYS_MEM_BASE 是系统定义的内存地址, 可由系统集成商指定配置, 他们希望内核从这里运行。 下图为内核镜像布局

具体代码如下:

        /* if we need to relocate to proper location or not | 如果需要重新安装到合适的位置*/
      adr     r4, __exception_handlers            /* r4: base of load address | 加载基址*/
      ldr     r5, =SYS_MEM_BASE                   /* r5: base of physical address | 物理基址*/
      subs    r12, r4, r5                         /* r12: delta of load address and physical address | 二者偏移量*/
      beq     reloc_img_to_bottom_done            /* if we load image at the bottom of physical address | 不相等就需要重定位 */

      /* we need to relocate image at the bottom of physical address | 需要知道拷贝的大小*/
      ldr     r7, =__exception_handlers           /* r7: base of linked address (or vm address) | 链接地址基地址*/
      ldr     r6, =__bss_start                    /* r6: end of linked address (or vm address),由于目前阶段有用的数据是中断向量表+代码段+只读数据段+数据段,
      										       所以只需复制[__exception_handlers,__bss_start]这段数据到内存基址处 */
      sub     r6, r7                              /* r6: delta of linked address (or vm address) | 内核镜像大小 */
      add     r6, r4                              /* r6: end of load address | 说明需拷贝[ r4,r4+r6 ] 区间内容到 [ r5,r5+r6 ]*/

      reloc_img_to_bottom_loop://重定位镜像到内核物理内存基地址,将内核从加载地址拷贝到内存基址处
          ldr     r7, [r4], #4	// 类似C语言 *r5 = *r4 , r4++ , r5++ 
          str     r7, [r5], #4	// #4 代表32位的指令长度,此时在拷贝内核代码区内容
          cmp     r4, r6          /* 拷贝完成条件. r4++ 直到等于r6 (加载结束地址) 完成拷贝动作 */
          bne     reloc_img_to_bottom_loop
          sub     pc, r12                             /* 重新校准pc寄存器, 无缝跳到了拷贝后的指令地址处执行 r12是重定位镜像前内核加载基地址和内核物理内存基地址的差值 */
          nop		// 注意执行完成sub       pc, r12后,新的PC寄存器也指向了 	nop ,nop是伪汇编指令,等同于 mov r0 r0 通常用于控制时序的目的,强制内存对齐,防止流水线灾难,占据分支指令延迟						
          sub     r11, r11, r12                       /* r11: eventual address offset | 最终地址偏移量 */
  • 第六步: 在打开MMU必须要做好虚拟地址和物理地址的映射关系 , 需构建页表 , 关于页表可翻看 虚实映射篇, 具体代码如下
    #ifdef LOSCFG_KERNEL_MMU 
    ldr     r4, =g_firstPageTable               /* r4: physical address of translation table and clear it
    											   内核页表是用数组g_firstPageTable存储 见于los_arch_mmu.c */
    add     r4, r4, r11                         //计算g_firstPageTable页表物理地址
    mov     r0, r4								//因为默认r0 将作为memset_optimized的第一个参数
    mov     r1, #0								//第二个参数,清0
    mov     r2, #MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS //第三个参数是L1表的长度
    bl      memset_optimized                    /* optimized memset since r0 is 64-byte aligned | 将内核页表空间清零*/

    ldr     r5, =g_archMmuInitMapping	        //记录映射关系表
    add     r5, r5, r11                         //获取g_archMmuInitMapping的物理地址
    init_mmu_loop:	                                //初始化内核页表
        ldmia   r5!, {r6-r10}                       /* r6 = phys, r7 = virt, r8 = size, r9 = mmu_flags, r10 = name | 物理地址、虚拟地址、映射大小、映射属性、名称*/
        cmp     r8, 0                               /* if size = 0, the mmu init done */
        beq     init_mmu_done		                //标志寄存器中Z标志位等于零时跳转到 	init_mmu_done处执行
        bl      page_table_build	                //创建页表
        b       init_mmu_loop						//循环继续
    init_mmu_done:
        orr     r8, r4, #MMU_TTBRx_FLAGS            /* r8 = r4 and set cacheable attributes on translation walk | 设置缓存*/
        ldr     r4, =g_mmuJumpPageTable             /* r4: jump pagetable vaddr | 页表虚拟地址*/
        add     r4, r4, r11				
        ldr     r4, [r4]
        add     r4, r4, r11                         /* r4: jump pagetable paddr | 页表物理地址*/

        /* build 1M section mapping, in order to jump va during turing on mmu:pa == pa, va == pa */
        /* 从当前PC开始建立1MB空间的段映射,分别建立物理地址和虚拟地址方式的段映射页表项
        * 内核临时页表在系统 使能mmu -> 切换到虚拟地址运行 这段时间使用
        */
        mov     r6, pc
        mov     r7, r6                              /* r7: pa (MB aligned)*/
        lsr     r6, r6, #20                         /* r6: pa l1 index */
        ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
        add     r12, r10, r6, lsl #20               /* r12: pa |flags */
        str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[paIndex] = pt entry */
        rsb     r7, r11, r6, lsl #20                /* r7: va */
        str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[vaIndex] = pt entry */

        bl      mmu_setup                           /* set up the mmu | 内核映射表已经创建好了,此时可以启动MMU工作了*/
    #endif
  • 第七步: 使能MMU, 有了页表就可以使用虚拟地址了
    mmu_setup:	//启动MMU工作
        mov     r12, #0                             /* TLB Invalidate All entries - TLBIALL */
        mcr     p15, 0, r12, c8, c7, 0              /* Set c8 to control the TLB and set the mapping to invalid */
        isb
        mcr     p15, 0, r12, c2, c0, 2              /* Translation Table Base Control Register(TTBCR) = 0x0
                                                    [31] :0 - Use the 32-bit translation system(虚拟地址是32位)
                                                    [5:4]:0 - use TTBR0和TTBR1
                                                    [2:0]:0 - TTBCR.N为0;
                                                    例如:TTBCR.N为0,TTBR0[31:14-0] | VA[31-0:20] | descriptor-type[1:0]组成32位页表描述符的地址,
                                                            VA[31:20]可以覆盖4GB的地址空间,所以TTBR0页表是16KB,不使用TTBR1;
                                                    例如:TTBCR.N为1,TTBR0[31:14-1] | VA[31-1:20] | descriptor-type[1:0]组成32位页表描述符的地址,
                                                            VA[30:20]可以覆盖2GB的地址空间,所以TTBR0页表是8KB,TTBR1页表是8KB(页表地址必须16KB对齐);
                                                    */
        isb
        orr     r12, r4, #MMU_TTBRx_FLAGS			//将临时页表属性[6:0]和基地址[31:14]放到r12
        mcr     p15, 0, r12, c2, c0, 0              /* Set attributes and set temp page table */
        isb
        mov     r12, #0x7                           /* 0b0111 */
        mcr     p15, 0, r12, c3, c0, 0              /* Set DACR with 0b0111, client and manager domian */
        isb
        mrc    p15, 0, r12, c1, c0, 1               /* ACTLR, Auxlliary Control Register */
        orr    r12, r12, #(1 << 6)                  /* SMP, Enables coherent requests to the processor. */
        orr    r12, r12, #(1 << 2)                  /* Enable D-side prefetch */
        orr    r12, r12, #(1 << 11)                 /* Global BP Enable bit */
        mcr    p15, 0, r12, c1, c0, 1               /* ACTLR, Auxlliary Control Register */
        dsb
        /*
        * 开始使能MMU,使用的是内核临时页表,这时cpu访问内存不管是取指令还是访问数据都是需要经过mmu来翻译,
        * 但是在mmu使能之前cpu使用的都是内核的物理地址,即使现在使能了mmu,cpu访问的地址值还是内核的物理地址值(这里仅仅从数值上来看),
        * 而又由于mmu使能了,所以cpu会把这个值当做虚拟地址的值到页表中去找其对应的物理地址来访问。
        * 所以现在明白了为什么要在内核临时页表里建立一个内核物理地址和虚拟地址一一映射的页表项了吧,因为建立了一一映射,
        * cpu访问的地址经过mmu翻译得到的还是和原来一样的值,这样在cpu真正使用虚拟地址之前也能正常运行。
        */
        mrc     p15, 0, r12, c1, c0, 0
        bic     r12, #(1 << 29 | 1 << 28)           /* disable access flag[bit29],ap[0]是访问权限位,支持全部的访问权限类型
                                                    disable TEX remap[bit28],使用TEX[2:0]与C Bbit控制memory region属性 */
        orr     r12, #(1 << 0)                      /* mmu enable */
        bic     r12, #(1 << 1)
        orr     r12, #(1 << 2)                     /* D cache enable */
        orr     r12, #(1 << 12)                    /* I cache enable */
        mcr     p15, 0, r12, c1, c0, 0              /* Set SCTLR with r12: Turn on the MMU, I/D cache Disable TRE/AFE */
        isb
        ldr     pc,  =1f                            /* Convert to VA | 1表示标号,f表示forward(往下) - pc值取往下标识符“1”的虚拟地址(跳转到标识符“1”处)
                                                    因为之前已经在内核临时页表中建立了内核虚拟地址和物理地址的映射关系,所以接下来cpu切换到虚拟地址空间 */
        1:
            mcr     p15, 0, r8, c2, c0, 0               /* Go to the base address saved in C2: Jump to the page table */
            isb                                         //r8中保存的是内核L1页表基地址和flags,r8写入到TTBR0实现临时页表和内核页表的切换
            mov     r12, #0
            mcr     p15, 0, r12, c8, c7, 0              /* TLB Invalidate All entries - TLBIALL(Invalidate all EL1&0 regime stage 1 and 2 TLB entries) */
            isb
            sub     lr,  r11                            /* adjust lr with delta of physical address and virtual address | 
                                                        lr中保存的是mmu使能之前返回地址的物理地址值,这时需要转换为虚拟地址,转换算法也很简单,虚拟地址 = 物理地址 - r11 */
            bx      lr                                  //返回
  • 第八步: 设置异常和中断栈 ,初始化栈内值和栈顶值
    //初始化栈内值
        ldr     r0, =__svc_stack	    //stack_init的第一个参数 __svc_stack表示栈顶
        ldr     r1, =__exc_stack_top	//stack_init的第二个参数 __exc_stack_top表示栈底, 这里会有点绕, top表高地址位
        bl      stack_init              //初始化各个cpu不同模式下的栈空间
        //设置各个栈顶魔法数字
        STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD //中断栈底设成"烫烫烫烫烫烫"
        STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD     //异常栈底设成"烫烫烫烫烫烫"
    stack_init:
        ldr     r2, =OS_STACK_INIT	//0xCACACACA
        ldr     r3, =OS_STACK_INIT
        /* Main loop sets 32 bytes at a time. | 主循环一次设置 32 个字节*/
    stack_init_loop:
        .irp    offset, #0, #8, #16, #24
        strd    r2, r3, [r0, \offset]    /* 等价于strd r2, r3, [r0, 0], strd r2, r3, [r0, 8], ... , strd r2, r3, [r0, 24] */
        .endr
        add     r0, #32			//加跳32个字节,说明在地址范围上 r1 > r0 ==> __exc_stack_top > __svc_stack
        cmp     r0, r1			//是否到栈底
        blt     stack_init_loop
        bx      lr

    //初始化栈顶值
    excstack_magic:
        mov     r3, #0 //r3 = 0
    excstack_magic_loop:
        str     r2, [r0]   //栈顶设置魔法数字
        add     r0, r0, r1 //定位到栈底
        add     r3, r3, #1 //r3++
        cmp     r3, #CORE_NUM //栈空间等分成core_num个空间,所以每个core的栈顶需要magic num
        blt     excstack_magic_loop
        bx      lr
    /* param0 is stack top, param1 is stack size, param2 is magic num */
    .macro STACK_MAGIC_SET param0, param1, param2
        ldr     r0, =\param0
        mov     r1, \param1
        ldr     r2, =\param2
        bl      excstack_magic
    .endm
    STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD //中断栈底设成"烫烫烫烫烫烫"
    STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD     //异常栈底设成"烫烫烫烫烫烫"
  • 第九步: 热启动
    warm_reset: //热启动 Warm Reset, warm reboot, soft reboot, 在不关闭电源的情况,由软件控制重启计算机
      /* initialize CPSR (machine state register) */
      mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE) /* 禁止IRQ中断 | 禁止FIQ中断 | 管理模式-操作系统使用的保护模式 */
      msr    cpsr, r0

      /* Note: some functions in LIBGCC1 will cause a "restore from SPSR"!! */
      msr    spsr, r0

      /* get cpuid and keep it in r12 */
      mrc     p15, 0, r12, c0, c0, 5		//R12保存CPUID 
      and     r12, r12, #MPIDR_CPUID_MASK //掩码操作获取当前cpu id

      /* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack */
      ldr    r0, =__svc_stack_top
      mov    r2, #OS_EXC_SVC_STACK_SIZE
      mul    r2, r2, r12
      sub    r0, r0, r2                   /* 算出当前core的中断栈栈顶位置,写入所属core的sp */
      mov    sp, r0

      LDR    r0, =__exception_handlers
      MCR    p15, 0, r0, c12, c0, 0       /* Vector Base Address Register - VBAR */

      cmp    r12, #0
      bne    cpu_start                    //从核处理分支
  • 第十步: 进入 C 语言的 main()
    bl     main                //带LR的子程序跳转, LR = pc - 4, 执行C层main函数

    LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//由主CPU执行,默认0号CPU 为主CPU 
      {
          UINT32 ret = OsMain();
          if (ret != LOS_OK) {
              return (INT32)LOS_NOK;
          }

          CPU_MAP_SET(0, OsHwIDGet());//设置CPU映射,参数0 代表0号CPU

          OsSchedStart();//调度开始

          while (1) {
              __asm volatile("wfi");//WFI: wait for Interrupt 等待中断,即下一次中断发生前都在此hold住不干活
          }
      }

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/626687.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

知了汇智引领未来:全新AIGC系列课程,打造数字时代人才新标杆

在全球AIGC&#xff08;生成式人工智能&#xff09;技术加速发展的背景下&#xff0c;一系列权威报道揭示了该领域内市场潜力、行业应用、教育研究、政府监管以及具体应用场景的蓬勃进展。据腾讯网4月19日报道&#xff0c;中国AIGC应用市场规模预计于2024年达到200亿人民币&…

FART 不需要刷机,通过脚本动态脱抽取壳

准备环境 adb , 见上一篇文章&#xff1b;frida-fart, https://github.com/hanbinglengyue/FART;frida, 参考上一篇文章 工具配置 解压该文件后&#xff0c;将lib文件夹中的fart.so和fart64.so拷贝到/data/app目录下&#xff0c;如果需要管理员权限&#xff0c;通过adb root…

C#窗体程序设计笔记:如何调出控件工具箱,并设置控件的属性

文章目录 调出控件工具箱设置控件属性 调出控件工具箱 使用Visual Studio打开C#解决方案后&#xff0c;初始界面如下图所示&#xff1a; 接着&#xff0c;在上方的菜单栏依次选择“视图”“工具箱”&#xff0c;即可打开工具箱&#xff0c;如下图所示&#xff1a; 设置控件属…

Jmeter 性能-阶梯式性能指标监听

例如&#xff1a;现要加载100个线程&#xff0c;希望聚合报告中分别展示&#xff1a;1-20&#xff0c;20-40&#xff0c;40-60&#xff0c;60-80的四个阶段的线程并发性能数据&#xff0c;而不是一并总体的统计数据。 实现方法&#xff1a;Jmeter通过自定义代码去实现 ①添加…

山东济南中国当代文化名人颜廷利:大自然赋予人类众生的真正贵重礼物

大自然赋予了众生---火&#xff08;太阳&#xff0c;万物生长靠太阳&#xff09;、水&#xff08;河流&#xff0c;水是生命之源&#xff09;、木&#xff08;空气&#xff0c;生命就在一翕一合的呼吸之间&#xff09;、土&#xff08;大地&#xff0c;坤为大地之母&#xff0c…

爱普生M-A352加速度计受到日本气象厅认证

地震一直是缠在人们头顶的乌云&#xff0c;如何能在地震发生的时候提前获悉&#xff0c;防止造成更大的经济损失&#xff0c;成为了许多企业准备解决的问题。精工爱普生公司获悉&#xff0c;东京Knowledge ForesightInc.生产的配备爱普生M-A352 高性能三轴加速度计的“Yure Mon…

电力物联网-(2)系统设计

电力物联网系统设计 前言 在此之前写过《电力物联网系统设计》开篇文章&#xff0c;上一篇文章主要的概述性的内容&#xff0c;发表之后总觉得对电力物联网系统设计这一方面还只是开了一个头&#xff0c;没有把相关的内容讲解清楚&#xff0c;于是经过一段时间的构思终于产出了…

开源相机管理库Aravis例程学习(七)——chunk-parser

开源相机管理库Aravis例程学习&#xff08;七&#xff09;——chunk-parser 简介例程代码函数说明arv_camera_create_chunk_parserarv_camera_set_chunksarv_chunk_parser_get_integer_value 简介 本文针对官方例程中的&#xff1a;05-chunk-parser做简单的讲解。并介绍其中调…

Spring Framework-简介

Spring Framework Java Spring是一个开源的Java应用框架&#xff0c;它的主要目的是简化企业级应用开发的复杂性。Spring框架为开发者提供了许多基础功能&#xff0c;使得开发者能够更专注于业务逻辑的实现&#xff0c;而不是底层的细节。 主要特点和功能&#xff1a; 控制反…

怎么识别数学公式?分享简单识别方法

怎么识别数学公式&#xff1f;在学术研究和日常工作中&#xff0c;数学公式无疑是一个常见且重要的元素。然而&#xff0c;手动输入复杂的数学公式往往既耗时又容易出错。幸运的是&#xff0c;随着科技的发展&#xff0c;现在我们有了一些高效的软件工具&#xff0c;可以帮助我…

C语言----斐波那契数列(附源代码)

各位看官们好&#xff0c;当我写了上一篇博客杨辉三角后&#xff0c;有一些看官叫我讲一下斐波那契数列。对于这个大家应该是有了解的。最简单的规律就是f(n)f(n-2)f(n-1)。就是当前是前两项之和&#xff0c;然后下标1和0都是1.从第三项开始计算的。那么我们知道规律&#xff0…

纯电动汽车的发展趋势简述

纯电车简介 纯电动汽车是使用电池驱动电动马达而不是传统的内燃机的汽车。它们通常使用电池组储存能量&#xff0c;然后通过电动马达转化为动力来驱动车辆。相比于传统的燃油车&#xff0c;纯电动汽车具有零排放、低噪音、低维护成本等优点&#xff0c;因此在环保和能源效率方…

MATLAB蚁群算法求解带时间窗的旅行商TSPTW问题代码实例

MATLAB蚁群算法求解带时间窗的旅行商TSPTW问题代码实例 蚁群算法编程求解TSPTW问题实例&#xff1a; 在经纬度范围为(121, 43)到(123, 45)的矩形区域内&#xff0c;散布着1个商家&#xff08;编号1&#xff09;和25个顾客点&#xff08;编号为226&#xff09;&#xff0c;各个…

Go-Zero定义API实战:探索API语法规范与最佳实践(五)

前言 上一篇文章带你实现了Go-Zero模板定制化&#xff0c;本文将继续分享如何使用GO-ZERO进行业务开发。 通过编写API层&#xff0c;我们能够对外进行接口的暴露&#xff0c;因此学习规范的API层编写姿势是很重要的。 通过本文的分享&#xff0c;你将能够学习到Go-Zero的API…

【御控物联】Java JSON结构转换、JSON协议转换、JSON属性互换(15):对象To数组——转换映射方式

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON数组》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

如何压缩图片大小?7个实用软件教你快速压缩图片大小

如何压缩图片大小&#xff1f;7个实用软件教你快速压缩图片大小 以下是七个实用的软件&#xff0c;可以帮助您快速压缩图片大小&#xff1a; 图片编辑助手&#xff1a;这是一款功能强大的图像处理软件&#xff0c;其中包含了图像压缩功能。您可以打开需要压缩的图片&#xf…

Java 包语句,看这一篇就够了

1.设计的文件层级 我们将“Package”文件夹称为根目录&#xff0c;“Level01”称为一级目录&#xff0c;“Level02”称为二级目录&#xff0c;以此类推。 2.发现在不同目录下的包名有如下特征&#xff1a; 根目录下的文件不需要包名&#xff0c;可以理解成包名为 “”一级目录…

上海人工智能实验室浦视团队联培博士(2025)招生正式启动!

上海人工智能实验室浦视团队2025级联培博士招生计划开启啦&#xff01; 上海人工智能实验室作为国内领先的人工智能领域的新型科研机构&#xff0c;不仅致力于攻克重要基础理论难题&#xff0c;更着眼于构建全球领先的 AI 技术人才培养平台。浦视团队是大模型方向的核心科研团…

【Java基础】我不允许还有人搞不懂lambda表达式!!!

λ希腊字母表中排序第十一位的字母避免匿名内部类定义过多&#xff0c;使得代码更加简洁其实质属于函数式编程的概念 (params)->expression[表达式] (params)->statement[语句] (params)->{statements}lambda表达式推导过程&#xff1a; 创建一个类&#xff0c;重写接…

AI机器人火了,探讨早就可以帮我们开拓市场的中关村科金语音机器人

近期AI机器人给我们带来了不少惊喜&#xff0c;比如国外 Figuer 联合 OpenAI 做了 Chatgpt 机器人&#xff0c;可以通过对话后推理干活&#xff0c;国内仿生机器人员工也开始量产&#xff0c;看到AI机器人不禁想到会不会替代我们的工作&#xff1f;我们了解到很多机器人厂家的…