image.png 说起来RTOS,第一印象就是单片机程序、ARM M核、微控制器低频运行处理程序,甚至其不能算一个真正的OS。但是随着时代发展,这些单片机程序在有限的硬件机制下非常接近像Linux这种的巨无霸OS了,例如其有线程、中断、内存管理、IPC、驱动、网络协议栈、shell、提供POSIX接口等,可以说其已经是一个缩小版的OS了,甚至在A核也可以运行,简单修改下添加一点用户空间支持等特性就可以。

在嵌入式领域中,定制化的软硬件,并且没有界面展示的时候,并不需要ARM A核这种高级的CPU,M核是一个更好的选择,其便宜,小,省电,可靠性高,偏向专用控制,但是频率低,功能进行了阉割。在其上运行的RTOS也有对应的特点:

  1. 地址空间只有一个,就像Linux的内核一样,共享地址空间
  2. 没有高级的硬件相关MMU功能,无虚拟地址
  3. 没有文件系统、UI界面等复杂服务
  4. 各种服务例如进程调度等是一个简单版本

之前介绍过一个典型的RTOS freeRTOS。其实RTOS的定义就是实时操作系统,例如Linux修改下具有实时特性也可以叫RTOS了。本文讲的zephyr RTOS就是一个类似freeRTOS的操作系统,可以等价平替,下面介绍下其特点,先直接上代码:https://github.com/zephyrproject-rtos/zephyr

1. Zephyr介绍

1.1 Zephyr简介

Zephyr最初是由Wind River公司开发的一个微内核,在2016年的时候成为Linux基金会维护的一个项目,发展至今,已经成为了一个功能齐全的嵌入式OS。平台现在支持ARM、RISC-V、X86、Xtensa等等处理器平台,拥有原生的BLE协议栈、完整的Net协议栈,包括TCP/IP与应用层协议,为嵌入式应用的开发提供了有力的支持。

Zephyr它不仅仅维护了一个RTOS内核,还维护一些编译链、libc、 IDE插件、HEL层驱动等,几乎每一个模块都有相关的文档。正是因为拥有详细的文档,Zephyr才能源源不断的吸引人来进行尝试与开发,以至于系统不断的被完善,进入开源项目的一个良性循环。

Linux基金会维护

所以看Zephyr就很Linux里Linux气的,比如make menuconfig支持、DTS支持等,可以说把Linux一些优秀的通用特性吸引进来了,做到了很多代码跟Linux同步,妥妥的Linux小弟。好处就是我们去抄代码的时候,不,是移植代码的时候Zephyr就是一个很好的调研对象,其完全开源嘛!

Wind River公司

大名鼎鼎的VxWorks实时操作系统就是Wind River公司开发的,其在实时操作系统领域早期可谓独领风骚,特别在军工中,它以其良好的可靠性和实时性被广泛地应用在通信、军事、航空、航天等高精尖技术及实时性要求极高的领域中,如卫星通讯、军事演习、弹道制导、飞机导航等。在美国的 F-16、FA-18战斗机、B-2 隐形轰炸机和爱国者导弹上,甚至连1997年4月在火星表面登陆的火星探测器、2008年5月登陆的凤凰号,和2012年8月登陆的好奇号也都使用到了VxWorks。VxWorks不开源,这里的Zephyr可以说是出身在一流家族中,其实时特性应该很值得借鉴。

1.2 特性介绍

image.png

image.png

Zephyr OS 基于小占用内核,设计用于资源受限的嵌入式系统:从简单的嵌入式环境传感器和 LED 可穿戴设备到复杂的嵌入式控制器、智能手表和物联网无线应用。

其中Zephyr OS为Zephyr提供的核心代码包含了:

  • Kernel/HAL

    • 内核调度
    • 低级的体系结构和板级支援
    • 各种RTOS内核对象
    • 电源管理框架
    • 低级的硬件接口抽象
  • OS服务及底层接口

    • 设备管理及底层驱动API
    • 文件系统,Logging系统,Debugging系统及IPC
    • 加密服务
    • 平台相关的特殊驱动
    • 网络协议栈
  • 应用服务

    • 高层级的应用API
    • 标准化的数据模型
    • 高层级网络协议接口

对于Zephyr应用开发者,需要熟知应用服务和OS服务,了解底层接口及Kernel/HAL特性。

Zephy也提供构建系统及开发环境和Zephyr OS一起构成了Zephyr Project,Zephyr Project包含了:

  • Zephyr的SDK,构建工具,CI及开发环境
  • Bootloader:MCUboot
  • 附加的中间件

Zephyr除了核心代码之外还引入了其它优秀的开源项目做为其功能组成部分,另外还有不同的项目也将Zephyr作为其组成部分,这些内容在一起构成了完整的Zephyr生态。Zephyr生态有如下内容:

  • 第三方模块或库:例如lvgl,fatfs,mcumgr,openthread,openamp,opencan等等。
  • 第三方支持Zephyr的项目:例如Jerryscript ,Micropython , Iotivity , SwiftIO等。
  • SOC供应商Hal,Zephyr将不同芯片的Hal作为外部模块引入到Zephyr中,通过Zephyr驱动的整合让应用可以通过统一的驱动接口控制不同芯片的片上设备。例如:nxp,st,ti,nordic,silabs等等。

Kernel涵盖以下内容:

  • 提供Kernel service,例如thread,同步,数据传递,中断管理,时间管理,内存管理等
  • 进行任务调度
  • 电源管理
  • 平台相关的特殊驱动,例如Radios,传感器,加密硬件,Flash等

OS服务涵盖以下内容:

  • 设备驱动实现,提供统一的底层驱动接口
  • 设备管理
  • 网络L2实现及接口抽象
  • 网络层和传输层协议栈(TCP/IP),socket接口

应用服务涵盖以下内容:

  • 网络应用协议及高层级接口,例如HTTP,coap,mqtt,tls,dtls等
  • 提供标准化的数据模型,目前尚未在Zephyr的目录树中找到该部分内容
  • 提供智能物件对象,例如基于lwm2m的IPSO

image.png

Zephyr的硬件层次结构被抽象出6层:

  • 架构(Architecutre):指令架构体系,例如ARM,RISC-V,x86等
  • CPU内核(CPU core):架构中特定的CPU,例如ARM中有Cortex-M0,M3,M4,M7等
  • 芯片族(Soc family):具有相似特性的SoC,例如Cortex-M7中有STMicro STM32,NXP i.MX
  • 芯片系列(SoC series):一小部分紧密关联的SoC,例如i.MX中有i.MX RT 系列,i.MX 8系列等
  • 芯片级(SoC):电路板上的SoC,例如i.MX RT系列中有RT1050,RT1060等芯片
  • 板级(Board):PCB上特定的SoC和一些外设相连构成有特定功能的电路板。例如Zephyr支援的mimxrt1050_evk,mm_swiftio开发板使用了rt1052芯片。

Zephyr 内核支持多种架构,包括:

  • ARCv2 (EM and HS) and ARCv3 (HS6X)
  • ARMv6-M, ARMv7-M, and ARMv8-M (Cortex-M)
  • ARMv7-A and ARMv8-A (Cortex-A, 32- and 64-bit)
  • ARMv7-R, ARMv8-R (Cortex-R, 32- and 64-bit)
  • Intel x86 (32- and 64-bit)
  • MIPS (MIPS32 Release 1 specification)
  • NIOS II Gen 2
  • RISC-V (32- and 64-bit)
  • SPARC V8
  • Tensilica Xtensa

广泛的内核服务套件,Zephyr 提供了许多熟悉的开发服务:

  • *用于协作、基于优先级、非抢占式和抢占式线程的多线程服务,具有可选的循环时间切片。* 包括 POSIX pthreads 兼容 API 支持。
  • 中断服务用于中断处理程序的编译时注册。
  • *用于动态分配和释放固定大小或可变大小内存块的内存分配服务。*
  • *用于二进制信号量、计数信号量和互斥信号量的线程间同步服务。*
  • *用于基本消息队列、增强消息队列和字节流的线程间数据传递服务。*

Zephyr 提供了一套全面的线程调度选择:

  • 协作和抢占式调度
  • 最早截止日期优先 (EDF)
  • 元 IRQ 调度实现“中断下半部分”或“tasklet”行为
  • 时间切片:在同等优先级的可抢占线程之间启用时间切片
  • 多种排队策略:
    • 简单链表就绪队列
    • 红/黑树就绪队列
    • 传统多队列就绪队列

高度可配置/模块化以实现灵活性

  • 允许应用程序仅根据需要合并其所需的功能,并指定其数量和大小。

跨架构

  • 支持具有不同 CPU 架构和开发工具的各种受支持的主板。贡献增加了对越来越多 SoC、平台和驱动程序的支持。

内存保护

  • 在 x86、ARC 和 ARM 架构、用户空间和内存域上实现可配置的特定于架构的堆栈溢出保护、内核对象和设备驱动程序权限跟踪以及具有线程级内存保护的线程隔离。

    对于没有 MMU/MPU 和内存受限设备的平台,支持将特定于应用程序的代码与自定义内核相结合,以创建在系统硬件上加载和执行的整体映像。应用程序代码和内核代码都在单个共享地址空间中执行。

编译时资源定义

  • 允许在编译时定义系统资源,从而减少代码大小并提高资源有限系统的性能。

优化的设备驱动模型

  • 提供用于配置属于平台/系统的驱动程序的一致设备模型,以及用于初始化配置到系统中的所有驱动程序的一致模型,并允许跨具有公共设备/IP 块的平台重用驱动程序。

设备树支持

  • 使用devicetree来描述硬件。来自 devicetree 的信息用于创建应用程序映像。

支持多种协议的本机网络堆栈

  • 网络支持功能齐全且经过优化,包括 LwM2M 和 BSD 套接字兼容支持。还提供 OpenThread 支持(在 Nordic 芯片组上) - 一个网状网络,旨在安全可靠地连接家庭周围的数百个产品。

低功耗蓝牙 5.0 支持

  • 符合蓝牙 5.0 标准 (ESR10) 和蓝牙低功耗控制器支持(LE 链路层)。包括蓝牙网状网络和蓝牙资格就绪的蓝牙控制器。

    • 具有所有可能的 LE 角色的通用访问配置文件 (GAP)
    • 通用属性配置文件 (GATT)
    • 配对支持,包括蓝牙 4.2 的安全连接功能
    • 干净的 HCI 驱动程序抽象
    • 原始 HCI 接口将 Zephyr 作为控制器运行,而不是完整的主机堆栈
    • 经多个流行控制器验证
    • 高度可配置

    网格支持:

    • 中继、朋友节点、低功耗节点 (LPN) 和 GATT 代理功能
    • 支持两种配置承载(PB-ADV 和 PB-GATT)
    • 高度可配置,适合具有至少 16k RAM 的设备

本机 Linux、macOS 和 Windows 开发

  • 命令行 CMake 构建环境在流行的开发人员操作系统上运行。本机端口 ( native_sim ) 允许您在 Linux 上构建和运行 Zephyr 作为本机应用程序,从而帮助开发和测试。

支持 ext2、FatFs 和 LittleFS 的虚拟文件系统接口

  • ext2、LittleFS 和 FatFS 支持;FCB(闪存循环缓冲区)适用于内存受限的应用程序。

强大的多后端日志框架

  • 支持日志过滤、对象转储、恐慌模式、多个后端(内存、网络、文件系统、控制台……)以及与 shell 子系统的集成。

用户友好且功能齐全的 Shell 界面

  • 多实例 shell 子系统,具有用户友好的功能,例如自动完成、通配符、着色、元键(箭头、退格键、ctrl+u 等)和历史记录。支持静态命令和动态子命令。

    非易失性存储上的设置

  • 设置子系统为模块提供了一种存储持久的每设备配置和运行时状态的方法。设置项存储为键值对字符串。

非易失性存储 (NVS)

  • NVS 允许存储二进制 blob、字符串、整数、长整型以及这些的任意组合。

本机端口

  • Native sim允许将 Zephyr 作为 Linux 应用程序运行,并支持各种子系统和网络。

2. 环境搭建和代码下载编译

参考: https://docs.zephyrproject.org/latest/develop/getting_started/index.html https://blog.csdn.net/shenhuxi_yu/article/details/103229958 多种编译工具编译参考:https://liao-xingyu.gitee.io/p/zephyr-%E7%89%A9%E8%81%94%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%BC%96%E8%AF%91%E4%B8%8E%E8%BF%90%E8%A1%8C/

按照本指南进行操作:

  • 在 Ubuntu、macOS 或 Windows 上设置命令行 Zephyr 开发环境(其他 Linux 发行版的说明在 安装 Linux 主机依赖项中讨论)
  • 获取源代码
  • 构建、刷新并运行示例应用程序

具体不赘述了,提下如果在qemu下运行,参考如下:

在QEMU中运行应用程序

在 Linux 和 macOS 上,当面向 x86 或 ARM Cortex-M3 架构时,您可以使用QEMU在主机系统上通过仿真运行 Zephyr 应用程序。(QEMU 包含在 Zephyr SDK 安装中。)

例如,您可以使用 x86 仿真板配置 ( ) 构建并运行Hello Worldqemu_x86示例,其中:

# From the root of the zephyr repository
west build -b qemu_x86 samples/hello_world
west build -t run

要退出 QEMU,请键入,然后键入。Ctrl-a``x

用于qemu_cortex_m3针对模拟的 Arm Cortex-M3 示例。

3. Zephyr的内核特性

3.1 构建

ephyr的构建系统主要有三个部分cmake、kconfig、devicetree

cmake :在前期驱动kconfig和devicetree需生成必要的头文件,后期用来生成像ninja或makefile等编译脚本

kconfig:平台的区分以及代码的裁剪,应用开发中主要体现在驱动的开关,如下图显示,在驱动的实现里面定义了wifi的kconfig宏,用户在使用的时候可以在prj.config文件去打开这个宏,这个驱动就会被打开。

image.png

devicetree: 配置硬件参数信息,比如像WIFI的模块,可以配置SPI引脚以及速率,还可以配置跟他相关的同步引脚。

image.png

3.2 设备驱动模型

Zephyr几乎为所有的外设驱动都提供了统一的API接口,芯片原厂基于API接口提供自己的实现,用户在使用外设的时候可以直接使用Zephyr提供的外设接口来进行应用开发。

Zephyr的设备驱动是在同一个地方统一初始化的,比如像这个SPI的驱动,它的宏函数就等效于这一段代码,代码的含义就是注册了一个函数。这个函数会在main函数前运行,运行等级为application级别。驱动的等级会比application高,等级越高就会越先执行。设备驱动一定会在main函数前初始化完毕。

image.png

3.3 线程调度和通信

Zephyr 在线程调度方面的功能更加强大、灵活,可以更好地满足不同场景下的需求。

而 FreeRTOS 则更加简单、易于使用,适合对资源需求较为简单的嵌入式应用场景

image.png

Zephyr 与 FreeRTOS 线程通信对比:

image.png

Zephyr提供了管道、消息队列和信号量等多种线程通信机制,而FreeRTOS提供了二值信号量、互斥量和队列等线程通信机制。可以看出,Zephyr提供的线程通信机制更加多样化。

3.4 内存管理

Zephyr在内存管理上具备一些比起其他RTOS更加先进机制,如下

Memory Heaps:提供了基于堆的动态内存分配和释放机制

Memory Slabs:提供了预分配一定数量内存块的机制,并能够快速分配和释放内存块,避免了堆内存管理的 开销以及内存碎片的产生

Memory Blocks Allocator:提供了固定大小内存块的动态分配和释放机制,适用于需要频繁分配、释放同一大小内存块 的场景

3.5 组件生态

越来越多的第三方组件库已经加入或者被移植进了ZephyrProject中,第三方组件管理与接入流程也日趋成熟这为开发者节省了大量常用组件的移植与适配工作。

image.png

4. Zephyr启动流程

这里我们以cortex_m为例,只讲下启动流程,其他代码流程分析,参考:https://blog.csdn.net/qq_36115224/category_12279396.html

4.1 程序入口

include/zephyr/arch/arm/cortex_m/scripts/linker.ld中定义了ENTRY程序入口

ENTRY(CONFIG_KERNEL_ENTRY)

这个宏的定义在Kconfig.zephyr中

config KERNEL_ENTRY
    string "Kernel entry symbol"
    default "__start"
    help
      Code entry symbol, to be set at linking phase.

__start是程序的入口,但是这是gcc的规则,Cortex-M系列处理器是通过中断向量表来确定程序入口 Cortex-M系列处理器是通过中断向量表来确定程序入口。

在 Cortex-M 系列 MCU 中,如果设置为从 flash 启动,flash 前1K 用于存放中断向量表,

  • 其中第一个字为程序栈顶指针,用于初始化栈顶
  • 第二个字为复位向量,即 ResetHandler 的地址,作为程序跳转地址,从而跳转到程序中运行

详细说明可参考异常模型, 该机制为Cortex-M 系列 MCU 中独有,与此相对的,不同的芯片在系统启动时对可执行程序都有一定的格式要求,需要在程序的头部添加符合操作规范的头。

中断向量表中,reset的处理地址定义在arch/arm/core/cortex_m/reset.S中

SECTION_SUBSEC_FUNC(TEXT,_reset_section,z_arm_reset)

4.2 汇编初始化

在arch/arm/core/cortex_m/reset.S中

SECTION_SUBSEC_FUNC(TEXT,_reset_section,__start)

#if defined(CONFIG_DEBUG_THREAD_INFO)
    /* Clear z_sys_post_kernel flag for RTOS aware debuggers */
    movs.n r0, #0
    ldr r1, =z_sys_post_kernel
    strb r0, [r1]
#endif /* CONFIG_DEBUG_THREAD_INFO */

#if defined(CONFIG_INIT_ARCH_HW_AT_BOOT)
    /* 复位CONTROL寄存器,在特权模式和非特权模式均使用MSP,
     * 当切换栈指针之后必须使用ISB指令刷新流水线,
     * 以保证在ISB之后执行的指令都使用新的栈
     */
    movs.n r0, #0
    msr CONTROL, r0
    isb
#if defined(CONFIG_CPU_CORTEX_M_HAS_SPLIM)
    /* 堆栈限制寄存器分别限制 MSP 和 PSP 寄存器可以下降的程度,此处设置为0 */
    movs.n r0, #0
    msr MSPLIM, r0
    msr PSPLIM, r0
#endif /* CONFIG_CPU_CORTEX_M_HAS_SPLIM */

#endif /* CONFIG_INIT_ARCH_HW_AT_BOOT */

#if defined(CONFIG_PM_S2RAM)
    /* 低功耗相关初始化 */
    bl arch_pm_s2ram_resume
#endif /* CONFIG_PM_S2RAM */

#if defined(CONFIG_PLATFORM_SPECIFIC_INIT)
    /* 针对内存,cache,jtag,时钟,中断的一些特殊配置  */
    bl z_arm_platform_init
#endif

#if defined(CONFIG_INIT_ARCH_HW_AT_BOOT)
#if defined(CONFIG_CPU_HAS_ARM_MPU)
    /* 操作系统未运行之前使用平坦内存模型,所有内存均不受保护,
     * 为避免在初始化过程中触发读写保护进入异常,一定要关闭MPU
     */
    movs.n r0, #0
    ldr r1, =_SCS_MPU_CTRL
    str r0, [r1]
    dsb
#endif /* CONFIG_CPU_HAS_ARM_MPU */
    /* ARM32使用满递减栈,栈顶指针初始时刻指向高地址,
     * 将MSP指向 z_main_stack 的末尾,以便后续进行函数调用
     */
    ldr r0, =z_main_stack + CONFIG_MAIN_STACK_SIZE
    msr msp, r0

    /* 清除MPU配置,关闭所有中断,清除被挂起的中断,重置Cache配置等 */
    bl z_arm_init_arch_hw_at_boot
#endif /* CONFIG_INIT_ARCH_HW_AT_BOOT */

    /* 屏蔽中断 */
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
    cpsid i
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
    movs.n r0, #_EXC_IRQ_DEFAULT_PRIO
    msr BASEPRI, r0
#else
#error Unknown ARM architecture
#endif

#ifdef CONFIG_WDOG_INIT
    /* 开启看门狗 */
    bl z_arm_watchdog_init
#endif

#ifdef CONFIG_INIT_STACKS
    /* 将栈全部设置为0xaa,可用于监测剩余栈容量 */
    ldr r0, =z_interrupt_stacks
    ldr r1, =0xaa
    ldr r2, =CONFIG_ISR_STACK_SIZE + MPU_GUARD_ALIGN_AND_SIZE
    bl z_early_memset
#endif

    /* 初始化PSP,将CONTROL的SPSEL位设置为1(在特权模式下使用MSP,非特权模式下使用PSP)
     * 后续操作将使用 z_interrupt_stacks 作为栈进行初始化
     */
    ldr r0, =z_interrupt_stacks
    ldr r1, =CONFIG_ISR_STACK_SIZE + MPU_GUARD_ALIGN_AND_SIZE
    adds r0, r0, r1
    msr PSP, r0
    mrs r0, CONTROL
    movs r1, #2
    orrs r0, r1 /* CONTROL_SPSEL_Msk */
    msr CONTROL, r0
    isb

    bl z_prep_c

4.3 C语言初始化

z_prep_c()函数的定义在arch/arm/core/cortex_m/prep_c.c中

void z_arm_prep_c(void)
{
    relocate_vector_table();
#if defined(CONFIG_CPU_HAS_FPU)
    z_arm_floating_point_init();
#endif
    z_bss_zero();
    z_data_copy();
#if ((defined(CONFIG_ARMV7_R) || defined(CONFIG_ARMV7_A)) && defined(CONFIG_INIT_STACKS))
    z_arm_init_stacks();
#endif
    z_arm_interrupt_init();
    z_cstart();
    CODE_UNREACHABLE;
}

对于不同的平台还需要增加额外的操作,例如浮点寄存器的初始化,中断向量表的重定向,在系统启动之前还运行了一段芯片内置程序,这段程序将中断向量表的位置设置为 0x00000000,当程序从boot跳转到指定存储器运行之后,首先需要立即关闭中断,避免中断产生并跳转到错误的中断响应函数中,重设中断向量表偏移位置之后,再重新开启中断。

初始化内核开启任务调度

FUNC_NO_STACK_PROTECTOR
FUNC_NORETURN void z_cstart(void)
{
    /* 代码覆盖率测试相关 */
    gcov_static_init();

    /* 调用初始化级别为 INIT_LEVEL_EARLY 的函数进行初始化 */
    z_sys_init_run_level(INIT_LEVEL_EARLY);

    /* z_arm_interrupt_stack_setup 初始化MSP
     * z_arm_exc_setup 初始化PENDSV、SysTick等中断的优先级,其中PENDSV优先级为最低
     * z_arm_fault_init 初始化 Fault 中断。
     * z_arm_cpu_idle_init 初始化 idle 线程
     * z_arm_clear_faults 清除所有故障标志
     * z_arm_mpu_init 初始化MPU
     * z_arm_mmu_init 初始化MMU
     */
    arch_kernel_init();

    /* 日志初始化 */
    LOG_CORE_INIT();

#if defined(CONFIG_MULTITHREADING)
    /* Note: The z_ready_thread() call in prepare_multithreading() requires
     * a dummy thread even if CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN=y
     */
    struct k_thread dummy_thread;

    z_dummy_thread_init(&dummy_thread);
#endif
    /* 初始化驱动中的静态节点 */
    z_device_state_init();

    /* 其他的硬件初始化 */
    z_sys_init_run_level(INIT_LEVEL_PRE_KERNEL_1);
    z_sys_init_run_level(INIT_LEVEL_PRE_KERNEL_2);

#ifdef CONFIG_STACK_CANARIES
    /* CONFIG_STACK_CANARIES 用于开启堆栈金丝雀功能,这是一种安全特性,有助于监测堆栈溢出,
     * 当启动该功能时,系统启动时会生成一个随机数并保存在 __stack_chk_guard 中,
     * 在函数返回之前会检查该值确保它没有被缓冲区溢出所覆盖。
     */
    uintptr_t stack_guard;

    z_early_boot_rand_get((uint8_t *)&stack_guard, sizeof(stack_guard));
    __stack_chk_guard = stack_guard;
    __stack_chk_guard <<= 8;
#endif    /* CONFIG_STACK_CANARIES */

#ifdef CONFIG_TIMING_FUNCTIONS_NEED_AT_BOOT
    /* timing_init 函数用于初始化系统计时器 */
    timing_init();
    timing_start();
#endif

#ifdef CONFIG_MULTITHREADING
    /* CONFIG_MULTITHREADING为y时,使用多线程,否则只会有一个 main 线程,
     * 默认情况下都启用多线程,通过将 main 线程添加到就绪队列中然后开启任务调度
     */
    switch_to_main_thread(prepare_multithreading());
#else
#ifdef ARCH_SWITCH_TO_MAIN_NO_MULTITHREADING
    /* Custom ARCH-specific routine to switch to main()
     * in the case of no multi-threading.
     */
    ARCH_SWITCH_TO_MAIN_NO_MULTITHREADING(bg_thread_main,
        NULL, NULL, NULL);
#else
    bg_thread_main(NULL, NULL, NULL);

    /* LCOV_EXCL_START
     * We've already dumped coverage data at this point.
     */
    irq_lock();
    while (true) {
    }
    /* LCOV_EXCL_STOP */
#endif
#endif /* CONFIG_MULTITHREADING */

    /*
     * Compiler can't tell that the above routines won't return and issues
     * a warning unless we explicitly tell it that control never gets this
     * far.
     */

    CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}

在进入 z_cstart 之后,首先调用平台相关的内核初始化函数,然后使用模块自动初始化机制根据不同优先级调用相关初始化函数 需要注意的是,同一优先级的模块之间不应该存在依赖关系,被依赖的模块应该先初始化。 在调度器启动之前会调用优先级为 INIT_LEVEL_PRE_KERNEL_1 和 INIT_LEVEL_PRE_KERNEL_2 的初始化函数,此时调度器还未运行,因此这些函数中不应该使用操作系统提供的功能。 在初始化完成之后通过将main线程添加到就绪队列中并开启调度器,main 线程从 bg_thread_main 函数开始运行。

4.4 main线程

bg_thread_main的定义在kernel/init.c中,

static void bg_thread_main(void *unused1, void *unused2, void *unused3)
{
    ARG_UNUSED(unused1);
    ARG_UNUSED(unused2);
    ARG_UNUSED(unused3);

#ifdef CONFIG_MMU
    /* Invoked here such that backing store or eviction algorithms may
     * initialize kernel objects, and that all POST_KERNEL and later tasks
     * may perform memory management tasks (except for z_phys_map() which
     * is allowed at any time)
     */
    z_mem_manage_init();
#endif /* CONFIG_MMU */
    z_sys_post_kernel = true;

    /* 调用优先级为INIT_LEVEL_POST_KERNEL的初始化函数,
     * 此时内核已经开始运行,可以使用操作系统API
     */
    z_sys_init_run_level(INIT_LEVEL_POST_KERNEL);
#if CONFIG_STACK_POINTER_RANDOM
    z_stack_adjust_initialized = 1;
#endif
    /* 从控制台输出系统启动标识 */
    boot_banner();

#if defined(CONFIG_CPP)
    /* 初始化CPP运行环境 */
    void z_cpp_init_static(void);
    z_cpp_init_static();
#endif

    /* 调用优先级为 INIT_LEVEL_APPLICATION 的初始化函数 */
    z_sys_init_run_level(INIT_LEVEL_APPLICATION);
    /* Zephyr支持静态创建线程,线程对应的信息在编译时确定,
     * 随代码一起被编译到程序中,系统启动之后从对应地址将线程的信息从flash中读出,
     * 创建并初始化线程并将其添加到就绪队列中等待操作系统调度。
     */
    z_init_static_threads();

#ifdef CONFIG_KERNEL_COHERENCE
    __ASSERT_NO_MSG(arch_mem_coherent(&_kernel));
#endif

#ifdef CONFIG_SMP
    if (!IS_ENABLED(CONFIG_SMP_BOOT_DELAY)) {
        z_smp_init();
    }
    z_sys_init_run_level(INIT_LEVEL_SMP);
#endif

#ifdef CONFIG_MMU
    z_mem_manage_boot_finish();
#endif /* CONFIG_MMU */

#ifdef CONFIG_CPP_MAIN
    extern int main(void);
#else
    extern void main(void);
#endif

    /* 跳转到main函数 */
    (void)main();

    /* Mark nonessential since main() has no more work to do */
    z_main_thread.base.user_options &= ~K_ESSENTIAL;

#ifdef CONFIG_COVERAGE_DUMP
    /* Dump coverage data once the main() has exited. */
    gcov_coverage_dump();
#endif
}

其中包含几个系统运行的重要的操作: 配置MMU(如果存在) 调用优先级为 INIT_LEVEL_POST_KERNEL 的初始化函数 CPP运行环境的初始化 调用优先级为 INIT_LEVEL_APPLICATION 的初始化函数 创建通过宏静态创建的线程,并添加到就绪队列。 初始化对称多核处理(如果存在多个处理器并启用了多核调度)。 在这些准备工作完成后跳转到用户编写的main函数中,如果main函数执行并返回,最终会返回到 z_thread_entry 被销毁。

学习方法

  1. 看官网:https://docs.zephyrproject.org/latest/introduction/index.html

  2. bilibili看视频: https://search.bilibili.com/all?keyword=Zephyr&from_source=webtop_search&spm_id_from=333.788&search_source=5

  3. CSDN、知乎等资料也挺多,可以自己去搜索

参考资料:

  1. Zephyr 入门,看这篇就够了(含FreeRTOS对比)
  2. https://blog.csdn.net/qq_36115224/article/details/130077367
  3. https://lgl88911.github.io/2020/11/29/Zephyr-%E6%A1%86%E6%9E%B6%E7%BB%93%E6%9E%84%E7%AE%80%E4%BB%8B/

后记:

Zephyr RTOS整体上网上资料很多,大家可以自己去搜索学习,也说明这个RTOS是比较流行的,各种硬件也对这个Zephyr进行了支持,里面有很多功能非常的丰富,如果自己项目的RTOS缺失的功能可以去上面进行移植。一个典型就是gcov代码覆盖率测试模块,一般的RTOS是不支持的就可以移植。

对于OS在国内可能很神秘,但是在欧美特别是高校里面各种定制版本或者学术版本的OS层出不穷,已经算是人人都能造OS了一样,这些小众的OS不一定流行起来,但是也各有特点,特别是一些特定场景下小众OS也许能获得更高的效率和效果,开源OS界也是天下文章一大抄。

results matching ""

    No results matching ""