IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【OpenHarmony】LiteOS-M内核启动流程 -> 正文阅读

[嵌入式]【OpenHarmony】LiteOS-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

接口原理-图源 Mculover666

一般常用的是 CMSIS-RTOS 接口,这套接口 Mculover666 写过详细的文章,我们不重复造轮子,请参考他的文章来使用 CMSIS 接口:RTOS内功修炼记(八)——CMSIS RTOS API,内核通用API接口

之前我们移植了 LiteOS-M 到 STM32F407ZGT6 单片机上,移植链接:【OpenHarmony】移植 3.1 版本系统到 STM32

移植的工程创建了一个简单的串口任务用于任务调度,本章将基于上一节移植的工程,简单的分析一下 STM32 单片机上电后,是如何运行到 LiteOS-M 任务调度内核那一块代码的

二、程序运行环境准备(上电执行)

当我们按下 STM32 的单片机后,第一时间,单片机执行的不是我们写的 C 语言代码,而是一段汇编代码,我们一般叫他启动文件,我们上一节工程的代码启动文件是 startup_stm32f407xx.s文件,文件如下,可以看到他是一段汇编代码:

20220412213112

单片机上电首先执行的就是这个汇编文件,可能你会有疑问为什么单片机上电先执行这段汇编,其他汇编文件不行嘛?这个是因为单片机上电后,执行的程序地址是固定的,由单片机的 PC 指针决定,而PC指针刚上的是由硬件复位,指向了固定地址,而工程文件中有一个链接文件,将汇编文件中的 Reset_Handler 函数放在了 PC 第一个执行地址,所以单片机上电后硬件初始化完 PC 和 SP 后,就会执行 Reset_Handler 了,Reset_Handler 再启动文件的位置如下:

20220412214147

他设置的 SP 堆栈指针的值,然后将数据段的起始地址传给 R0-2 寄存器,用于传参数给下一个调用函数,也就是 LoopCopyDataInit 函数,用来拷贝具有非 0 初始化值的变量到 SRAM,因为代码中的变量一开始全在 Flash 中,我们需要拷贝到 SRAM 运行,之后再清除未初始化数据段(.bss),初始化完成后,调用 SystemInit 来初始化单片机的 FPU 和时钟:

20220412215452

初始化完成后调用 __libc_init_array 来初始化 lib 库,初始化完成后就会跳转到 main 函数了,就可以愉快的运行 C 语言代码了!

可能有同学会问为什么这个启动文件和我再 MDK 里面看到的启动文件有差别,MDK 的还调用了 __main 函数,其实 __main 函数和上面时钟初始化前做的内容都差不多,只不过我这编译器是 GCC,MDK 是 ARMCC,有些许差别正常,如果一样就见鬼了!

好了,下面分析一下 main 函数做了啥!

三、外设复位及关键外设初始化

首先是一个 HAL_Init 函数,复位所有的外设,对 Flash 读写进行初始化以及配置内核定时器:

20220412221425

函数内容如下:

20220412221838

四、时钟树及片上外设配置

在上面的的复位和初始化完成后,下面我们又要对时钟进行一次初始化,这次时钟初始化较为完整,将整个 MCU 的时钟树进行初始化,这里的初始化代码由 CubeMX 生成,配置完成的时钟树如下:

20220412222613

时钟树初始化完成后就算对片上外设的初始化,比如这里我初始化了串口、DMA以及两个GPIO口:

20220412222800

这里单片机初始化部分基本上完成了,下面就是进入到 LiteOS-M 的内核初始化

五、内核初始化

5.1 基本内核功能初始化

内核我使用的 CMSIS-RTOS 接口进行配置,我按照 CMSIS 接口里面的代码逻辑顺序分析,先看一下内核基本初始化函数

先调用内核初始化函数进行初始化:

20220412223344

初始化函数判断当前如果系统没激活,且调用的位置不在中断中,就会调用 LiteOS-M 的初始化函数:

20220412223602

我们好好看一下 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 内核的基础功能初始化流程图如下:

20220413195443

5.2 其他系统功能初始化

以上就是系统运行的几个关键初始化函数,除此之外还有一些其他的可裁剪功能:

下面代码判断栈回溯宏定义有没有开启,开启了就初始化栈回溯,将回溯的hook关联上去,这样就会执行相关函数,这里我把这个功能裁剪了:

#if (LOSCFG_BACKTRACE_TYPE != 0)
    OsBackTraceInit();
#endif

下面一段代码则是用于 LMS 模块裁剪控制,该模块可以检测memcpy、memmove、strcat、strcpy、memcpy_s、memmove_s、strcat_s、strcpy_s 这些函数的使用是否会引入内存问题,这里我没有开启:

20220412225924

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

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:58:47  更:2022-04-18 18:02:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:51:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码