![d58a9847e4032c69a5ee6d7fef65bdfe.gif](https://i-blog.csdnimg.cn/blog_migrate/7f51c6da9877bc73825991f3ef151d03.gif)
los_startup_gcc.s启动引导文件
LiteOS操作系统的启动是从main函数开始的。而ARM Cortex-M芯片从上电到执行main函数,中间经过了Reset_Handler等函数。当对STM32F769IDISCOVERY开发板进行上电操作或者复位操作时,STM32F769IDISCOVERY会从异常向量表中获取Reset_Handler函数的地址并执行该函数。位于targets/STM32F769IDISCOVERY/GCC/目录下的汇编文件los_startup_gcc.s定义了该函数。
los_startup_gcc.s是启动引导文件,从Reset_Handler开始到执行main函数,主要工作就是准备C代码的运行环境,具体包括:
设置栈指针SP
初始化中断向量
初始化data段
初始化bss段
初始化系统时钟
跳转到 C 代码函数main
代码如下:
Reset_Handler: cpsid i /* 设置栈指针sp */ ldr sp, =_estack /* 从flash中拷贝中断向量到SRAM */ movs r1, #0 /* 跳转到LoopCopyVectorInit */ b LoopCopyVectorInit/* 拷贝中断向量 */CopyVectorInit: ldr r3, =_si_liteos_vector_data ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4LoopCopyVectorInit: ldr r0, =_s_liteos_vector ldr r3, =_e_liteos_vector adds r2, r0, r1 cmp r2, r3 bcc CopyVectorInit /* 从flash中拷贝data段到SRAM中 */ movs r1, #0 /* 跳转到LoopCopyDataInit */ b LoopCopyDataInit/* 拷贝data段 */CopyDataInit: ldr r3, =_sidata ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4LoopCopyDataInit: ldr r0, =_sdata ldr r3, =_edata adds r2, r0, r1 cmp r2, r3 bcc CopyDataInit ldr r2, =_sbss /* 跳转到LoopFillZerobss */ b LoopFillZerobss/* bss段清零 */FillZerobss: movs r3, #0 str r3, [r2], #4LoopFillZerobss: ldr r3, = _ebss cmp r2, r3 bcc FillZerobss /* 调用STM32系统时钟初始化函数SystemInit */ bl SystemInit /* 调用C代码的入口函数main */ bl main
data段存放的是已经初始化的全局变量,需要从flash中获取这些数据到RAM中。而bss段存放的是没有初始化的全局变量,因此flash中并没有bss段的变量值,所以启动引导文件只是对RAM中的.bss段进行清零操作。
los_startup_gcc.s启动引导文件中使用的_estack 、_si_liteos_vector_data、_s_liteos_vector、_e_liteos_vector、_sidata、_sdata 、_edata、_sbss、_ebss,这些符号都定义在targets/STM32F769IDISCOVERY/GCC/目录下的STM32F769IDISCOVERY_LiteOS.ld链接脚本中。
链接脚本根据应用需要,设置堆栈大小和栈地址,并控制每个段的存放位置。对于中断向量和data段,既要放到flash中,也需要放到RAM中,并通过链接脚本的AT关键字把flash的地址设定为load地址。
下图是链接脚本中的相关代码:los_startup_gcc.s启动引导文件中除了定义Reset_Handler函数,还定义了其他中断异常处理函数Default_Handler,并为Default_Handler的每个异常处理程序提供弱别名。所谓弱别名,即具有相同名称的任何函数都将覆盖此处的函数。这样做可以防止用户使能了中断却没有设置中断处理程序时造成的崩溃。Default_Handler函数只是进入一个无限循环以保留系统状态供调试器检查。
main函数
LiteOS的main函数定义在targets/STM32F769IDISCOVERY/Src/main.c。main函数主要负责LiteOS的初始化工作。INT32 main(VOID){ /* * 初始化硬件,主要包括设置MPU、设置芯片PLL为 * 系统时钟、设置UART,并初始化HAL 和SDRAM */ HardwareInit(); /* 初始化LiteOS内核及例程 */ UINT32 ret = OsMain(); if (ret != LOS_OK) { return LOS_NOK; } /* 开始任务调度,LiteOS开始正常工作 */ OsStart(); return 0;}
硬件初始化和主要芯片相关,这里不做详细介绍。下面介绍LiteOS内核的初始化,代码如下:
LITE_OS_SEC_TEXT_INIT UINT32 OsMain(void){ UINT32 ret; /* 初始化动态内存池 */ ret = OsMemSystemInit((UINTPTR)&OS_SYS_MEM_START + OS_EXC_INTERACTMEM_SIZE); if (ret != LOS_OK) { return ret; } /* * 配置最大支持的任务个数、信号量个数、互斥锁个数、 * 队列个数以及软件定时器个数,设置g_sysClock和 * g_tickPerSecond全局变量 */ OsRegister();/* * LOSCFG_PLATFORM_HWI宏默认在los_config.h中定义为YES, * 初始化硬中断,此后LiteOS就会接管系统的中断, * 使用中断前需要先注册中断并使能 */#if (LOSCFG_PLATFORM_HWI == YES) OsHwiInit();#endif /* * 设置中断向量的中断处理函数,包括 * Hard Fault硬件故障中断、 * Non Maskable Interrupt不可屏蔽中断(NMI)、 * Memory Management内存管理中断、 * Bus Fault 总线故障中断、 * Usage Fault使用故障中断、 * SVCall利用SVC指令调用系统服务的中断 */ OsExcInit(); /* * 初始化任务链表包括任务的排序链表, * 初始化优先级消息队列链表(用于管理不同优先级任务) */ ret = OsTaskInit(); if (ret != LOS_OK) { PRINT_ERR("OsTaskInit error\n"); return ret; }/* * LOSCFG_BASE_CORE_TSK_MONITOR宏默认在 * los_config.h中定义为YES,初始化任务监视器 */#if (LOSCFG_BASE_CORE_TSK_MONITOR == YES) OsTaskMonInit();#endif/* * 下面的和进程通信相关的3个宏默认在los_config.h中定义为YES。 * OsIpcInit包括初始化消息队列链表、互斥锁链表和信号量链表 */#if ((LOSCFG_BASE_IPC_QUEUE == YES) || (LOSCFG_BASE_IPC_MUX == YES) || (LOSCFG_BASE_IPC_SEM == YES)) ret = OsIpcInit(); if (ret != LOS_OK) { return ret; }#endif/* * LOSCFG_BASE_CORE_SWTMR宏默认在los_config.h中定义为YES. * OsSwtmrInit对软件定时器和其在percpu上的排序链表进行初始化, * 并初始化定期器处理函数的内存池,同时还会创建软件定时器 * 的消息队列和定时器任务 */#if (LOSCFG_BASE_CORE_SWTMR == YES) ret = OsSwtmrInit(); if (ret != LOS_OK) { return ret; }#endif /* 创建空闲任务 */ ret = OsIdleTaskCreate(); if (ret != LOS_OK) { return ret;}/* * LOSCFG_PLATFORM_OSAPPINIT宏默认已经在Makefile中定义。 * OsAppInit创建了一个名为“app_Task”的任务,该任务处理函数为 * app_init,任务优先级为10; * OsTestInit创建了一个名为“IT_TST_IN”的任务,该任务处理函数为 * TestTaskEntry,任务优先级为25 */#ifdef LOSCFG_PLATFORM_OSAPPINIT ret = OsAppInit();#else /* LOSCFG_TEST */ ret = OsTestInit();#endif if (ret != LOS_OK) { return ret; } return LOS_OK;}
以上代码只是摘取了宏变量已经定义的部分,还有部分代码的宏变量未定义,所以不会进入这些代码分支,比如CPU占用率计算相关的初始化、shell相关的初始化等。
完成内核的初始化后,调用OsStart()开始任务调度,自此LiteOS开始正常工作。OsStart函数的代码如下:
LITE_OS_SEC_TEXT_INIT VOID OsStart(VOID){ LosTaskCB *taskCB = NULL; /* 获取当前执行任务的CPU ID,即0 */ UINT32 cpuid = ArchCurrCpuid(); /* * 配置Tick中断向量,其中断处理函数为OsTickHandler。 * 初始化System Tick Timer及其中断,并启动此Timer。 * 计数器会产生周期性中断 */ OsTickStart(); LOS_SpinLock(&g_taskSpin); /* 获取最高优先级任务队列中的第一个任务,赋给taskCB */ taskCB = OsGetTopTask(); /* 设置32位的调度flag,第CPU ID位设置为1 */ OS_SCHEDULER_SET(cpuid); /* 设置全局变量g_runTask为taskCB */ OsCurrTaskSet(taskCB); PRINTK("cpu %d entering scheduler\n", cpuid); /* * 调度g_runTask即taskCB任务,OsStartToRun函数 * 定义在los_dispatch.S汇编文件中 */ OsStartToRun(taskCB);}
通过上面的分析,可以看到整个启动流程从板子复位上电开始,调用汇编代码Reset_Handler进入启动引导文件,完成C代码运行环境的准备工作后跳转到main函数中。在main中完成硬件初始化和LiteOS内核的初始化,并通过汇编跳转到执行第一个最高优先级的任务命令的地址上,从而开始LiteOS的运行。