使用LiteOSStudio揭示LiteOS在STM32上的运行LiteOSStudio是LiteOS的一站式开发工具,通过单步调试动态分析LiteOS的启动过程,给开发者更直观的展示。要了解LiteOS系统,我们可以从它的启动过程说起。不同的芯片和编译工具在启动过程中可能会有一些差异。本文基于码云LiteOS开源站master分支12月代码。以STM32F769IDISCOVERY(ARMCortexM7)开发板和GCC编译工具为例,使用LiteOSStudio的单步调试,动态分析LiteOS启动过程。1.LiteOSStudio环境准备在开始之前,需要准备LiteOSStudio环境,包括LiteOSStudio的安装、新建工程、编译、烧录、掌握如何调试LiteOSStudio等,可以参考官网文档站点https://liteos。gitee.io/liteos_studio/#/project_stm32。LiteOSStudio开发环境搭建请参考搭建Windows开发环境。如何为STM32F769IDISCOVERY创建一个新的LiteOS工程请参考新建工程。如何编译、烧录、调试分别参考编译配置-编译代码、烧录配置-烧录、调试器-执行调试。注意,如果开发板使用板载ST-LINK仿真器,需要刷成JLINK。请参考st-link仿真器单步调试。另外,单步调试的执行默认停在main()函数。LiteOS操作系统的启动是从main函数开始的。ARMCortex-M芯片从上电到执行主函数要经过Reset_Handler等函数。LiteOS系统重启、复位等都是从Reset_Handler函数中执行的。在LiteOSStudio工程中找到.vscode\launch.json文件,将postLaunchCommands属性下的“bmain”修改为“bReset_Handler”。如下图:重启调试,系统会暂停在Reset_Handler函数处。如下图所示:2.los_startup_gcc.S启动引导文件介绍当STM32F769IDISCOVERY开发板上电或复位时,开发板会从异常向量表中获取Reset_Handler函数的地址并执行该函数。汇编文件targets\STM32F769IDISCOVERY\los_startup_gcc.S定义了这个函数。los_startup_gcc.S是启动引导文件。从Reset_Handler到执行main函数,主要工作是准备C代码的运行环境,具体包括:设置栈指针SP,对应语句ldrsp,=_estack。初始化中断向量,对应函数LoopCopyVectorInit。初始化数据段,对应函数LoopCopyDataInit。初始化bss段,对应函数LoopF??illZerobss。初始化系统时钟并跳转到函数SystemInit。跳转到C代码函数main代码如下:Reset_Handler:cpsidildrsp,=_estack/*setstackpointer*//*Copythevector_ramsegmentinitializersfromflashtoSRAM*/movsr1,#0bLoopCopyVectorInitCopyVectorInit:ldrr3,=_si_liteos_vector_dataldrr3,[r3,r1]strr3,[r0,r1]添加r1,r1,#4LoopCopyVectorInit:ldrr0,=_s_liteos_vectorldrr3,=_e_liteos_vector添加r2,r0,r1cmpr2,r3bccCopyVectorInit/*将数据段初始值从闪存复制到SRAM*/movsr1,#0bLoopCopyDataInitCopyDataInit:ldrr3,=_sidataldrr3,[r3,r1]strr3,[r0,r1]addsr1,r1,#4LoopCopyDataInit:ldrr0,=_sdataldrr3,=_edataaddsr2,r0,r1cmpr2,r3bccCopyDataInitldrr2,=_sbssbLoopF??illZerobss/*零填充bss段。*/FillZerobss:movsr3,#0strr3,[r2],#4LoopF??illZerobss:ldrr3,=_ebsscmpr2,r3bccFillZerobss/*调用时钟系统初始化函数*/blSystemInit/*调用静态构造函数*//*bl__libc_init_array*//*调用应用程序的入口点*/blmainbxlrData段存放初始化的全局变量,需要从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\liteos.ld链接脚本中。链接脚本根据应用需要设置堆栈大小和堆栈地址,控制每个段的存储位置。至于中断向量和数据段,需要同时放在Flash和RAM中,通过链接脚本的AT关键字设置Flash的地址作为加载地址。注:链接脚本中的相关代码可以查看https://gitee.com/LiteOS/LiteOS/blob/master/targets/STM32F769IDISCOVERY/liteos.ld。在los_startup_gcc.S启动引导文件中,除了定义了Reset_Handler函数外,还定义了其他中断异常处理函数Default_Handler,并为Default_Handler的各个异常处理器提供了弱别名。所谓弱别名,即任何同名函数都会覆盖这里的函数。这样做可以防止因用户启用中断但未设置中断处理程序而导致的崩溃。Default_Handler函数只是进入一个无限循环以保存系统状态以供调试器检查。3、los_startup_gcc.S启动引导文件动态运行。下面我们一步步调试运行los_startup_gcc.S。开始测试后,系统会在Reset_Handler函数中的第一行代码cpsidi处暂停。该语句用于禁用中断。执行前后,观察寄存器primask的值变化,会发现从0变为1。继续执行语句“ldrsp,=_estack”,同样观察寄存器,寄存器sp的值发生了变化。如下图所示:继续运行单步调试,观察如何调用LoopCopyVectorInit和CopyVectorInit将中断向量从Flash复制到RAM。在调试过程中,寄存器的值可能以十进制显示。如果想查看其他基数显示的值,可以在调试界面的监控窗口输入r0,x,查看r0寄存器的16进制数。详细的base代码如下:xhexadecimalsigneddecimaluunsigneddecimaoctaltbinaryaaddressbaseswitch如图:由于循环次数较多,如果想跳过中断向量的拷贝,继续下面的代码,可以设置断点,然后F5继续调试到断点处。如下图所示,我们在118行设置断点,继续执行会完成向量表的拷贝,执行数据段数据的初始化。以此类推,使用Studio调试分析启动过程的后续代码。当执行完“blmain”语句后,再按F11跳转继续执行,就会跳转到C代码的main函数。下面继续分析main函数。4、主要功能介绍LiteOS的主要功能定义在targets\STM32F769IDISCOVERY\Src\main.c中。main函数主要负责LiteOS的初始化。代码如下:INT32main(VOID){HardwareInit();PRINT_RELEASE("\n********HelloHuaweiLiteOS********\n""\nLiteOS内核版本:%s\n""构建数据:%s%s\n\n”“****************************************************************n",HW_LITEOS_KERNEL_VERSION_STRING,__DATE__,__TIME__);UINT32ret=OsMain();if(ret!=LOS_OK){returnLOS_NOK;}OsStart();return0;}硬件初始化函数HardwareInit()与主芯片相关,这里不做详细介绍。下面介绍LiteOS内核的初始化,代码如下:LITE_OS_SEC_TEXT_INITUINT32OsMain(VOID){UINT32ret;#ifdefLOSCFG_EXC_INTERACTIONret=OsMemExcInteractionInit((UINTPTR)&__bss_end);if(ret!=LOS_OK){返回ret;}#endif/*初始化动态内存池*/ret=OsMemSystemInit((UINTPTR)&__bss_end+g_excInteractMemSize);if(ret!=LOS_OK){返回ret;/**配置最大支持的任务数、信号量和互斥量、*队列数和软件定时器数,设置g_sysClock和*g_tickPerSecond全局变量*/OsRegister();#ifdefLOSCFG_SHELL_LKOsLkLoggerInit(NULL);#endif#ifdefLOSCFG_SHELL_DMESGret=OsDmesgInit();if(ret!=LOS_OK){returnret;}#endif/**初始化硬中断,然后LiteOS会接管系统中断,*在使用中断之前,需要注册中断并启用*/OsHwiInit();/**设置中断向量中断处理函数,包括*HardFault硬件故障中断,*NonMaskableInterrupt不可屏蔽中断(NMI),*MemoryManagement内存管理中断,*BusFault总线故障中断,*UsageFault使用故障interrupt,*SVCall使用SVC指令调用系统服务Interrupt*/ArchExcInit();ret=OsTickInit(GET_SYS_CLOCK(),LOSCFG_BASE_CORE_TICK_PER_SECOND);if(ret!=LOS_OK){返回ret;}#ifdefLOSCFG_PLATFORM_UART_WITHOUT_VFSuart_init();#ifdefLOSCFG_SHELLexternintuart_hwiCreate(void);/*HuaWeiChange*/uart_hwiCreate();#endif/*LOSCFG_SHELL*/#endif/*LOSCFG_PLATFORM_UART_WITHOUT_VFS*//**初始化任务列表,包括已排序的任务列表,*初始化优先消息队列列表(用于管理任务与不同的优先级)*/ret=OsTaskInit();if(ret!=LOS_OK){PRINT_ERR("OsTaskInit错误\n");返还;}#ifdefLOSCFG_KERNEL_TRACEret=LOS_TraceInit(NULL,LOS_TRACE_BUFFER_SIZE);if(ret!=LOS_OK){PRINT_ERR("LOS_TraceInit错误\n");return}#endif/**初始化任务监视器*/#ifdefLOSCFG_BASE_CORE_TSK_MONITOROsTaskMonInit();#endif/**OsIpcInit包括初始化消息队列列表、互斥锁列表和信号量列表*/ret=OsIpcInit();if(ret!=LOS_OK){返回ret;}/**CPUP应该在第一个任务创建之前初始化,这取决于信号量*whenLOSCFG_KERNEL_SMP_TASK_SYNC已启用。因此,如无必要,请勿更改此初始化序列*。序列应该是这样的:*1.OsIpcInit*2.OsCpupInit->有第一个任务创建*3.其他inits有任务创建*/#ifdefLOSCFG_KERNEL_CPUPret=OsCpupInit();if(ret!=LOS_OK){PRINT_ERR("OsCpupInit错误\n");返还;}#endif/**OsSwtmrInit在percpu上初始化软件定时器及其排序链表,*并初始化定时定时器处理函数的内存池,同时创建软件定时器的消息队列和定时器任务**/#ifdefLOSCFG_BASE_CORE_SWTMRret=OsSwtmrInit();if(ret!=LOS_OK){返回ret;}#endif#ifdefLOSCFG_KERNEL_SMP(VOID)OsMpInit();#endif#ifdefLOSCFG_KERNEL_DYNLOADret=OsDynloadInit();if(ret!=LOS_OK){返回ret;}#endif#ifdefined(LOSCFG_HW_RANDOM_ENABLE)||ra_init_alg(NULL);run_harvester_iterate(NULL);#endif/*创建空闲任务*/ret=OsIdleTaskCreate();如果(重新!=LOS_OK){返回ret;}#ifdefLOSCFG_KERNEL_RUNSTOPret=OsWowWriteFlashTaskCreate();if(ret!=LOS_OK){返回ret;}#endif#ifdefLOSCFG_DRIVERS_BASEret=OsDriverBaseInit();if(ret!=LOS_OK){返回ret;}#ifdefLOSCFG_COMPAT_LINUX(VOID)do_initCalls(LEVEL_ARCH);#endif#endif#ifdefLOSCFG_KERNEL_PERFret=LOS_PerfInit(NULL,LOS_PERF_BUFFER_SIZE);if(ret!=LOS_OK){返回ret;,*menuconfig.h中定义的OsAppInit创建一个名为“app_Task”的任务,任务处理函数为*app_init,任务优先级为10;*OsTestInit创建一个名为“IT_TST_IN”的任务,任务处理函数为*TestTaskEntry,任务优先级为25。该函数尚未开源。*/#ifdefLOSCFG_PLATFORM_OSAPPINITret=osAppInit();#else/*LOSCFG_TEST*/ret=OsTestInit();#endifif(ret!=LOS_OK){返回ret;}returnLOS_OK;}内核初始化完成后,调用OsStart()开始任务调度,自此LiteOS开始正常工作。OsStart函数的代码如下:LITE_OS_SEC_TEXT_INITVOIDOsStart(VOID){LosTaskCB*taskCB=NULL;/*获取当前执行任务的CPUID,STM32F769为单核芯片,cpuid为0*/UINT32cpuid=ArchCurrCpuid();/**配置Tick中断向量,其中断处理程序为OsTickHandler。*初始化SystemTickTimer及其中断,并启动这个Timer。*计数器会产生周期性中断*/OsTickStart();LOS_SpinLock(&g_taskSpin);/*获取最高优先级任务队列中的第一个任务并将其分配给taskCB*/taskCB=OsGetTopTask();#ifdefLOSCFG_KERNEL_SMP/**注意:需要设置当前cpu,以防第一个任务删除*可能会失败,因为这个标志与实际当前cpu不匹配。*/taskCB->currCpu=(UINT16)cpuid;#endif/*设置32位调度标志,第一个CPUID位设置为1*/OS_SCHEDULER_SET(cpuid);PRINTK("cpu%u进入调度程序\n",cpuid);/**调度g_runTask为taskCB任务,OsStartToRun函数*定义在los_dispatch.S汇编文件中*/OsStartToRun(taskCB);}Copy5.main函数动态运行下面我们调试运行main.c源码一步步。在调试时,LiteOSStudio可以同时显示当前运行的源代码行和对应的反汇编文件OK,如下图所示:调试过程中,变量的值可能以十进制显示。如果想查看其他基地显示的数值,可以在调试界面的监控窗口输入变量名+十进制代码。切换十六进制,比如memStart,x查看变量memStart的十六进制。如图:总结本期分享LiteOSStudio的使用,查看LiteOS的启动过程,同时展示使用LiteOSStudio的调试技巧。可以继续调试分析后面的代码,会看到LiteOS的整个启动过程:从板子复位上电后,调用汇编代码Reset_Handler进入启动引导文件,完成C代码的准备运行环境,最后跳转到main函数。在main函数中完成硬件初始化和LiteOS内核初始化,通过汇编跳转到执行优先级最高的第一条任务命令的地址,启动LiteOS的运行。了解更多开源知识,请访问:开源基础软件社区https://ost.51cto.com。
