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内核启动流程的主要内容,如果未能解决你的问题,请参考以下文章

OpenHarmonyLiteOS-M 源码目录

OpenHarmonyLiteOS-M 源码目录

Linux 内核启动流程

内核启动流程

Tiny4412 Linux 内核启动流程

启动流程排错,自制linux内核,编译内核