OpenHarmonyLiteOS-M内核启动流程
Posted Top嵌入式
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenHarmonyLiteOS-M内核启动流程相关的知识,希望对你有一定的参考价值。
文章目录
【OpenHarmony】LiteOS-M内核启动流程
摘要
前一节我们从官方仓库移植了 OpenHarmony 到 STM32F407ZGT6 上,然后新建了一个测试任务用于测试,本节我结合源码,分析一下上一节的工程代码上电后,单片机初始化内核到启动内核的一个过程,本节有如下几个关键点
- 了解 LiteOS-M 内核代码如何启动?
- LiteOS-M 代码架构如何?
- 单片机在执行操作系统前进行了哪些准备?
一、LiteOS 内核框架
首先先看一下 LiteOS 的内核框架,如下:
最底层是芯片的硬件架构适配,使用汇编代码适配各种芯片架构,比如 Cortex-M3、Cortex-M4、Risc-V等等
中间层是系统的内核,左边是基础内核是系统运行所具备的基础特性,一般一个 RTOS 都会有这些特性,右边是扩展的特性
最上层是抽象层,提供了 CMSIS 和 POSIX 接口,用于提供对外的接口,这套接口的 API是固定的,但 API 的底层则是调用 LiteOS 的内核函数,我们开发使用 CMSIS 的 API 进行开发,如果需要换 RTOS 开发,直接换 CMSIS 的底层接口就行,这样方便我们将代码从一个 RTOS 移植到另外一个 RTOS
一般常用的是 CMSIS-RTOS 接口,这套接口 Mculover666 写过详细的文章,我们不重复造轮子,请参考他的文章来使用 CMSIS 接口:RTOS内功修炼记(八)——CMSIS RTOS API,内核通用API接口
之前我们移植了 LiteOS-M 到 STM32F407ZGT6 单片机上,移植链接:【OpenHarmony】移植 3.1 版本系统到 STM32
移植的工程创建了一个简单的串口任务用于任务调度,本章将基于上一节移植的工程,简单的分析一下 STM32 单片机上电后,是如何运行到 LiteOS-M 任务调度内核那一块代码的
二、程序运行环境准备(上电执行)
当我们按下 STM32 的单片机后,第一时间,单片机执行的不是我们写的 C 语言代码,而是一段汇编代码,我们一般叫他启动文件,我们上一节工程的代码启动文件是 startup_stm32f407xx.s
文件,文件如下,可以看到他是一段汇编代码:
单片机上电首先执行的就是这个汇编文件,可能你会有疑问为什么单片机上电先执行这段汇编,其他汇编文件不行嘛?这个是因为单片机上电后,执行的程序地址是固定的,由单片机的 PC 指针决定,而PC指针刚上的是由硬件复位,指向了固定地址,而工程文件中有一个链接文件,将汇编文件中的 Reset_Handler 函数放在了 PC 第一个执行地址,所以单片机上电后硬件初始化完 PC 和 SP 后,就会执行 Reset_Handler 了,Reset_Handler 再启动文件的位置如下:
他设置的 SP 堆栈指针的值,然后将数据段的起始地址传给 R0-2 寄存器,用于传参数给下一个调用函数,也就是 LoopCopyDataInit 函数,用来拷贝具有非 0 初始化值的变量到 SRAM,因为代码中的变量一开始全在 Flash 中,我们需要拷贝到 SRAM 运行,之后再清除未初始化数据段(.bss),初始化完成后,调用 SystemInit 来初始化单片机的 FPU 和时钟:
初始化完成后调用 __libc_init_array 来初始化 lib 库,初始化完成后就会跳转到 main 函数了,就可以愉快的运行 C 语言代码了!
可能有同学会问为什么这个启动文件和我再 MDK 里面看到的启动文件有差别,MDK 的还调用了 __main 函数,其实 __main 函数和上面时钟初始化前做的内容都差不多,只不过我这编译器是 GCC,MDK 是 ARMCC,有些许差别正常,如果一样就见鬼了!
好了,下面分析一下 main 函数做了啥!
三、外设复位及关键外设初始化
首先是一个 HAL_Init 函数,复位所有的外设,对 Flash 读写进行初始化以及配置内核定时器:
函数内容如下:
四、时钟树及片上外设配置
在上面的的复位和初始化完成后,下面我们又要对时钟进行一次初始化,这次时钟初始化较为完整,将整个 MCU 的时钟树进行初始化,这里的初始化代码由 CubeMX 生成,配置完成的时钟树如下:
时钟树初始化完成后就算对片上外设的初始化,比如这里我初始化了串口、DMA以及两个GPIO口:
这里单片机初始化部分基本上完成了,下面就是进入到 LiteOS-M 的内核初始化
五、内核初始化
5.1 基本内核功能初始化
内核我使用的 CMSIS-RTOS 接口进行配置,我按照 CMSIS 接口里面的代码逻辑顺序分析,先看一下内核基本初始化函数
先调用内核初始化函数进行初始化:
初始化函数判断当前如果系统没激活,且调用的位置不在中断中,就会调用 LiteOS-M 的初始化函数:
我们好好看一下 LOS_KernelInit 这个初始化函数
先打印一段提示:
PRINTK("entering kernel init...\\r\\n");
调用 OsRegister 函数设置最大的任务数量:
LITE_OS_SEC_TEXT_INIT static VOID OsRegister(VOID)
g_taskMaxNum = LOSCFG_BASE_CORE_TSK_LIMIT + 1; /* Reserved 1 for IDLE */
return;
下面的代码就是调用内存初始化函数初始化堆,OsMemSystemInit 初始化内容就是获取堆的首地址,然后根据设置的大小分配内存:
ret = OsMemSystemInit();
if (ret != LOS_OK)
PRINT_ERR("OsMemSystemInit error %d\\n", ret);
return ret;
之后的初始化是跟底层架构相关的初始化函数,该函数调用了硬件中断初始化,如果我们使能了内核中断接管,则会在这个函数进行初始化,架构相关初始化函数:
LITE_OS_SEC_TEXT_INIT VOID ArchInit(VOID)
HalHwiInit();
芯片架构初始化完成后,下面的代码就是系统时基任务初始化,因为系统的正常运行离不开由内核定时器提供的时间基准,时间基准的维护由其相关时基任务进行,时基初始化就是配置相关任务,设置时基的一些相关参数:
ret = OsTickTimerInit();
if (ret != LOS_OK)
PRINT_ERR("OsTickTimerInit error! 0x%x\\n", ret);
return ret;
之后调用 OsTaskInit 初始化任务列表,复位相关结构体的参数,调用的函数如下:
ret = OsTaskInit();
if (ret != LOS_OK)
PRINT_ERR("OsTaskInit error\\n");
return ret;
任务初始化完成后,接着初始化 IPC 通信的相关内容,分别初始化信号量、互斥量、队列:
#if (LOSCFG_BASE_IPC_SEM == 1)
ret = OsSemInit();
if (ret != LOS_OK)
return ret;
#endif
#if (LOSCFG_BASE_IPC_MUX == 1)
ret = OsMuxInit();
if (ret != LOS_OK)
return ret;
#endif
#if (LOSCFG_BASE_IPC_QUEUE == 1)
ret = OsQueueInit();
if (ret != LOS_OK)
PRINT_ERR("OsQueueInit error\\n");
return ret;
#endif
之后初始化软件定时器,OsSwtmrInit 内容就是将软件定时器列表初始化,同时创建一个软件定时器守护线程,用于维护软件定时器
#if (LOSCFG_BASE_CORE_SWTMR == 1)
ret = OsSwtmrInit();
if (ret != LOS_OK)
PRINT_ERR("OsSwtmrInit error\\n");
return ret;
#endif
最后创建空闲任务:
ret = OsIdleTaskCreate();
if (ret != LOS_OK)
return ret;
LiteOS-M 内核的基础功能初始化流程图如下:
5.2 其他系统功能初始化
以上就是系统运行的几个关键初始化函数,除此之外还有一些其他的可裁剪功能:
下面代码判断栈回溯宏定义有没有开启,开启了就初始化栈回溯,将回溯的hook关联上去,这样就会执行相关函数,这里我把这个功能裁剪了:
#if (LOSCFG_BACKTRACE_TYPE != 0)
OsBackTraceInit();
#endif
下面一段代码则是用于 LMS 模块裁剪控制,该模块可以检测memcpy、memmove、strcat、strcpy、memcpy_s、memmove_s、strcat_s、strcpy_s 这些函数的使用是否会引入内存问题,这里我没有开启:
LMS 的初始化就是初始化控制句柄,为它分配好内存,然后指向对应的函数实体:
VOID OsLmsInit(VOID)
memset(g_lmsCheckPoolArray, 0, sizeof(g_lmsCheckPoolArray));
LOS_ListInit(&g_lmsCheckPoolList);
static LmsHook hook =
.init = LOS_LmsCheckPoolAdd,
.mallocMark = OsLmsLosMallocMark,
.freeMark = OsLmsLosFreeMark,
.simpleMark = OsLmsSimpleMark,
.check = OsLmsCheckValid,
;
g_lms = &hook;
还有一些其他的功能,这里不具体展开,这些功能没有使用到,都裁剪了
六、启动系统(开始调度)
在内核初始化完成后,下面我们创建我们需要的内核对象,比如创建一个串口任务
osThreadId_t uart_taskHandle;
const osThreadAttr_t uart_task_attributes =
.name = "uart_task",
.stack_size = 512 * 4,
.priority = (osPriority_t)osPriorityNormal3,
;
uart_taskHandle = osThreadNew(Uart_Task, NULL, &uart_task_attributes);
创建完任务后,开启启动内核,运行任务,启动接口如下
osKernelStart();
该函数调用的底层函数为 LOS_Start()
LITE_OS_SEC_TEXT_INIT UINT32 LOS_Start(VOID)
return ArchStartSchedule();
该函数就是,查找任务就绪列表最高优先级任务,然后立即进行任务调度,之后内核就开启运行了
LITE_OS_SEC_TEXT_INIT UINT32 ArchStartSchedule(VOID)
(VOID)LOS_IntLock();
OsSchedStart();
HalStartToRun();
return LOS_OK; /* never return */
代码工程仓库:Gitee
以上是关于OpenHarmonyLiteOS-M内核启动流程的主要内容,如果未能解决你的问题,请参考以下文章