共计 8024 个字符,预计需要花费 21 分钟才能阅读完成。
摘要:本文带来基于 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 LoopCopyVectorInit
CopyVectorInit:
ldr r3, =_si_liteos_vector_data
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyVectorInit:
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 LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
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], #4
LoopFillZerobss:
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。
点击关注,第一工夫理解华为云陈腐技术~