Zephyr OS

zephyr官网:Introduction — Zephyr Project Documentation

zephyr简介

zephyr 是一个用于物联网的轻量级开源操作系统目标是构建一个针对资源受限设备的小型、可裁剪的实时操作系统(RTOS)提供了一个低占用空间、高性能、多线程的执行环境。关于内核类型的划分,2017年1月推出V1.6.0内核版本采用统一的内核代替了原来分离的超微内核和微内核。根据使用场景配置,zephyr内核既适合内存量有限(低至 2 KB!)或具有简单多线程要求(例如一组中断处理程序和单个后台任务)应用程序,例如嵌入式传感器集线器、环境传感器、简单的 LED 可穿戴设备和商店库存标签,也适合开发需要更多内存(50 到 900 KB)、多个通信设备(如 Wi-Fi 和蓝牙低功耗)和复杂多线程的应用程序,例如健身可穿戴设备、智能手表和物联网无线网关。

zephyr系统架构(项目实际架构怎么做,自己需要做的内容,以及给其他模块提供样例)

从zephyr架构图可以看到,zephyr将架构分为操作系统部分(内核 + 操作系统服务)和用户特定部分(应用程序服务)。操作系统部分本身包含底层的、特定于平台的驱动程序和 I/O API、文件系统、内核特定函数和加密库的通用实现。

Kernel/HAL:Kernel service(例如thread,同步,数据传递,中断管理,时间管理,内存管理等)、内核调度、电源管理、平台殊驱动,例如Radios,传感器,加密硬件,Flash等;

OS服务:设备驱动实现及统一的底层驱动接口、设备管理、网络层和传输层协议栈(TCP/IP),socket接口;

应用服务:网络应用协议及高层级接口,例如HTTP,coap,mqtt,tls,dtls等,提供智能物件对象,例如基于lwm2m的IPSO;

项目在zephyr使用过程中梳理提供了基础功能表,目前待新增驱动开发方法;

zephyr硬件支持架构

zephyr硬件抽象可以划分为6层,此处只做概念上的描述,具体案例参考编写设备树及配置系统Kconfig部分。

架构(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芯片

芯片与开发板关系举例:

STM32 SoC Family有STM32F4、STM32G4等SoC Series;

STM32F4 SoC Series 有STM32F401、STM32F429等SoC;

STM32F429 SoC有野火STM32F429挑战者开发板、正点原子stm32f429阿波罗开发板等;

zephyr内核支持架构(简述)

ARCv2(EM和HS)和ARCv3(HS6X)

ARMv6-M、ARMv7-M和ARMv8-M (Cortex-M)

ARMv7-A和ARMv8-A(Cortex-A,32 位和 64 位)

ARMv7-R、ARMv8-R(Cortex-R、32位和64位)

英特尔x86(32位和64位)

MIPS(MIPS32第1版规范)

NIOS II第2代

RISC-V(32位和64位)

SPARC V8

Tensilica Xtensa

zephyr 系统

开源:基于 Apache 2.0 许可,完全开源,代码托管在github

模块化:针对受限制的物联网设备而设计,可以通过 Kconfig 裁剪功能选项,从而实现用户自定义的最佳配置;

编程语言:主要用C语言编写,代码风格、框架与Linux十分相似;

开发环境:支持多种开发环境(Windows、Linux、MacOS)

联网能力:系统中提供了多种针对低功耗、内存受限设备的连接协议,支持低功耗蓝牙(BLE)、wifi、802.15.4以及其他标准,包括6lowpan、coap、ipv4和ipv6 ;

安全性:项目在开发过程中将安全因素考虑在内,该项目中提供了安全验证、模糊和渗透测试、代码审查、静态代码分析、威胁建模和审查等多种检测方法,用来防止代码中存在后门和漏洞。以上工作由专门的安全小组及维护人员进行监督和维护。

zephyr 内核有很多独特的优秀特性(zephyr基础功能特性表详述):

1、单地址空间操作系统。将应用程序相关的代码与内核结合在一起,创建一个在硬件上加载、运行的单一镜像。应用程序代码和内核代码运行在同一个共享地址空间。

2、高度可配置。允许应用程序只包含它们需要的功能,例如对于多级中断是否支持、是否使线程在user mode模式运行等,均可以通过CONFIG_进行配置;

3、编译时定义资源。所有系统资源都在编译时定义,以减小代码量、增强代码性能。

4、最小错误检查。提供最小化的运行时错误检查,以减小代码量、增强代码性能。提供一个可选的错误检查基础,以协助应用程序的开发和调试。

5、广泛的服务。提供了许多耳熟能详的服务:

多线程服务:为基于优先级的、非抢占式的 fiber 和基于优先级的、抢占式的 task 提供可选时间片。

中断服务:在编译时、运行时均可注册中断处理函数。

线程间同步服务:包括二元信号量、计数信号量和互斥信号量。

线程间数据传递服务:包括基本消息队列、增强型消息队列和字节流。

内存分配服务:动态地分配固定尺寸、可变尺寸的内存块。

电源管理服务:包括无滴答 CPU 空转和高级 CPU 空转。

zephyr OS 源码结构

zephyr 源码树的顶层目录如下所述,每个顶层目录都包括一级或多级子目录

 arch架构相关的微内核代码和支持的各种架构的平台代码

支持的每个架构都有一个子目录,且这些子目录还包括下面子目录:

 ·架构相关的超微内核源文件。

·架构相关的超微内核的私有 API 的头文件。

·平台相关的代码。

boards 板级相关的代码和配置文件,boards下一层级目录结构与arch很相似,依据不同的处理器架构划分,每个目录内是包括dts、Kconfig等设备树配置文件对应zephyr硬件支持架构board模块。
doc  zephyr系统相关的材料和工具,描述系统内模块使用什么样子的工具完成什么样子的模块功能
drivers 设备驱动代码。对应zephyr系统架构OS services中I2C、SPI等驱动模块。
include 所有(不包括 lib 目录)公有 API 的头文件,例如定时器、USB等
kernel  架构无关的微内核代码。对应zephyr系统架构kernel services。
lib  库代码,包括最小的 C 库、posix等
misc 暂时无法按照统一标准进行归类的杂项代码。
net 网络相关的代码,包括蓝牙协议栈和网络协议栈。对应zephyr系统架构OS services中BLE、TCP/IP模块;
samples 微内核、蓝牙协议栈和网络协议栈的应用程序举例。
tests 内核各个特性的测试代码。
scripts 用于编译、测试 zephyr 应用程序的程序和文件,例如搭建zephyr工程过程中安装python依赖选项命令:

 modules 提供特定芯片系列的一些外设配置选项

官方说明:https://docs.zephyrproject.org/latest/develop/modules.html?highlight=west

CMakeLists.txt CMake 构建系统的顶层文件,包含构建 Zephyr 所需的大量逻辑

Kconfig 顶层 Kconfig 文件,它引用 Kconfig.zephyr 文件。也可以在顶级目录中找到

west.yml  west清单,列出由 West 命令行工具管理的外部存储库

dts  devicetrree 源文件,用于描述特定板子的设备细节,如外设寄存器数量及寄存器地址

soc SoC 相关代码和配置文件,如 nRF9160 soc

zephyr管理工具west

west介绍

west是zephyr项目提供的一款命令行工具,类似于 Google 的 Repo 工具,多库管理,支持自定义编写扩展命令,使用python调用脚本来完成系统构建,zephyr使用west来构建、烧写和调试应用程序。west运行需要用到常用选项、需要要运行的子命令,以及该子命令的选项和参数:

west安装

 执行west -V可以验证安装是否成功

初始化west工作区

获取zephyr源码及update获取其他的依赖仓库

 west init默认使用https://github.com/zephyrproject-rtos/zephy 作为zephyr代码仓库地址。完整的zephyr 项目实际包含了30多个子仓库,并且每个子仓库都要求版本号(分支),例如驱动的SDK代码或者HAL层,为各大半导体商所定义,均是单独管理的。west init只获取了关于zephyr部分的代码,而 west update 命令就是获取这些相关联的子仓库代码并放到指定的目录下。zephyr团队写了个脚本用来关联这些子仓库,这个脚本叫 west.yml,放在 zephyr 的源代码仓库下。west update 会读取 zephyr\west.yml 文件,将其中列举的仓库依次下载下来,放到指定目录下,若找不到 west.yml 文件就会报错。每次新版本发布,可以通过git checkout来切换分支,获取新的west.yml脚本,以此更新整个仓库。

west.yml的部分内容如下所示:从 Zephyr Project · GitHub 获取仓库代码,将 canopennode、civetweb、cmsis 仓库的对应分支代码下载放至 modules/lib/canopennode、 modules/lib/civetweb、modules/hal/cmsis 的目录下直至所有子仓库代码获取完毕

 在上图可以看到mainfest,manifest指明要下载各代码仓库的名称,路径,版本。
manifest包含四项: default, remotes, projects, self。其中remotes和projects是强制必须要的,remote指定远端地址, projects指定仓库地址和版本,以图中标记的chre为例:

west下载时会以remotes的url-base+projects chre的name结合为https://github.com/zephyrproject

-rtos/chre下载版本为0edfe2c2ec656afb910cfab8ed59a5ffd59b87c8的代码到modules/lib/chre下,manifest可以在remotes里面指定多个url-base, 在projects中被不同的project引用,此处不再详述;

west build编译

基于具体的板子进行编译

west build -p ...,-p可以清除老的生成文件,west build核心工作内容为:

west命令执行过程中会转换成cmake命令,具体转换方法为west在运行的时候会根据路径去寻找.west文件夹,该文件夹下的config文件记录了zephyr文件夹的路径,然后去读取zephyr目录下的west.yml文件,在west.yml中指向了west的扩展命令路径:scripts/west-commands.yml。

展开west-commands.yml如下所示,当执行west build时,会执行scripts/west-commands/build.py,将执行命令引导到python脚本,经过build.py脚本处理生成cmake执行命令。

west flash与west build总体相似,所不同的是,在执行flash.py时会调用run_common.py,而该脚本会读取runners.yaml文件,runners.yaml记录了下载和调试工具信息,不同的板子依据不同的board.cmake会生成不同的runners.yaml,具体编译过程如下:

zephyr\cmake\app\boilerplate.cmake

zephyr\CMakeLists.txt

zephyr\cmake\flash\CMakeLists.txt

在最后一步会根据board.cmake生成runners.yaml。

CMakeLists.txt文件编译过程

  前面讲述了west命令经多次文件调用最终转成cmake命令,cmake命令执行就是依赖CMakeLists.txt文件完成的。以~/zephyrproject/zephyr/samples/hello_world为例,CMakeLists.txt展开如下:

第3行设置cmake要求最低版本,第6行设置工程,第8行设置源代码文件,重点关注第5行,find_package是cmake系统函数,表示在ZEPHYR_BASE目录下找名为Zephyr的模块(ZEPHYR_BASE宏定义在scripts/west-commands/zephyr _ext _common.py中),根据find_package工作原理可知,最终要找的是<PackageName>Config.cmake,即zephyr\share\zephyr- package\cmake\ZephyrConfig.cmake,ZephyrConfig.cmake调用zephyr\cmake\app\boilerplate.cmake,它将处理Device tree和Kconfig等配置信息,最终生成必要的头文件,它还会关联其他的相关文件及Zephyr下的CMakeLists.txt文件,这个 CMakeLists.txt又会关联其他的配置文件以及zephyr\cmake\flash\CMakeLists.txt,最终完成整个编译的过程。编译完成后,会在生成的 <build> 文件夹下生成一个名为 CMakeCache.txt 的文件,这个文件记录了编译过程中保存的一些变量值和文件,还有相关的文件路径,这些信息用来烧录与调试。

west调用menuconfig

west build -t menuconfig

west调用GUI 界面

west build -t guiconfig

其他west命令汇总:

烧写/调试

west flash  等同 make flash
west debug  等同 make debug 
west debugserver  等同 make debugserver  

ram/rom信息查看

west build -t rom_report  等同  make rom_report
west build -t ram_report  等同  make ram_report

west自定义扩展(官网查看):Extensions — Zephyr Project Documentation

west:Zephyr RTOS -- West 命令及编译过程简介_搬砖-工人的博客-CSDN博客_west命令

Zephyr west简介 | Half Coder

清风徐来——Zephyr实战篇(2)之西 - 「MCU加油站」 - 恩智浦技术社区

zephyr配置系统裁剪介绍

  zephyr作为一个针对资源受限设备的小型、可裁剪的实时操作系统,本身支持多种架构类型处理器及开发板,此处分享配置最小系统,其实就是在zephyr os上新增支持soc、开发板,以CPU_CORTEX_M7为例,新增board、soc、dts相应配置文件夹,最终配置出一套名为xp_cortex_m7的新板子,新增文件夹结构如下:

针对board、soc、dts新增配置文件详解具体配置内容:

board:zephyrproject/zephyr/boards/arm/xp_cortex_m7

如上图所示,board.cmake、Kconfig.board、Kconfig.defconfig、xp_cortex_m7.dts、xp_cortex_m7.yaml、xp_cortex_m7_defconfig

六份新增文件,其中(1)xp_cortex_m7.dts为设备树格式的硬件描述,声明 SoC、连接器和其他硬件组件,例如 LED、按钮、传感器或通信外围设备;(2)Kconfig.board、Kconfig.defconfig、xp_cortex_m7_defconfig是配置系统格式(Kconfig)的软件配置,提供了软件功能和外围驱动程序的默认设置;(3)board.cmake用于支持烧写和调试;(4)xp_cortex_m7.yaml为支持测试用到的各种元数据的YAML文件。

编写设备树(重要)

设备树是描述硬件的分层数据结构,有两种类型的设备树输入文件:设备树源和 设备树绑定。源包含设备树本身绑定描述了它的内容以及数据类型,构建系统使用设备树源和绑定来生成 C 头文件,过程如下:

设备树语法和结构:

/dts-v1/;表示当前设备树遵循DTS语法规范版本1(目前设备树规范有4个版本);

设备树有3个节点:

  1. 根节点:/
  2. 根节点的子节点a-node
  3. 子节点a-node的子节点a-sub-node
  4. 可以为节点分配节点标签,上图中subnode_nodelable为a-sub-node的节点标签可在设备树中被引用,一个节点可以有零个、一个或多个节点标签,设备树节点内会有属性,属性分属性名与属性值,属性值可以是任何字节序列,上图中节点a-sub-nod的属性foo,值为3,属性label,值为字符串“SUBNODE”。

在实际应用中,devicetree节点通常对应一些硬件,节点层次反映了硬件的物理布局,例如具有三个 I2C 外设的板,这些外设连接到 SoC 上的 I2C 总线控制器,示意图及DTS文件分别如下:

下面以xp_cortex_m7.dts为例看一下本次zephyr最小系统设备树内容,如下面四图所示:

设备树文件有*.dts、*.dtsi两种,其中,图3、图4是图1的xp_cortex_m7.dts引用头文件xpm7.dtsi内容,*.dts文件是一种ASCII文本对Device Tree的描述,一个*.dts文件对应一个ARM的machine,由于一个SOC可能有多个不同的电路板,而每个电路板拥有一个 *.dts。这些dts势必会存在许多共同部分,为了减少代码的冗余,设备树将这些共同部分提炼保存在*.dtsi文件中,供不同的dts共同使用。*.dtsi的使用方法,类似于C语言的头文件,在dts文件中需要进行include *.dtsi文件。当然,dtsi本身也支持include 另一个dtsi文件。

在xpm7.dtsi中可以看到类似于sram0:memory@40000000{}这样的子节点,其中40000000是一种新的设备树概念即单元地址,使用@符号将子节点与单元地址衔接。

下面了解一下设备树节点中的各种属性,设备树常用属性有以下几种:

compatible:代表的硬件设备的名称,推荐的格式是"vendor,device",“vendor部分是供应商名字,可以在 dts/bindings/vendor-prefixes.txt查询,“device部分为模块对应驱动的名字,构建系统使用 compatible 属性为节点找到 正确的绑定,作为驱动和设备(设备节点)的匹配依据,以图4为例,compatible = arm,pl011;参数为arm公司的pl011 驱动,而根目录下compatible = "ti,lm3s6965evb-qemu", "ti,lm3s6965";会发现没有ti这样的公司,也没有lm3s6965这样 的驱动,实际上这里的"ti,lm3s6965"为zephyrproject/zephyr/dts/arm目录下ti文件夹的lm3s6965.dtsi,而lm3s6965.dtsi 内完成了类似图4这样的驱动设备匹配工作。

label:标签属性与节点标签不是一个内容,标签属性依据设备驱动模型而来,例如UART、I2C等,设置label为I2C_0,则会 调用device_get_binding("I2C_0")并返回一个指向设备结构的指针,该结构可以传递给I2C API函数, 如 i2c_transfer() 生成的 C 头文件将包含该字符串的宏

reg:用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息,常见设置模式(address, length)。

status:描述设备的状态信息,okay代表设备正常运行,disable代表该设备尚未运行,fail代表表明设备不可操作,fail-sss代 表含义和“fail”相同,后面的 sss 部分是检测到的错误内容。

model:描述板子的型号或者芯片型号,仅仅是给人看的,无其他特殊用途。

#address-cells决定了子节点 reg 属性中地址信息所占用的字长(u32)。

#size-cells决定了子节点 reg 属性中长度信息所占用的字长(u32)。

name:用于记录节点名字,已废弃,不建议使用。

ranges:格式 <local地址, parent地址, size>, 表示将local地址向parent地址的转换,比如对于#address-cells和#size-cells 都为1的话,以<0x0  0x10 0x20>为例,表示将local的从0x0~(0x0 + 0x20)的地址空间映射到parent的0x10~(0x10 + 0x20),其中,local地址的个数取决于当前含有ranges属性的节点的#address-cells属性的值,size取决于当前含有ranges 属性的节点的#size-cells属性的值。而parent地址的个数取决于当前含有ranges属性的节点的parent节点的 #address-cells的值。对于含有ranges属性的节点的子节点来说,子节点reg都是基于local地址的,ranges属性值为空 的话,表示1:1映射。

配置系统Kconfig(重要)

zephyr 内核和功能子系统可以在构建时进行配置,以适应特定的应用和平台需求。配置通过 Kconfig 处理,这是 Linux 内核使用的相同配置系统。目标是无需更改任何源代码下支持各种配置。

Kconfig、Makefile、.config联系:Makefile是编译源文件的方法,最大的特点就是方便,把需要编译的文件都写Makefile里面,Kconfig是搭配Makefile文件一起使用Makefile里面编译的模块是从Kconfig中选取出来的,.config文件就是控制和选择那些文件编译或者不编译的。

设置Kconfig配置

Kconfig符号有可见和不可见两种,可见符号是用提示符定义的符号,在交互式界面上可以看到,示例如下:

    

在交互式界面上显示为:

不可见符号是没有提示的符号,交互配置界面中不显示隐形符号,用户无法直接控制其值,通过默认值或者其他符号来获取相应的值。如下例所示:

可见符号可以通过配置文件的配置来设置,初始配置在版信息的*_defconfig文件与应用程序设置合并而来,配置文件语法如下:

Kconfig基础语法

config语句:config 定义了一组新的配置选项,例如

config表示一个配置选项的开始,紧跟着的BSP_USING_GPIO是配置选项的名称,config下面几行定义了该配置选项的属性。属性可以是该配置选项的-类型-输入提示-依赖关系-默认值-帮助信息。其中,bool 表示配置选项的类型,每个 config 菜单项都要有类型定义。select是反向依赖关系的意思,即当前配置选项被选中,则RT_USING_PIN 就会被选中。default表示配置选项的默认值,bool类型的默认值可以是y/n。help为帮助信息。

变量类型:bool类型,布尔类型变量的取值范围是y/n;string类型,字符串变量的默认值是一个字符串;int类型,整型变量的取值范围是一个整型的数;hex类型,十六进制类型变量的取值范围是一个十六进制的数,其使用方法和整型变量用法一致,整型变量生成的是十进制的数,而十六进制生成的是十六进制的数;tristate类型(?),三态类型变量的取值范围是 y、n 和 m。tristate 代表在内核中有三种状态,n是不选中,y是选中直接编译进内核,还有一种是m表示将这个配置项编译为模块,而布尔类型变量只有两种状态,即选中和不选中,其使用方法和布尔类型变量类似。

menu/endmenu语句:menu用于生成菜单,endmenu菜单结束标志,两者一般成对出现,例如

source语句:用于调用其他路径下的Kconfig,类似C语言中#include<>

depend on与select:depends on表示一种依赖关系,select表示选择。

choice与endchoice: 将多个类似的配置项组合在一起,然后用户可以在menuconfig界面中进行选择,只能单选

comment:menuconfig界面注释,如下所示

初始配置设置

初始配置设置有三个来源,分别是1.BOARD特定的配置文件存储在boards/<architecture>/<BOARD>/<BOARD>_defconfig;

2.任何CMake 缓存中前缀为CONFIG_的条目;3.应用程序配置,且应用程序配置优先级高于<BOARD>_defconfig。合并后的配置保存zephyr/.config在构建目录中(.config只对可见符号配置起作用)。

其中,应用程序配置默认使用prj.conf,实际使用的配置按照优先级次序如下所示:

  1. 通过对CONF_FILE变量的设置来指定项目的conf文件,赋值语句为-DCONF _FILE=<file1.conf;file2.conf>,该语句需要在CMakeLists.txt中并且必须在调用find_package(Zephyr)之前,也可以通过CMake变量cache(?)来赋值;
  2. 使用应用目录下的prj_<build>.conf 和boards/<BOARD>_<build>.conf两者的合并结果;
  3. 使用应用目录下的prj_<BOARD>.conf ;
  4. 使用应用目录下的boards/<BOARD>.conf和prj.conf 的合并结果;
  5. 使用应用目录下的prj.conf ;

不可见符号的配置在boards/<architecture>/<BOARD>/Kconfig.defconfig这样一个常规Kconfig文件中配置完成。

自定义Kconfig预处理器函数

 自定义预处理器函数主要用于获取Kconfig中的设备树信息,例如设备树默认值可以从设备树reg属性信息获取。下面列出的函数用于将设备树信息获取到 Kconfig,每个函数的*_int版本都以十进制整数形式返回值,而*_hex版本则返回以0x开头的十六进制值有关详细文档,请参阅scripts/kconfig/kconfigfunctions.py

应用举例如下:

设备树为

使用预处理函数设置FLASH_BASE_ADDRESS

上述配置语句含义为获取/soc/spi@1001400子节点,序号1(第二个)block中reg_addr,并以16进制输出

经预处理函数处理扩展后,实际可转化为

Kconfig配置修改方法(重要)

  1. 两种交互式配置方式:menuconfig和guiconfig。
  2. 和linux内核配置一样,最终配置会写入.config文件,保存至zephyr/bulid/zephyr路径下,所以可以直接通过zephyr/.config来修改配置,这种修改方式无法处理好不同配置之间的依赖关系,不建议使用该方法修改配置。

menuconfig基础用法west build -t menuconfig或者 nijia menuconfig可以启动menuconfig交互配置界面。在menu底部是基础功能键说明,例如 [?]键可以显示symbol详细说明,[/]可以直接跳转到搜索框去搜索想要看的symbol。

guiconfig基础用法:west build -t guiconfig或者 nijia guiconfig可以启动guiconfig交互配置界面。选中一项symbol后,底部框格会有详细描述,点击Jump to symbol可以通过搜索的方式直接找到想要的symbol。

如果在menuconfig或guiconfig找一个symbol无法显示,一般是由于某种依赖关系不满足导致不可见,此时可以使用按键A(menuconfig)或者ctrl+A(guiconfig)切换全显示模式(再按一次A或者ctrl+A可推出全显示模式),就可找到所要查找的symbol,此时选中该symbol后点击[?]健(menuconfig)或者在底部框格(guiconfig)查看详细信息,一般可以显示出该symbol依赖的另一个未启用symbol。

仍然以xp_cortex_m7板子为例,设置CONFIG_SOC_XP_M7=n,此时在menuconfig中搜BOARD如图1所示,BOARD后为空,按键[A]选中<BOARD>后按键[?]可得图2所示,发现BOARD_XP_CORTEX_M7=n,同样的搜索方法去看BOARD_XP_CORTEX_M7为什么是未启用状态,如图3图4,发现依赖项SOC_XP_M7=n,再去搜索SOC_XP_M7发现属于未选状态,将其选中后即图5所示,此时再去搜索BOARD,可看到图6中xp_cortex_m7显示信息。

以上例子只是我特意构造的问题场景,以作参考,后续有类似问题可借鉴上述方法去定位问题原因。

zephyr构建系统

CMake 与 Zephyr 内核一起构建应用程序。CMake 构建分两个阶段完成。第一阶段称为 配置阶段( CMake 驱动)。在配置期间,将执行 CMakeLists.txt 构建脚本。配置后,通过执行生成的构建脚本开始第二阶段即构建阶段(Make 或 Ninja 驱动)。Zephyr 使用 CMake 的“目标”概念来组织构建。目标可以是可执行文件、库或生成的中间文件。程序开发人员需要重点理解库,进入 Zephyr 构建的所有源代码都是通过包含在库目标中来实现的。库目标通过 CMakeLists.txt 构建脚本添加,例如target_sources (app PRIVATE src/main.c)表示一个名为app的库文件被配置为包含了源文件src/main.c,关键字PRIVATE表示正在修改构建库的内部结构,如果使用关键字PUBLIC则除app之外的其他库也会受影响。

配置阶段

CMake 首先处理应用程序目录中的CMakeLists.txt文件,目录CMakeLists.txt指的是 Zephyr 顶目录中的文件,同时又指的是整个构建树中直接和间接(即调用)的CMakeLists.txt文件。它的主要输出是一组 Makefiles 或 Ninja 文件以便驱动构建过程,CMake 脚本也会自己进行一些处理。

*.dts和*.dtsi是从是从目标的架构、SoC、板和应用程序目录中收集的,*.dts通过c预处理器来include *.dtsi,此外,c预处理器也会处理合并设备树扩展文件*.overlay,并在dts、dtsi、overlay文件中实现宏扩展,预处理器最终输出文件为build/zephyr/zephyr.dts.pre。

 gen_defines.py解析处理zephyr.dts.pre生成包含预处理宏的devicetree_unfixed.h头文件。devicetree.h包含device tree_unfixed.h,源代码通过包含devicetree.h来访问设备树预处理宏。gen_defines.py将最终设备树信息写入zephyr.dts

以便debug使用。

  图中的dtc是一个设备树编译器,运行zephyr.dts可以获取意外告警和错误,未安装可以跳过。文件dts_fixup.h属于遗留功能,已废弃。

  Kconfig配置存储在配置文件中。初始配置是通过合并来自板和应用程序的配置片段(例如prj.conf)生成的。Kconfig的输出是头文件autoconf.h,以及保存了配置文件的.config文件。其中autoconf.h里边的定义在编译的时候会自动使用,无需手动添加包含头文件。Kconfig可以通过kconfigfunctions.py中定义的函数使用设备树信息

构建阶段(和west编译过程结合起来)

调用make或者ninja开始构建,最终输出是zephyr可执行文件(zephyr.elf、 zephyr.hex zephyr.bin),整个构建过程分为四个阶段:预构建,中间二进制文件,最终二进制文件, and post-processing

预构建

预构建是在真正编译源文件之前处理生成源文件需要使用的头文件。

生成offsets.h

在底层汇编代码中实现内核上文切换等情况时,通常会访问C中定义结构体内的成员,汇编就需要这些成员在数据结构中的偏移地址,offset.c利用gcc的特性,可以在编译后按照一定的规则生成一组宏定义来保存结构体成员的偏移地址,在汇编中通过这些宏就可以取得结构中体成员的偏移值,从而可以访问C结构体变量的成员。因为和上文切有关,因此这是架构相关的,不同的架构有不同的offset.c,cortex-m7的就是zephyr/arch/arm/core/offsets/offsets_ aarch32.c。生成流程是offset.c被gcc编译成obj文件, scripts/gen_offset_header.py解析obj文件,生成offsets.h,里面保存了偏移地址宏。

生成系统调用

  script/gen_syscall.py扫描头文件和c文件,生成系统调用的头文件gen_syscall.py和parse_ syscalls.py脚本一起工作以将潜在的系统调用函数与其对应的实现绑定在一起。

中间二进制文件

从各个子系统收集C和汇编源文件,根据配置阶段的头文件选择相应的代码进行编译,编译产出库文件

如果启用内存保护,则需要分区分组,gen_app_partitions.py脚本扫描所有生成的archives(?) 并输出链接器脚本,以确保应用程序分区正确分组并与目标的内存保护硬件对齐。cpp用于将来自SoC目标体系结构、内核树、分区输出(如果启用了内存保护)的链接器脚本片段以及配置过程中选择的任何其他片段组合到链接器中。然后,按照链接器中的指定,将编译的archives文件与ld按照链接器要求进行链接。

在启用用户模式或参考模式时,会生成大小不固定的二进制文件,这样也会影响最终二进制文件。在启用用户模式或中间向量表被使用时,配置CONFIG_GEN_ISR_TABLES会生成大小固定的二进制文件,该场景下中间二进制文件与最终二进制文件大小一致。

中间二进制文件的后期处理:前一阶段产生的二进制文件是不完整的,里边有空位或占位符,需要进行填充。通过后续相应脚本文件生成最终二进制文件的缺失部分,完善二进制文件。

当用户模式启动时:分区对齐

gen_app_partitions.py脚本扫描大小不固定的二进制文件,并生成应用程序共享内存对齐的链接器脚本片段,其中分区按降序排序。

注释zephyr提供一种降低权限级别运行线程的能力,称之为用户模式。用户模式主要是为具有MPU硬件的设备设计的,由CONFIG_USERSPACE选项配置确定是否为用户模式。

当使用Reference(?)时:设备依赖

gen_handles.py脚本扫描不固定大小的二进制文件通过设备树数据记录以确定设备之间的关系,优化编码关系以定位应用程序中实际存在的设备。

当使能 CONFIG_GEN_ISR_TABLES时:

gen_isr_tables.py脚本扫描固定大小的二进制文件并创建一个带有硬件向量表软件IRQ表的isr_tables.c 源文件

当启用用户模式时:内核对象哈希处理

gen_kobject_list.py扫描ELF DWARF调试数据以查找所有内核对象的地址。这个列表被传递给gperf ,它生成一个完整的

哈希函数和地址表格,输出内容会再经过一轮process_gperf.py的优化。如果不需要二进制文件后处理(?),则第一个二进制文件直接作为最终的二进制文件输出。

最终二进制文件

上一阶段产生的二进制文件仍是不完整的,里边有空位或占位符,需要进行填充完善。重复上一阶段的链接,填充缺失的部分

后期处理

最后,如有必要,将完成的内核从ELF转换为目标所需的加载程序或闪存工具所期望的格式。这是通过objdump以简单的方式完成的。一些情况下zephyr.elf并不能满足实际的需求,例如zephyr的镜像会被bootloader加载(需要bin),或者是一些flash tool进行烧写(hex或bin),因此zephyr的构建系统会在最后将elf转化为bin或者hex。

zephyr os基于M7启动过程

zephyr 启动到main主要有下面几个步骤: (每个阶段做了什么事,最好画图)

汇编阶段(arch/arm/core/cortex_m/vector_table.S arch/arm/core/cortex_m/reset.S)

C准备阶段(arch/arm/core/cortex_m/prep_c.)

初始化阶段(kernel/init.c )

启动流程图见末尾

 汇编阶段

vector_table.S 中定义了向量表,芯片每次上电复位之后,0x0000 0000地址内容固定读取到SP,里面存放的是栈顶的地址(z_main_stack + CONFIG_MAIN_STACK_SIZE), 按顺序执行,0x0000 0004 地址的内容读取到PC,存放的是reset函数地址(z_arm_reset),CPU上电之后跳到 z_arm_reset。

CONFIG_MAIN_STACK_SIZE,决定栈的大小,后期可以调整来优化。

中断向量表的定义参见cortex-M7的开发文档

z_arm_reset完成下面的工作:(主要是前期初始化工作)

当系统完成重置,或固件映像处于链接状态时,由一个应用程序(例如,引导加载程序)加载。1、此时处理器必须处于具有特权级别的线程模式。在这一点上主堆栈指针(MSP)应该已经指向SRAM中的有效区域。2、锁定中断可防止除NMI和硬故障以外的任何故障中断CPU。默认的NMI处理程序已在向量表,启动代码不应该产生硬错误,否则会有大麻烦。3、zephyr希望使用进程堆栈指针(PSP)而不是MSP,因为MSP应设置为在运行期间指向一个唯一的中断堆栈。如果用于运行C代码是不可行的。4、完成前述步骤后,跳到z_arm_prep_c(),这将完成设置运行C代码的系统。

#if defined(CONFIG_INIT_ARCH_HW_AT_BOOT)

/* 复位控制寄存器* /

movs.n r0, #0

msr CONTROL, r0

isb/*指令隔离,保证所有前边指令执行完毕才会执行后边的指令*/

#if defined(CONFIG_CPU_CORTEX_M_HAS_SPLIM)

/ Clear SPLIM registers(清除堆栈指针限制寄存器) /

movs.n r0, #0

msr MSPLIM, r0

msr PSPLIM, r0

........

中间包括disable MPU、core寄存器/时钟初始化、

........

/*设置PSP并用其来boot,并在初始化期间设置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

bl z_arm_prep_c /*跳转到 z_arm_prep_c*/

11.2 C准备阶段

z_arm_prep_c 主要干了下面几件事

C准备阶段的最后调用z_cstart()函数,进行内核初始化工作;

11.3 初始化阶段

kernel/init.c 的z_cstart函数主要完成thread环境准备,低级驱动初始化,切换到main thread运行

arch_kernel_init(); /*内核初始化*/

z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);

z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);

这两个函数完成了低级设备的驱动初始化工作。

zephyr中设备驱动共分四级:

#define _SYS_INIT_LEVEL_PRE_KERNEL_1 0

#define _SYS_INIT_LEVEL_PRE_KERNEL_2 1

#define _SYS_INIT_LEVEL_POST_KERNEL 2

#define _SYS_INIT_LEVEL_APPLICATION 3

设备模型:

struct device {

const char *name; /*设备实例名*/

const void *config; /*设备实例配置信息的地址*/

const void *api; /*设备实例公开的API结构的地址*/

struct device_state * const state; /*公共设备状态的地址*/

void * const data; /*设备实例私有数据的地址*/

const device_handle_t *const handles; /*指向与设备关联的句柄的可选指针*/

#ifdef CONFIG_PM_DEVICE

struct pm_device * const pm; /*参考设备PM资源*/

#endif

};

注:zephyr中大多使用 I2C_DEVICE_DT_DEFINE 宏来处理设备驱动。如下示例:

使用STM32_I2C_INIT(i2c1) 结合 I2C_DEVICE_DT_DEFINE 宏,实现I2C1外设的设备驱动关联。

              

设备驱动启动后,若配置为多线程,则调用:prepare_multithreading();switch_to_main_thread();若未配置多线程,      

则(默认)调用:bg_thread_main(NULL, NULL, NULL);

11.4 线程准备

static void bg_thread_main(void *unused1, void *unused2, void *unused3)

{

#ifdef CONFIG_MMU

z_mem_manage_init();

#endif /* CONFIG_MMU */

z_sys_post_kernel = true;

z_sys_init_run_level(_SYS_INIT_LEVEL_POST_KERNEL);

z_sys_init_run_level(_SYS_INIT_LEVEL_APPLICATION);/*进行 _SYS_INIT_LEVEL_POST_KERNEL

 及 _SYS_INIT_LEVEL_APPLICATION

 级别的设备初始化工作*/

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(_SYS_INIT_LEVEL_SMP);

#endif

#ifdef CONFIG_MMU

z_mem_manage_boot_finish();

#endif /* CONFIG_MMU */

extern void 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

}

11.5启动流程图

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值