摘要:本文带来基于LiteOS一站式开发工具LiteOS Studio,通过单步调试,来动态分析LiteOS的启动流程。

编者按:在LiteOS大揭秘系列,咱们和读者们分享了《LiteOS是怎么在STM32上开始运行的》,从源码上动态剖析了一遍LiteOS的启动流程。本文提供一种新的形式,即基于LiteOS一站式开发工具LiteOS Studio,通过单步调试,来动态分析LiteOS的启动流程,给开发者一个更直观的展现。

理解LiteOS零碎,咱们能够先从它的启动流程开始。不同的芯片和编译工具,其启动流程可能会有一些差别,本文基于码云 LiteOS开源站点 master分支12月的代码,以STM32F769IDISCOVERY(ARM Cortex M7)开发板和GCC编译工具为例,应用LiteOS Studio的单步调试,动态分析LiteOS的启动流程。

LiteOS Studio环境筹备

在开始前,须要筹备好LiteOS Studio环境,蕴含LiteOS Studio装置、新建工程、编译、烧录,把握LiteOS Studio如何调测等等,能够参考官网文档站点https://liteos.gitee.io/liteo...。

  • 如何搭建LiteOS Studio开发环境 请参考搭建Windows开发环境
  • 如何新建STM32F769IDISCOVERY的LiteOS工程 请参考 新建工程
  • 如何编译,烧录、调测,请别离参考 编译配置-编译代码,烧录配置-烧录,调试器-执行调试

留神,如果开发板应用的是板载ST-LINK仿真器,须要刷为JLINK。请参考 st-link仿真器单步调测。

另外,执行单步调测,默认进行在main()函数。LiteOS操作系统的启动是从main函数开始的。而ARM Cortex-M芯片从上电到执行main函数,两头通过了Reset_Handler等函数。LiteOS零碎重启、复位等都是从Reset_Handler函数开始执行的。在LiteOS Studio工程找到文件.vscodelaunch.json,把其中的postLaunchCommands属性上面的"b main"改为"b Reset_Handler"。如下图:

从新开始调测,零碎会暂停在Reset_Handler函数处。如下图:

los_startup_gcc.S启动引导文件介绍

当对STM32F769IDISCOVERY开发板进行上电操作或者复位操作时,该开发板会从异样向量表中获取Reset_Handler函数的地址并执行该函数。汇编文件targetsSTM32F769IDISCOVERYlos_startup_gcc.S定义了该函数。

los_startup_gcc.S是启动引导文件,从Reset_Handler开始到执行main函数,次要工作就是筹备C代码的运行环境,具体包含:

  • 设置栈指针SP,对应语句 ldr sp, =_estack
  • 初始化中断向量,对应函数LoopCopyVectorInit
  • 初始化data段,对应函数LoopCopyDataInit
  • 初始化bss段,对应函数LoopFillZerobss
  • 初始化零碎时钟,跳转到函数SystemInit
  • 跳转到 C 代码函数main

代码如下:

Reset_Handler:  cpsid i  ldr   sp, =_estack      /* set stack pointer *//* Copy the vector_ram segment initializers from flash to SRAM */  movs  r1, #0  b  LoopCopyVectorInitCopyVectorInit:  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/* Copy the data segment initializers from flash to SRAM */  movs  r1, #0  b  LoopCopyDataInitCopyDataInit:  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  b  LoopFillZerobss/* Zero fill the bss segment. */FillZerobss:  movs  r3, #0  str  r3, [r2], #4LoopFillZerobss:  ldr  r3, = _ebss  cmp  r2, r3  bcc  FillZerobss/* Call the clock system initialization function.*/  bl  SystemInit/* Call static constructors *//*    bl __libc_init_array *//* Call the application's entry point.*/  bl  main  bx  lr

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,这些符号都定义在targetsSTM32F769IDISCOVERYliteos.ld链接脚本中。

链接脚本依据利用须要,设置堆栈大小和栈地址,并管制每个段的寄存地位。对于中断向量和data段,既要放到Flash中,也须要放到RAM中,并通过链接脚本的AT关键字把Flash的地址设定为load地址。

注:链接脚本中的相干代码能够拜访https://gitee.com/LiteOS/LiteOS/blob/master/targets/STM32F769IDISCOVERY/liteos.ld查看。

los_startup_gcc.S启动引导文件中除了定义Reset_Handler函数,还定义了其余中断异样处理函数Default_Handler,并为Default_Handler的每个异样处理程序提供弱别名。所谓弱别名,即具备雷同名称的任何函数都将笼罩此处的函数。这样做能够避免用户使能了中断却没有设置中断处理程序时造成的解体。Default_Handler函数只是进入一个有限循环以保留零碎状态供调试器查看。

los_startup_gcc.S启动引导文件动静运行

当初咱们来单步调测运行los_startup_gcc.S,启动调测后,零碎会暂停在Reset_Handler函数的第一行代码cpsid i,此语句用来关中断,执行前后,察看寄存器primask值的变动,会发现由0变为1。继续执行语句" ldr sp, =_estack",同样察看寄存器,寄存器sp的值变动了。如下图:

持续运行单步调测,察看如何调用LoopCopyVectorInit和CopyVectorInit,实现把中断向量从Flash复制到RAM的。在调测过程中,寄存器的数值可能是10进制进行展现的,如果想查看其余进制展现的数值,能够在调测界面的监视器窗口输出$寄存器名称+进制代码来切换进制查看,如$r0,x来查看r0寄存器的16进制。具体的进制代码如下:

进制切换如图所示:

因为循环次数较多,如果想跨过中断向量的复制,持续上面的代码,能够设置断点,而后F5持续调测到断点处。如下图,咱们在118行设置了断点,继续执行会实现向量表的复制,去执行数据段data的初始化。

以此类推,通过Studio边调测、边剖析启动过程的后续代码。当执行到语句“bl main”,再按F11跳入继续执行时,就会跳转到C代码的main函数。下文持续剖析main函数。

main函数介绍

LiteOS的main函数定义在targetsSTM32F769IDISCOVERYSrcmain.c。main函数次要负责LiteOS的初始化工作。代码如下:

INT32 main(VOID){    HardwareInit();    PRINT_RELEASE("n********Hello Huawei LiteOS********n"                  "nLiteOS Kernel Version : %sn"                  "build data : %s %snn"                  "**********************************n",                  HW_LITEOS_KERNEL_VERSION_STRING, __DATE__, __TIME__);    UINT32 ret = OsMain();    if (ret != LOS_OK) {        return LOS_NOK;    }    OsStart();    return 0;}

硬件初始化函数HardwareInit()和次要芯片相干,这里不做具体介绍。上面介绍LiteOS内核的初始化,代码如下:

LITE_OS_SEC_TEXT_INIT UINT32 OsMain(VOID){    UINT32 ret;#ifdef LOSCFG_EXC_INTERACTION    ret = OsMemExcInteractionInit((UINTPTR)&__bss_end);    if (ret != LOS_OK) {        return ret;    }#endif    /* 初始化动态内存池 */    ret = OsMemSystemInit((UINTPTR)&__bss_end + g_excInteractMemSize);    if (ret != LOS_OK) {        return ret;    }    /*     * 配置最大反对的工作个数、信号量个数、互斥锁个数、     * 队列个数以及软件定时器个数,设置g_sysClock和     * g_tickPerSecond全局变量     */    OsRegister();#ifdef LOSCFG_SHELL_LK    OsLkLoggerInit(NULL);#endif#ifdef LOSCFG_SHELL_DMESG    ret = OsDmesgInit();    if (ret != LOS_OK) {        return ret;    }#endif/* * 初始化硬中断,尔后LiteOS就会接管零碎的中断, * 应用中断前须要先注册中断并使能 */    OsHwiInit();    /*     * 设置中断向量的中断处理函数,包含     * Hard Fault硬件故障中断、     * Non Maskable Interrupt不可屏蔽中断(NMI)、     * Memory Management内存治理中断、     * Bus Fault 总线故障中断、     * Usage Fault应用故障中断、     * SVCall利用SVC指令调用零碎服务的中断     */    ArchExcInit();    ret = OsTickInit(GET_SYS_CLOCK(), LOSCFG_BASE_CORE_TICK_PER_SECOND);    if (ret != LOS_OK) {        return ret;    }#ifdef LOSCFG_PLATFORM_UART_WITHOUT_VFS    uart_init();#ifdef LOSCFG_SHELL    extern int uart_hwiCreate(void); /* HuaWeiChange */    uart_hwiCreate();#endif /* LOSCFG_SHELL */#endif /* LOSCFG_PLATFORM_UART_WITHOUT_VFS */    /*     * 初始化工作链表包含工作的排序链表,     * 初始化优先级音讯队列链表(用于治理不同优先级工作)     */    ret = OsTaskInit();    if (ret != LOS_OK) {        PRINT_ERR("OsTaskInit errorn");        return ret;    }#ifdef LOSCFG_KERNEL_TRACE    ret = LOS_TraceInit(NULL, LOS_TRACE_BUFFER_SIZE);    if (ret != LOS_OK) {        PRINT_ERR("LOS_TraceInit errorn");        return ret;    }#endif/* * 初始化工作监视器 */#ifdef LOSCFG_BASE_CORE_TSK_MONITOR    OsTaskMonInit();#endif/* * OsIpcInit包含初始化音讯队列链表、互斥锁链表和信号量链表 */    ret = OsIpcInit();    if (ret != LOS_OK) {        return ret;    }    /*     * CPUP should be inited before first task creation which depends on the semaphore     * when LOSCFG_KERNEL_SMP_TASK_SYNC is enabled. So don't change this init sequence     * if not necessary. The sequence should be like this:     * 1. OsIpcInit     * 2. OsCpupInit -> has first task creation     * 3. other inits have task creation     */#ifdef LOSCFG_KERNEL_CPUP    ret = OsCpupInit();    if (ret != LOS_OK) {        PRINT_ERR("OsCpupInit errorn");        return ret;    }#endif/* * OsSwtmrInit对软件定时器和其在percpu上的排序链表进行初始化, * 并初始化定期器处理函数的内存池,同时还会创立软件定时器 * 的音讯队列和定时器工作 */#ifdef LOSCFG_BASE_CORE_SWTMR    ret = OsSwtmrInit();    if (ret != LOS_OK) {        return ret;    }#endif#ifdef LOSCFG_KERNEL_SMP    (VOID)OsMpInit();#endif#ifdef LOSCFG_KERNEL_DYNLOAD    ret = OsDynloadInit();    if (ret != LOS_OK) {        return ret;    }#endif#if defined(LOSCFG_HW_RANDOM_ENABLE) || defined (LOSCFG_DRIVERS_RANDOM)    random_alg_context.ra_init_alg(NULL);    run_harvester_iterate(NULL);#endif/* 创立闲暇工作 */    ret = OsIdleTaskCreate();    if (ret != LOS_OK) {        return ret;    }#ifdef LOSCFG_KERNEL_RUNSTOP    ret = OsWowWriteFlashTaskCreate();    if (ret != LOS_OK) {        return ret;    }#endif#ifdef LOSCFG_DRIVERS_BASE    ret = OsDriverBaseInit();    if (ret != LOS_OK) {        return ret;    }#ifdef LOSCFG_COMPAT_LINUX    (VOID)do_initCalls(LEVEL_ARCH);#endif#endif#ifdef LOSCFG_KERNEL_PERF    ret = LOS_PerfInit(NULL, LOS_PERF_BUFFER_SIZE);    if (ret != LOS_OK) {        return ret;    }#endif/* * LOSCFG_PLATFORM_OSAPPINIT宏默认曾经在.config、menuconfig.h中定义。 * 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;}

实现内核的初始化后,调用OsStart()开始任务调度,自此LiteOS开始失常工作。OsStart函数的代码如下:

LITE_OS_SEC_TEXT_INIT VOID OsStart(VOID){    LosTaskCB *taskCB = NULL; /* 获取以后执行工作的CPU ID,STM32F769是单核芯片,cpuid为0 */    UINT32 cpuid = ArchCurrCpuid();    /*     * 配置Tick中断向量,其中断处理函数为OsTickHandler。     * 初始化System Tick Timer及其中断,并启动此Timer。     * 计数器会产生周期性中断     */     OsTickStart();    LOS_SpinLock(&g_taskSpin);    /* 获取最高优先级工作队列中的第一个工作,赋给taskCB */    taskCB = OsGetTopTask();#ifdef LOSCFG_KERNEL_SMP    /*     * attention: current cpu needs to be set, in case first task deletion     * may fail because this flag mismatch with the real current cpu.     */    taskCB->currCpu = (UINT16)cpuid;#endif    /* 设置32位的调度flag,第CPU ID位设置为1 */    OS_SCHEDULER_SET(cpuid);    PRINTK("cpu %u entering schedulern", cpuid);    /*     * 调度g_runTask即taskCB工作,OsStartToRun函数     * 定义在los_dispatch.S汇编文件中     */    OsStartToRun(taskCB);}

main函数动静运行

当初咱们来单步调测运行main.c源代码,LiteOS Studio在调测时,能够同步展现以后运行的源代码行,及对应的反汇编文件行,如下图:

在调测过程中,变量的数值可能是10进制进行展现的,如果想查看其余进制展现的数值,能够在调测界面的监视器窗口输出变量名称名称+进制代码来切换进制查看,如memStart,x来查看变量memStart的16进制。如图:

本期分享应用LiteOS Studio查看LiteOS启动过程,同时展现了应用LiteOS Studio调测的技巧,大家能够持续边调测、边剖析后续的代码,会看到LiteOS整个启动流程:从板子复位上电开始,调用汇编代码Reset_Handler进入启动引导文件,实现C代码运行环境的筹备工作、最初跳转到main函数。在main函数中实现硬件初始化和LiteOS内核的初始化,并通过汇编跳转到执行第一个最高优先级的工作命令的地址上,从而开始LiteOS的运行。

欢送大家分享应用LiteOS Studio调测LiteOS的心得,有任何问题、倡议,都能够在开源LiteOS社区(https://gitee.com/liteos)留言,谢谢!

本文分享自华为云社区《应用LiteOS Studio图形化查看LiteOS在STM32上运行的神秘》,原文作者:zhushy 。

点击关注,第一工夫理解华为云陈腐技术~