摘要:本文带来基于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/liteos_studio/#/project_stm32

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

重新开始调测,系统会暂停在Reset_Handler函数处。如下图:

los_startup_gcc.S启动引导文件介绍

当对STM32F769IDISCOVERY开发板进行上电操作或者复位操作时,该开发板会从异常向量表中获取Reset_Handler函数的地址并执行该函数。汇编文件targets\STM32F769IDISCOVERY\los_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,这些符号都定义在targets\STM32F769IDISCOVERY\liteos.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函数定义在targets\STM32F769IDISCOVERY\Src\main.c。main函数主要负责LiteOS的初始化工作。代码如下:

INT32 main(VOID)
{
HardwareInit(); PRINT_RELEASE("\n********Hello Huawei LiteOS********\n"
"\nLiteOS Kernel Version : %s\n"
"build data : %s %s\n\n"
"**********************************\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 error\n");
return ret;
} #ifdef LOSCFG_KERNEL_TRACE
ret = LOS_TraceInit(NULL, LOS_TRACE_BUFFER_SIZE);
if (ret != LOS_OK) {
PRINT_ERR("LOS_TraceInit error\n");
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 error\n");
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 scheduler\n", 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 。

点击关注,第一时间了解华为云新鲜技术~

使用LiteOS Studio图形化查看LiteOS在STM32上运行的奥秘的更多相关文章

  1. 物联网打工人必备:LiteOS Studio图形化调测能力

    摘要:本文会给大家介绍下LiteOS Studio的调测的几个知识点,包括: 调测配置,监视变量,反汇编代码同步展示,数值进制切换,跨平台编译调测,Qemu模拟器调测,多核调测,远程设备调测等. 掌握 ...

  2. Android Studio 图形化设计 UI 界面

    我们开发 Android 程序必定是从 UI 开始的 ,使用最新版的 Android Studio 可以在图形化界面下设计软件 UI, Android Studio 默认的布局是 Constraint ...

  3. 图形化查看maven的dependency依赖

    开发项目的时候,我们想知道一个maven的依赖库是来自那个工程,eclipse有插件可以直接看Dependency Hierarchy,推荐一个第三方的工具yED 在工程的pom.xml文件中添加如下 ...

  4. Caffe学习系列(21):caffe图形化操作工具digits的安装与运行

    经过前面一系列的学习,我们基本上学会了如何在linux下运行caffe程序,也学会了如何用python接口进行数据及参数的可视化. 如果还没有学会的,请自行细细阅读: caffe学习系列:http:/ ...

  5. Visual Studio写的项目在 IIS 服务器上运行的两种简单方法

    首先需要PC上开启了IIS服务,相关方法网上很多,也很简单 第一种:直接在项目中操作 1.创建一个项目,然后右击选中项目,右击,单击属性,打开项目属性标签页面 如图,选择Web标签,在服务器栏目中选中 ...

  6. 利用Xmanager Enterprise 5的passive显示远程linux主机图形化信息

    问题描述: 最初的需求是,安装oracle数据(第一次安装都是图形化linxu进去一步步操作,后续发现可以命令静默安装不调用图形化,学习就是步步入深,方得始终),最初实现window弹出linux主机 ...

  7. Caffe学习系列(22):caffe图形化操作工具digits运行实例

    上接:Caffe学习系列(21):caffe图形化操作工具digits的安装与运行 经过前面的操作,我们就把数据准备好了. 一.训练一个model 右击右边Models模块的” Images" ...

  8. 给Linux装图形化界面

    在工作中遇到这种情况,安装confluence服务的时候需要调用图形化桌面,但是原来装系统的时候是最小化装的,所以这里就麻烦了 给装图形化吧 在我们安装Linux系统时,刚开始的时候可能没有安装图形界 ...

  9. 华为方舟编译器 下载 和 LiteOS Studio Setup 2019-04-16.exe SDK下载

    华为方舟编译器是首个取代Android虚拟机模式的静态编译器,可供开发者在开发环境中一次性将高级语言编译为机器码.此外,方舟编译器未来将支持多语言统一编译,可大幅提高开发效率. 编译器下载 [Ark] ...

  10. ROS通过图形化界面控制和查看小乌龟参数

    ROS图形化界面能够让我们快速开发ROS,也有利于我们观测数据. 下面介绍一下利用图形化界面控制小乌龟按照指令行进和查看小乌龟的行进参数. 首先我们需要做一些准备工作: 在Terminal中运行以下命 ...

随机推荐

  1. kubernetes发布周期

    前言 页面介绍了版本发布的一些时间点和PR的要求,通过了解k8s的发布周期来规划自己的版本选择. 合并PR的要求 如果你希望将你的代码合并到官方代码仓库中,不同的开发阶段需要有不同的标签和里程碑.也是 ...

  2. Redis 6 学习笔记 3 —— 用SpringBoot整合Redis的踩坑,了解事务、乐观锁、悲观锁

    SpringBoot整合Redis时踩到的坑 jdk1.8环境,用idea的Spring Initializr创建spring boot项目,版本我选的2.7.6.pom文件添加的依赖如下,仅供参考. ...

  3. 从零开始搭建antd4.x + react16 + redux4 + webpack4 + react-router5基础框架解析

    以上是2020年10月份的版本,后来,我将xmind进行了完善,文档也写的差不多了,可是,电脑坏了,硬盘换了,文件都没有了.这已经是第三次写这个文档了,思维导图就不更新了,按照几个重点进行说明. 这个 ...

  4. JavaScript:对象的三个属性

    每一个对象都有与之相关的原型(prototype).类(class)和可扩展性(extension attribute). 原型 prototype 对象的原型属性是用来继承属性的.通过对象直接量创建 ...

  5. games101-1 光栅化与光线追踪中的空间变换

    在学习了一些games101的课程之后,我还是有点困惑,对于计算机图形学的基础知识,总感觉还是缺乏一些更加全面的认识,幸而最*在做games101的第五次作业时,查询资料找到了scratchpixel ...

  6. 搞懂 RESTful API

    https://apifox.com/blog/a-cup-of-tea-time-to-understand-restful-api/ 什么是 RESTful API 在互联网并没有完全流行的初期, ...

  7. 【pwn】[MoeCTF 2022]babyfmt --格式化字符串漏洞,got表劫持

    拿到程序,先checksec一下 发现是Partial RELRO,got表可修改 当RELRO保护为NO RELRO的时候,init.array.fini.array.got.plt均可读可写:为P ...

  8. rancher安装及部署k8s

    一.安装docker 参考:https://www.cnblogs.com/uestc2007/p/15598527.html 二.安装rancher 1.Rancher概述 rancher官方文档 ...

  9. 持续集成指南:GitHubAction 自动构建+部署AspNetCore项目

    前言 之前研究了使用 GitHub Action 自动构建和发布 nuget 包:开发现代化的.NetCore控制台程序:(4)使用GithubAction自动构建以及发布nuget包 现在更进一步, ...

  10. 栈与队列应用:逆波兰计算器(逆波兰表达式;后缀表达式)把运算符放到运算量后边 && 中缀表达式转化为后缀表达式

    1 //1.实现对逆波兰输入的表达式进行计算如(2-1)*(2+3)= 5 就输入2 1 - 2 3 + * //先把2 1 压栈 遇到-弹栈 再把2 3压进去 遇到+弹栈 最后遇到*弹栈 2 //2 ...