一、Zephyr RTOS 是什么?

Zephyr 是 Linux 基金会旗下的开源实时操作系统(RTOS),2016 年由 Intel、NXP、Nordic 等厂商联合发起,目前已成为嵌入式领域最具活力的 RTOS 项目之一。

核心特征

多架构支持。 Zephyr 支持 ARM (Cortex-M/R/A)、x86、RISC-V、ARC、Xtensa、MIPS、SPARC 等十余种 CPU 架构,覆盖从资源受限的 MCU(如 Cortex-M0+,~8KB RAM)到带 MMU 的 MPU(如 Cortex-A)的广泛硬件谱系。

完整的平台体系。 Zephyr 不止是一个内核调度器。它内置了驱动模型、设备树(Devicetree)、Kconfig 配置系统、构建系统(CMake + west)、网络协议栈、蓝牙协议栈、文件系统、Shell、日志子系统、USB 协议栈、电源管理等一整套平台级组件。这是它和 FreeRTOS 等纯内核 RTOS 最本质的区别——后文会展开讨论。

模块化与可配置。 通过 Kconfig 菜单系统,你可以在编译前精细裁剪内核和子系统——不用的模块不参与编译,零代码体积开销。

上游优先。 Zephyr 强调板级代码和驱动尽量合入上游主线,减少厂商 fork,避免生态碎片化。

开源治理。 采用 Apache 2.0 许可证,由 Linux 基金会技术指导委员会管理,Intel、NXP、Nordic、ST、TI 等均为白金/银牌成员。


二、Zephyr 源码目录结构

下面是 Zephyr 主仓库的顶层目录结构和各目录的职责说明:

zephyr/
├── arch/                   # 架构相关代码
├── boards/                 # 板级支持
├── cmake/                  # CMake 构建脚本
├── doc/                    # 官方文档(RST/Sphinx)
├── drivers/                # 设备驱动
├── dts/                    # 设备树源文件和绑定
├── include/                # 公共头文件
├── kernel/                 # 内核核心实现
├── lib/                    # 通用库
├── modules/                # 外部模块(HAL、MCUboot 等)
├── samples/                # 示例程序
├── scripts/                # 工具脚本
├── share/                  # 共享资源
├── soc/                    # SoC 级定义和初始化
├── subsys/                 # 子系统
├── tests/                  # 测试用例
├── CMakeLists.txt          # 顶层 CMake
├── Kconfig                 # 顶层 Kconfig
└── west.yml                # 多仓库管理清单

逐目录说明

arch/ —— 架构层

包含不同 CPU 架构的底层实现:

子目录 说明
arch/arm/core/ ARM 架构的启动代码、异常/中断向量、上下文切换、堆栈初始化
arch/arm/core/cortex_m/ Cortex-M 专项:NVIC 配置、MPU 支持、SysTick、浮点上下文
arch/x86/ x86 架构(Intel 处理器),包含 MMU、IOAPIC 等
arch/riscv/ RISC-V 架构,支持 RV32/RV64,MMU 和 CLIC 中断控制器

架构层的责任是:让内核和驱动不关心 CPU 差异。启动序列、线程切换的汇编、特权级别切换、MMU/MPU 配置、异常处理都在这里。

boards/ —— 板级支持

每个板子一个目录,包含:

  • Kconfig 板级默认配置——这个板子的默认串口是 UART0,默认晶振频率是 32MHz……
  • 板级设备树文件——描述板上有什么外设、它们接在哪个引脚、I2C/SPI 地址是什么
  • 板级文档——板子照片、引脚图、跳线说明

例如 boards/nordic/nrf52840dk_nrf52840/ 就是 Nordic nRF52840-DK 的板级支持。

drivers/ —— 设备驱动

这是 Zephyr 最大的目录之一,按外设类型分层:

drivers/
├── gpio/          # GPIO 驱动
├── i2c/           # I2C 控制器驱动
├── spi/           # SPI 控制器驱动
├── uart/          # 串口驱动
├── sensor/        # 传感器驱动(加速度、温度、湿度等 100+ 种)
├── pwm/           # PWM 驱动
├── adc/           # ADC 驱动
├── dac/           # DAC 驱动
├── counter/       # 定时器/计数器驱动
├── watchdog/      # 看门狗驱动
├── flash/         # Flash 存储驱动
├── bluetooth/     # 蓝牙 HCI 驱动
├── usb/           # USB 控制器驱动
├── can/           # CAN 控制器驱动
├── ethernet/      # 以太网驱动
├── dma/           # DMA 控制器驱动
├── clock_control/ # 时钟控制器驱动
├── pinctrl/       # 引脚复用控制
├── regulator/     # 电源调节器驱动
└── ...

Zephyr 的驱动模型是统一的:所有 GPIO 驱动实现同一个 gpio_driver_api,上层代码通过 gpio_pin_set()gpio_pin_get() 等统一 API 操作任何厂家的 GPIO。驱动是 Zephyr 平台价值的核心体现——它屏蔽了芯片差异,让上层业务逻辑可以跨硬件复用。

dts/ —— 设备树

包含两部分:

  • dts/bindings/:设备树绑定(YAML 格式),定义每种设备节点允许的属性和类型。这是编译期校验设备树正确性的依据。
  • SoC 和板级 .dts/.dtsi 文件:描述具体芯片的外设基地址、中断号、引脚分配等硬件事实。

设备树是 Zephyr 硬件描述的核心机制。它让同样的驱动代码在不同板子上运行时,只需换一套设备树,无需修改驱动源码。

include/ —— 公共头文件

include/zephyr/ 下按功能分子目录:

include/zephyr/
├── kernel/        # 内核 API(线程、信号量、互斥锁、队列、消息队列…)
├── drivers/       # 驱动 API 头文件
├── sys/           # 系统工具(环形缓冲、原子操作、C++ 互操作…)
├── net/           # 网络 API
├── bluetooth/     # 蓝牙 API
├── usb/           # USB API
├── logging/       # 日志宏
├── shell/         # Shell 框架
├── posix/         # POSIX 子集(pthread、信号…)
├── dt-bindings/   # 设备树宏定义头文件
└── ...
kernel/ —— 内核核心

Zephyr 内核的实现代码,包括:

  • 调度器(多优先级、时间片轮转、最早截止时间优先 EDF)
  • 线程管理(创建、终止、挂起、恢复、优先级调整)
  • 同步原语(信号量、互斥锁、条件变量、事件、屏障)
  • 数据传递(消息队列、管道、栈、队列)
  • 内存管理(内存池、堆分配器、内存域、虚拟地址空间)
  • 中断管理(ISR 注册、延迟中断下半部 workqueue)
  • 时钟和超时管理
  • SMP 多核支持
subsys/ —— 子系统

这是 Zephyr 区别于纯内核 RTOS 的关键目录之一:

子系统 说明
subsys/logging/ 日志系统(支持多后端:串口、RTT、网络、文件……)
subsys/shell/ 命令行 Shell(支持 UART、Telnet、USB CDC ACM 后端)
subsys/fs/ 文件系统(LittleFS、FATFS 等)
subsys/net/ 网络栈(含 IP/TCP/UDP/HTTP/MQTT/CoAP/TLS/DTLS)
subsys/bluetooth/ 蓝牙 Host 协议栈(GAP/GATT/L2CAP/Mesh)
subsys/usb/ USB 协议栈(CDC/DFU/HID/MSC/Audio)
subsys/storage/ 持久化存储(Flash Map、NVS、Settings API)
subsys/mgmt/ 设备管理(MCUmgr OTA 固件升级)
subsys/dfu/ 设备固件升级引导
subsys/pm/ 电源管理策略
subsys/tracing/ 内核事件追踪
subsys/modbus/ Modbus 协议栈
subsys/canbus/ CAN 总线高层协议
soc/ —— SoC 级定义

SoC 层级夹在"架构"和"板级"之间:同一个架构(如 ARM Cortex-M4)可以有很多不同厂家(Nordic nRF52、ST STM32、NXP i.MX RT)的 SoC。此目录存放 SoC 特定的初始化代码、寄存器定义、Flash/RAM 布局等。

modules/ —— 外部模块

Zephyr 通过 west.yml 管理外部依赖(HAL 层、MCUboot、LittleFS、mbedTLS、LVGL 等),这些外部仓库被下载到 modules/ 下统一编译。每个模块有一个 zephyr/module.yml 或使用 zephyr/module.yml 描述如何集成。

samples/ —— 示例程序

按子系统分类的完整示例,如:

  • samples/basic/blinky/ —— 经典的 LED 闪烁(最小示例)
  • samples/basic/threads/ —— 线程创建和调度
  • samples/subsys/shell/shell_module/ —— Shell 使用示例
  • samples/net/sockets/echo/ —— Socket 网络通信

这是学习 Zephyr 最好的入口。

tests/ —— 测试

Zephyr 的测试基础设施,每个驱动/子系统都有对应的测试用例,通过 twister 测试框架在 CI 中运行。

scripts/ —— 工具脚本

包含 west 命令的实现、twister 测试框架、menuconfig 配置工具、设备树工具链(edtlibdtlib)、编译工具链适配脚本等。

cmake/ —— 构建系统

Zephyr 基于 CMake 的构建框架。此目录包含通用的 CMake 函数和宏,供应用项目和模块使用,例如:

  • cmake/kconfig.cmake —— Kconfig 到 C 宏的转换
  • cmake/dts.cmake —— 设备树到 C 宏的处理
  • cmake/toolchain.cmake —— 交叉编译工具链配置
  • cmake/flash.cmake —— 烧录目标

三、Zephyr 应用项目与 Zephyr 源码的关系

典型应用项目结构

my_zephyr_app/
├── CMakeLists.txt          # CMake 构建入口
├── prj.conf                # Kconfig 项目配置
├── src/
│   ├── main.c              # 主程序
│   └── ...                 # 其他源文件
├── boards/                 # 板级配置(可选)
│   └── nrf52840dk_nrf52840.overlay  # 设备树 Overlay
├── include/                # 头文件(可选)
├── lib/                    # 自定义库(可选)
└── README.md

核心目录/文件说明

CMakeLists.txt

项目的构建入口。最关键的一行是:

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)

target_sources(app PRIVATE src/main.c)

find_package(Zephyr) 会加载 Zephyr 源码中的 CMake 框架,把 Zephyr 的 arch、kernel、drivers、subsys 等全部作为编译对象纳入同一个 CMake 构建图。你的应用和 Zephyr 系统一起编译,共享同一个构建上下文——这是理解 Zephyr 应用项目关系的关键。

prj.conf

应用层的 Kconfig 配置。你可以在这里启用/禁用内核和子系统功能:

CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_SHELL=y
CONFIG_LOG=y
CONFIG_BT=y

这些配置会与 Zephyr 源码中的 Kconfig 文件自动合并,最终只编译你需要的模块。

boards/<board>.overlay

设备树 Overlay 文件。它不会修改 Zephyr 源码中的板级设备树,而是在编译时叠加到基础设备树上,用于:

  • 启用板子上某个外设(Zephyr 默认可能没启用)
  • 修改引脚分配
  • 配置外设参数(如 I2C 频率)
  • 添加板载外设的传感器节点
&i2c0 {
    status = "okay";
    bme280@76 {
        compatible = "bosch,bme280";
        reg = <0x76>;
    };
};

应用与源码的关系模型

可以用一张图来理解:

┌──────────────────────────────────────────┐
│           my_zephyr_app                   │
│  ┌─────────┐  ┌─────────┐  ┌──────────┐ │
│  │ main.c  │  │ prj.conf│  │ .overlay │ │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘ │
│       │             │             │       │
└───────┼─────────────┼─────────────┼───────┘
        │             │             │
        ▼             ▼             ▼
┌──────────────────────────────────────────┐
│        CMake + Kconfig + Devicetree       │  ← 构建系统层(Zephyr 源码提供)
│    find_package(Zephyr) 把它们全部串起来  │
└──────────────────────────────────────────┘
        │             │             │
        ▼             ▼             ▼
┌──────────────────────────────────────────┐
│           Zephyr 源码树                   │
│  ┌────────┐ ┌──────────┐ ┌────────────┐ │
│  │ kernel │ │ drivers  │ │  subsys    │ │
│  │  arch  │ │ include  │ │  modules   │ │
│  └────────┘ └──────────┘ └────────────┘ │
└──────────────────────────────────────────┘
        │
        ▼
┌──────────────────────────────────────────┐
│           交叉编译工具链                   │
│        arm-none-eabi-gcc / llvm           │
└──────────────────────────────────────────┘
        │
        ▼
┌──────────┐
│  ELF 固件 │
└──────────┘

关键结论:

  1. 应用和 Zephyr 不是分离编译再链接的两个独立产物。 find_package(Zephyr) 把你的 src/main.c 和 Zephyr 的 kernel/drivers/ 等源代码放入同一次 CMake 构建调用中,最终产出单个 ELF + hex/bin
  2. Zephyr 是底座,应用是上面的逻辑。 Zephyr 提供了内核、驱动、协议栈、构建系统;你的应用只需关注业务逻辑,通过 prj.conf 裁剪和 .overlay 适配硬件。
  3. 应用项目不产生自己的 SDK。 应用只有一个 CMakeLists.txt + 一些配置文件 + 应用源代码。它依赖的环境变量 ZEPHYR_BASE 指向 Zephyr 源码树,所有底层能力都由 Zephyr 源码提供。

四、“Zephyr 不能当 middleware”——从平台视角理解 Zephyr 的抽象边界

很多从 FreeRTOS 转过来的开发者会问:“能不能把 Zephyr 像 FreeRTOS 一样,当做一个 middleware 组件塞进我们的 SDK 里?”

答案是:可以,但通常不应该这样做。 这不是 Zephyr 的能力问题,而是两种不同的设计范式。

FreeRTOS 的边界:内核 + 少量组件

FreeRTOS 的核心交付物是:

  • 一个任务调度器
  • 一组同步原语(信号量、互斥锁、队列、事件组)
  • 软件定时器
  • 可选的内存管理方案(heap_1 ~ heap_5)
  • 可选的 TCP/UDP 协议栈和 CLI

概括一下:FreeRTOS 主要覆盖线程调度和基本的 RTOS IPC,它几乎不碰驱动、外设、板级硬件描述、构建系统、配置框架这些东西。所以它能很自然地作为一个 “middleware” 组件嵌入到任何 SDK 里——它要的只是底层的 tick 时钟和一个启动入口,剩下的交给厂商的 HAL/SDK 就行。

Zephyr 的边界:完整的 OS 平台

Zephyr 的抽象边界完全不一样。它覆盖了:

层面 FreeRTOS 覆盖 Zephyr 覆盖
内核调度
同步原语和 IPC
统一设备驱动模型 ✓(drivers/ + 标准驱动 API)
硬件描述语言 ✓(Devicetree)
配置系统 ✓(Kconfig)
构建系统框架 ✓(CMake + west)
多仓库依赖管理 ✓(west.yml)
网络/蓝牙/USB 协议栈 可选+有限 ✓(完整集成)
文件系统
Shell / 日志 可选
电源管理
设备管理 / OTA

Zephyr 的抽象边界已经做到了OS 平台级别。它的构建/配置/设备树/驱动模型/依赖管理是一个紧密耦合的整体,很难像 FreeRTOS 那样干净地切出一个独立的内核组件放进另一个构建体系。

不是你技术不行,是"两套平台叠加"的工程现实

假设你要把 Zephyr 塞进一个厂商 SDK——

那个 SDK 大概率已经有:

  • 自己的构建系统(Make / IAR / 自定义脚本)
  • 自己的外设驱动(HAL / LL / stdperiph)
  • 自己的配置方式(头文件宏 / CubeMX 代码生成)
  • 自己的板级管理方式
  • 自己的链接脚本和启动流程

然后你再加上 Zephyr 的:

  • CMake + Kconfig + Devicetree 构建和配置体系
  • 统一驱动模型
  • 板级和 SoC 级设备树定义

结果是什么?平台叠平台。 构建时要协调两套构建系统,初始化时要协调两套启动流程,驱动要决定用谁的(用 Zephyr 的就得放弃 SDK 的 HAL 封装,用 SDK 的就得放弃 Zephyr 的跨硬件可移植性),引脚配置要两头配置保持一致……

这就像在 iOS 上再跑一套 Android 框架——两套完整的平台系统同时存在,协调成本远远超过收益。

抽象层不是问题,合理的抽象边界才是

有一种常见的误解是:“Zephyr 的抽象层太厚了,所以不好当 middlelayer。” 其实恰恰相反:

合理的抽象层可以把不可避免会变的东西给隔离掉——比如芯片差异、外设 IP、板级差异。 这样上层业务逻辑才能稳定。Zephyr 的驱动模型、设备树、Kconfig 正是在做这件事——而且做得很好。你在 nRF52840 上写的 I2C 传感器驱动,换到 STM32 上只需改设备树,代码一行不用动。

真正的问题不是"抽象层太厚",而是抽象边界放在哪一层

  • FreeRTOS 的边界在"内核",它可以很容易地被嵌入到任何 SDK 中——代价是驱动和外设的统一需要另想办法(通常是厂商 SDK 各搞一套,上层的可移植性靠你自己的 HAL 层)。
  • Zephyr 的边界在"OS 平台",它自己就是底座——好处是驱动、配置、构建、依赖统一搞定,代价是它不能简单地被人塞进另一个平台里当 middlelayer。

内核厂商为什么统一不了驱动 API?

一个自然的问题是:为什么内核厂商不把驱动 API 也统一了?这样 FreeRTOS 不就能像 Zephyr 一样有跨硬件的驱动可移植性了吗?

答案在于驱动 API 统一需要的东西超出了内核的能力范围

  • 设备模型:不是简单的函数指针表,而是包含设备生命周期(init→power→suspend→resume)、设备依赖关系(SPI 依赖 GPIO 引脚,传感器依赖 SPI/I2C 总线)、设备电源域归属
  • 配置机制:一个 UART 驱动在没有 Kconfig 的情况下如何让用户选择波特率?靠 #define 宏和 #ifdef 条件编译凑出来的地狱?
  • 硬件描述:一个 I2C 设备在第几个 I2C 控制器上、地址是多少、中断线接哪个 GPIO——这些信息放哪里?硬编码在 C 文件里?那换一个板子怎么办?
  • 构建链接:不同芯片的驱动源文件不同,片选需要构建系统的支持,不是靠 if defined(STM32F4) include "stm32f4_i2c.c" 就能搞定的
  • 依赖管理:某些驱动依赖外部的 HAL 库、协议栈或者算法库,谁来管理版本兼容性?

这些不是"一个调度器 + 一组同步原语"能解决的范畴。它们需要的是一个操作系统平台的体系——设备模型 + 配置机制 + 硬件描述 + 构建链接 + 依赖管理,五者缺一不可。

所以结论是:真正能统一驱动的,要么是 Zephyr/RT-Thread 这样把自己定位成"平台"的系统,要么是某个强势厂商自己的全栈 SDK(但那会锁死生态)。纯 RTOS 内核天然覆盖不了驱动统一这件事。

总结:选平台还是选 middleware,本质是选谁做底座

FreeRTOS 模式 Zephyr 模式
定位 内核 + 少量组件 完整的 OS 平台
抽象边界 RTOS IPC 层 从硬件到子系统的全栈
集成方式 嵌入到厂商 SDK 中 它就是底座,厂商 HAL 适配到它之上
驱动模型 依赖厂商 SDK 统一的跨平台驱动 API
硬件描述 无标准(头文件/代码生成) Devicetree 标准化描述
可移植性 需要你自己的抽象层 换板子改设备树即可
多仓库管理 无(手工管理版本) west.yml 声明式管理
适合场景 已有成熟 SDK 平台,只需一个调度器 从零构建跨硬件产品线,需要完整 OS 平台

Zephyr 让你觉得"麻烦",恰恰是因为它在做平台。你要么接受它做底座,让上层业务逻辑享受跨硬件复用的红利;要么就别把它当 FreeRTOS 那样的 middleware——这两种用法背后的设计哲学不同,没有高下之分,只有适合不适合。

但如果你今天还在找"把 Zephyr 塞进 SDK 当 middleware 的方法"——那不是 Zephyr 的设计缺陷,而是你对它的定位预期和它的设计边界发生了错位。理解了这个,你才算真正理解了 Zephyr。


五、接下来

本文只讲了"是什么"和"为什么"。后续计划写:

  1. Zephyr 的编译与构建流程:CMake + Kconfig + Devicetree 三者的协作机制
  2. 在电脑上跑第一个 Zephyr 程序:环境搭建、编译、用 QEMU 仿真运行
  3. 建立自己的板子设备树:从零写一个板级 Devicetree,在自己板子上跑通电灯程序

欢迎关注。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐