鸿蒙内核源码分析 (内核启动篇) | 从汇编到 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 = 0mcr     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 reservedMCR    p15, 0, r0, c1, c1, 2LDR    r0, =(0xF << 20)      //允许在EL0和EL1下,访问协处理器10和11(控制Floating-point和Advanced SIMD特性)MCR    p15, 0, r0, c1, c0, 2ISB
#endifMOV    r3, #0x40000000	    //EN, bit[30] 设置FPEXC的EN位来使能FPUVMSR   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	  //物理地址-虚拟地址 = 映射偏移量 放入r11mrc     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_loopsub     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								//第二个参数,清0mov     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, pcmov     r7, r6                              /* r7: pa (MB aligned)*/lsr     r6, r6, #20                         /* r6: pa l1 index */ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGSadd     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, r0LDR    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_startldr    r2, =__bss_endmov    r1, #0sub    r2, r2, r0bl     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
#endifbl     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`) 给r11ldr     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_loopsub     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								//第二个参数,清0mov     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, pcmov     r7, r6                              /* r7: pa (MB aligned)*/lsr     r6, r6, #20                         /* r6: pa l1 index */ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGSadd     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 */isbmcr     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对齐);*/isborr     r12, r4, #MMU_TTBRx_FLAGS			//将临时页表属性[6:0]和基地址[31:14]放到r12mcr     p15, 0, r12, c2, c0, 0              /* Set attributes and set temp page table */isbmov     r12, #0x7                           /* 0b0111 */mcr     p15, 0, r12, c3, c0, 0              /* Set DACR with 0b0111, client and manager domian */isbmrc    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, 0bic     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 */isbldr     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, #0mcr     p15, 0, r12, c8, c7, 0              /* TLB Invalidate All entries - TLBIALL(Invalidate all EL1&0 regime stage 1 and 2 TLB entries) */isbsub     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	//0xCACACACAldr     r3, =OS_STACK_INIT/* Main loop sets 32 bytes at a time. | 主循环一次设置 32 个字节*/stack_init_loop:.irp    offset, #0, #8, #16, #24strd    r2, r3, [r0, \offset]    /* 等价于strd r2, r3, [r0, 0], strd r2, r3, [r0, 8], ... , strd r2, r3, [r0, 24] */.endradd     r0, #32			//加跳32个字节,说明在地址范围上 r1 > r0 ==> __exc_stack_top > __svc_stackcmp     r0, r1			//是否到栈底blt     stack_init_loopbx      lr

    //初始化栈顶值excstack_magic:mov     r3, #0 //r3 = 0excstack_magic_loop:str     r2, [r0]   //栈顶设置魔法数字add     r0, r0, r1 //定位到栈底add     r3, r3, #1 //r3++cmp     r3, #CORE_NUM //栈空间等分成core_num个空间,所以每个core的栈顶需要magic numblt     excstack_magic_loopbx      lr/* param0 is stack top, param1 is stack size, param2 is magic num */.macro STACK_MAGIC_SET param0, param1, param2ldr     r0, =\param0mov     r1, \param1ldr     r2, =\param2bl      excstack_magic.endmSTACK_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_topmov    r2, #OS_EXC_SVC_STACK_SIZEmul    r2, r2, r12sub    r0, r0, r2                   /* 算出当前core的中断栈栈顶位置,写入所属core的sp */mov    sp, r0LDR    r0, =__exception_handlersMCR    p15, 0, r0, c12, c0, 0       /* Vector Base Address Register - VBAR */cmp    r12, #0bne    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号CPUOsSchedStart();//调度开始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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在k8s中安装Grafana并对接Prometheus,实现k8s集群监控数据的展示

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Grafana&#xff1a;让数据说话的魔术师》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Grafana简介 2、Grafana的重要性与影响力 …

强化训练:day9(添加逗号、跳台阶、扑克牌顺子)

文章目录 前言1. 添加逗号1.1 题目描述2.2 解题思路2.3 代码实现 2. 跳台阶2.1 题目描述2.2 解题思路2.3 代码实现 3. 扑克牌顺子3.1 题目描述3.2 解题思路3.3 代码实现 总结 前言 1. 添加逗号   2. 跳台阶   3. 扑克牌顺子 1. 添加逗号 1.1 题目描述 2.2 解题思路 我的写…

STM32学习和实践笔记(28):printf重定向实验

1.printf重定向简介 在C语言中printf函数里&#xff0c;默认输出设备是显示器&#xff0c;如果想要用这个函数将输出结果到串口或者LCD上显示&#xff0c;就必须重定义标准库函数里中printf函数调用的与输出设备相关的函数。 比如要使用printf输出到串口&#xff0c;需要先将f…

linux 任务管理(临时任务定时任务) 实验

目录 任务管理临时任务管理周期任务管理 任务管理 临时任务管理 执行如下命令添加单次任务&#xff0c;输入完成后按组合键Ctrl-D。 [rootopenEuler ~]# at now5min warning: commands will be executed using /bin/sh at> echo "aaa" >> /tmp/at.log at&g…

J-STAGE (日本电子科学与技术信息集成)数据库介绍及文献下载

J-STAGE (日本电子科学与技术信息集成)是日本学术出版物的平台。它由日本科学技术振兴机构&#xff08;JST&#xff09;开发和管理。该系统不仅包括期刊&#xff0c;还有论文集&#xff0c;研究报告、技术报告等。文献多为英文&#xff0c;少数为日文。目前网站上所发布的内容来…

使用Vue调用ColaAI Plus大模型,实现聊天(简陋版)

首先去百度文心注册申请自己的api 官网地址&#xff1a;LuckyCola 注册点开个人中心 查看这个文档自己申请一个ColaAI Plus定制增强大模型API | LuckyColahttps://luckycola.com.cn/public/docs/shares/api/colaAi.html来到vue的页面 写个样式 <template><Header …

ICode国际青少年编程竞赛- Python-5级训练场-综合练习6

ICode国际青少年编程竞赛- Python-5级训练场-综合练习6 1、 for i in range(3):Dev.step(2 * (i 1))Dev.turnLeft()while Flyer[2 - i].disappear():wait()Dev.step(2 * (i 1))Dev.turnRight()while Dev.x ! Item[i].x:wait()2、 for i in range(3):Dev.step(2 * i 1)while …

用Python的pynput库成为按键记录高手

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 揭秘键盘输入&#xff1a;pynput库的基本介绍 无论是为了安全审计、数据分析还是创建热键操作&#xff0c;能够记录和处理键盘事件都显得尤为关键。这就是pynput库发挥作用的地方。pynput是一个Python库&#xff0c…

Java 对象序列化

序列化&#xff1a;把对象转化为可传输的字节序列过程称为序列化。 反序列化&#xff1a;把字节序列还原为对象的过程称为反序列化 序列化的作用是方便存储和传输&#xff0c;细节可参考如下文章&#xff1a; 序列化理解起来很简单 - 知乎序列化的定义 序列化&#xff1a;把对…

echarts map地图添加背景图

给map地图添加了一个阴影3d的效果&#xff0c;添加一张背景图&#xff0c;给人感觉有3d的效果 具体配置如下&#xff1a; html代码模块&#xff1a; <div class"echart_img" style"position: fixed; visibility: hidden;"></div><div id&q…

Autoware内容学习与初步探索(一)

0. 简介 之前作者主要是基于ROS2&#xff0c;CyberRT还有AutoSar等中间件完成搭建的。有一说一&#xff0c;这种从头开发当然有从头开发的好处&#xff0c;但是如果说绝大多数的公司还是基于现成的Apollo以及Autoware来完成的。这些现成的框架中也有很多非常好的方法。目前作者…

【Java的抽象类和接口】

1. 抽象类 1.1 抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果 一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 以上代码中…

Leaflet系列——【一】初识Leaflet与Leaflet视图操作

初识Leaflet&#xff08;vue3 &#xff09; 前言&#xff1a;当你熟悉了openlayer、mapbox、cesium等一些GIS框架之后&#xff0c;对于我们开发来说其实他们的本质就是往瓦片上面叠加图层、【点、线、面、瓦片、geoJson、热力图、图片、svg等等】都是一层层的Layer图层&#xf…

Ceph集群扩容及数据再均衡原理分析

用户文件在Ceph RADOS中存储、定位过程大概包括&#xff1a;用户文件切割成对象、对象映射到PG、PG分组PGP、PG映射到OSD。这些过程中&#xff0c;可能涉及了大量概念和变量&#xff0c;而其实它们大部分是通过HASH、CRUSH等算法计算出来的&#xff0c;初始参数可能也就只有这么…

sql实践

1.从excel导入数据 在excel导入数据时要先在数据库中创建对应的数据库表 CREATE TABLE your_table_name (crawl_datetime DATE,url CHAR(255),company_name CHAR(255),company_size CHAR(255),company_type CHAR(255),job_type CHAR(255),job_name CHAR(255),edu CHAR(255),e…

暗区突围TWITCH掉宝关联帐号不了 无法关联帐号 关联不上

Twitch&#xff0c;作为全球知名的游戏直播平台&#xff0c;常常携手热门游戏如《暗区突围》举办互动活动&#xff0c;为玩家带来独特的参与体验。在这个过程中&#xff0c;“绑定关联”成为了连接直播观众与游戏世界的桥梁。简单来说&#xff0c;Twitch绑定关联《暗区突围》指…

leetcode——链表的中间节点

876. 链表的中间结点 - 力扣&#xff08;LeetCode&#xff09; 链表的中间节点是一个简单的链表OJ。我们要返回中间节点有两种情况&#xff1a;节点数为奇数和节点数是偶数。如果是奇数则直接返回中间节点&#xff0c;如果是偶数则返回第二个中间节点。 这道题的解题思路是&a…

OpenAI 发布了免费的 GPT-4o,国内大模型还有哪些机会?

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 这是今天在某乎看到一个问题&#xff1a;OpenAI 发完 GPT-4o&…

关闭 Visual Studio Code 项目中 的eslint的语法校验 lintOnSave: false;; 项目运行起来之后 自动打开浏览器 端口

1、在 vue.config.js 配置 一个属性 lintOnSave: false 2、配置两个属性 open: true, // 自动打开浏览器 port: 3000 // 端口 port 端口号根据自己的项目实际开发来 配置

Lumina-T2X 一个使用 DiT 架构的内容生成模型,可通过文本生成图像、视频、多视角 3D 对象和音频剪辑。

Lumina-T2X 是一个新的内容生成系列模型&#xff0c;统一使用 DiT 架构。通过文本生成图像、视频、多视角 3D 对象和音频剪辑。 可以在大幅提高生成质量的前提下大幅减少训练成本&#xff0c;而且同一个架构支持不同的内容生成。图像质量相当不错。 由 50 亿参数的 Flag-DiT …