FreeRTOS 与 Zephyr 核心区别详解:从任务创建到系统对象管理

ARM / RISC-V 芯片设计生成全流程深度报告(2026):工具链、技术栈与制造工艺 从产品定义一路走到量产爬坡:13 个阶段、4 大工程域、全阶段 EDA 工具矩阵、先进制程与封装工艺、成本与周期模型、签核清单与风险登记册,并标注 ARM 与 RISC-V 两条路线的真实差异。 阅读详情

摘要

FreeRTOS 和 Zephyr 都是嵌入式领域常见的实时操作系统,但二者的定位和工程组织方式存在明显差异。FreeRTOS 更偏向轻量级 RTOS 内核,核心关注任务调度、信号量、队列、互斥锁等基础实时机制;Zephyr 则更像一个完整的嵌入式操作系统生态,除内核外,还包含设备树、Kconfig、驱动模型、构建系统、网络协议栈、蓝牙协议栈、文件系统、电源管理等完整框架。

本文重点从“任务创建机制”切入,分析 FreeRTOS 与 Zephyr 在动态创建、静态分配、编译期定义、系统自动初始化等方面的差异,并进一步扩展到信号量、队列、定时器、work、内存池、设备驱动和系统初始化机制,帮助理解二者在设计思想上的核心区别。


一、FreeRTOS 与 Zephyr 的整体定位

可以先用一句话概括二者区别:

FreeRTOS 更像一个轻量级 RTOS 内核;
Zephyr 更像一个完整的嵌入式操作系统生态。

FreeRTOS 主要提供 RTOS 内核能力,例如:

任务调度
任务同步
任务间通信
信号量
队列
互斥锁
事件组
软件定时器
内存管理
中断与任务协作

Zephyr 除了具备 RTOS 内核能力,还提供了大量系统级框架:

devicetree 设备树
Kconfig 配置系统
west + CMake 构建系统
统一设备驱动模型
蓝牙协议栈
网络协议栈
文件系统
电源管理
日志系统
MCUboot OTA
多板卡支持

因此,二者的差异并不只是 API 名称不同,而是整体设计思想不同:

FreeRTOS 更关注 RTOS 内核本身;
Zephyr 更关注完整嵌入式系统工程。

二、整体对比

对比项FreeRTOSZephyr
定位轻量级 RTOS 内核完整嵌入式操作系统生态
上手难度较低较高
工程风格简单、直接、显式控制系统化、模块化、声明式
硬件描述HAL / BSP / 用户代码devicetree
配置方式FreeRTOSConfig.hKconfig / prj.conf
构建系统Keil、IAR、Makefile、CMake 等west + CMake
驱动模型不强制统一统一 device model
资源占用更轻量相对更完整,也更复杂
适合场景小型 MCU、实时控制、资源受限设备多外设、多协议、BLE、OTA、复杂产品级系统

如果只是学习 RTOS 的基础机制,例如任务、队列、信号量、互斥锁、调度和中断同步,FreeRTOS 更直接。

如果目标是构建复杂嵌入式系统,例如 BLE 设备、IoT 终端、可穿戴设备、传感器节点、低功耗数据采集系统、OTA 升级、多板卡适配项目,Zephyr 的系统化优势会更加明显。


三、二者都有 RTOS 基本能力

FreeRTOS 和 Zephyr 都支持常见 RTOS 能力:

任务 / 线程
优先级调度
抢占式调度
信号量
互斥锁
消息队列
事件机制
软件定时器
中断同步
内存管理

FreeRTOS 中常见 API 包括:

xTaskCreate();
xTaskCreateStatic();
xSemaphoreTake();
xSemaphoreGive();
xQueueSend();
xQueueReceive();
vTaskDelay();

Zephyr 中常见 API 包括:

k_thread_create();
k_sem_take();
k_sem_give();
k_msgq_put();
k_msgq_get();
k_sleep();

从内核机制角度看,二者都可以实现多任务调度和任务间同步。

真正的区别在于:

FreeRTOS 更轻、更直接;
Zephyr 更完整、更工程化。

四、理解二者差异的关键:动态手动操作 vs 静态声明式操作

在比较 FreeRTOS 和 Zephyr 时,一个非常重要的维度是:

对象是运行时手动创建,还是编译期静态定义?
对象只是静态分配内存,还是被系统自动识别和初始化?

FreeRTOS 的典型风格是:

用户准备对象所需内存;
用户在运行时调用 API 创建或初始化对象;
对象什么时候创建、是否创建,由应用代码显式控制。

Zephyr 的典型风格是:

使用 XXX_DEFINE 宏静态定义对象;
对象在编译期或启动阶段被系统识别;
部分对象会在系统启动阶段自动初始化或注册。

因此可以简化理解为:

FreeRTOS 偏“手动显式控制”;
Zephyr 偏“声明式系统组织”。

任务创建是这个差异最典型的体现。


五、FreeRTOS 的任务创建方式

FreeRTOS 创建任务主要有两种方式:

xTaskCreate();        // 动态创建任务
xTaskCreateStatic();  // 静态内存创建任务

1. 动态创建任务:xTaskCreate()

典型代码如下:

void LedTask(void *arg)
{
    while (1)
    {
        // 翻转 LED
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    xTaskCreate(
        LedTask,       // 任务函数
        "LedTask",    // 任务名
        128,           // 任务栈大小
        NULL,          // 参数
        2,             // 优先级
        NULL           // 任务句柄
    );

    vTaskStartScheduler();

    while (1)
    {
    }
}

执行过程可以理解为:

main() 开始运行
    ↓
调用 xTaskCreate()
    ↓
FreeRTOS 从 heap 中分配任务 TCB 和任务栈
    ↓
任务加入就绪列表
    ↓
调用 vTaskStartScheduler()
    ↓
调度器开始运行任务

这种方式的特点是:

任务创建发生在运行时;
任务栈和 TCB 来自 FreeRTOS heap;
使用灵活,但依赖动态内存分配。

可能带来的问题包括:

heap 空间不足
动态内存分配失败
内存碎片
运行时内存不可控

六、FreeRTOS 的静态任务创建:xTaskCreateStatic()

FreeRTOS 也支持静态创建任务:

#define LED_TASK_STACK_SIZE 128

static StaticTask_t ledTaskTCB;
static StackType_t ledTaskStack[LED_TASK_STACK_SIZE];

void LedTask(void *arg)
{
    while (1)
    {
        // 翻转 LED
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    xTaskCreateStatic(
        LedTask,                 // 任务函数
        "LedTask",               // 任务名
        LED_TASK_STACK_SIZE,      // 栈大小
        NULL,                    // 参数
        2,                       // 优先级
        ledTaskStack,             // 用户提供的任务栈
        &ledTaskTCB               // 用户提供的 TCB
    );

    vTaskStartScheduler();

    while (1)
    {
    }
}

其中:

static StaticTask_t ledTaskTCB;
static StackType_t ledTaskStack[LED_TASK_STACK_SIZE];

这两个对象是静态分配的,通常位于 .bss 段。

也就是说:

任务栈:编译期已经分配好
任务 TCB:编译期已经分配好

但需要注意:

xTaskCreateStatic(...);

这句代码仍然需要在运行时执行。只有执行到这句 API 调用后,任务才真正加入 FreeRTOS 内核并被调度器管理。

因此,FreeRTOS 的 xTaskCreateStatic() 更准确的理解是:

任务内存静态分配;
任务创建和注册仍然在运行时手动完成。

七、FreeRTOS 中 Static 的真正含义

xTaskCreateStatic() 中的 Static,重点不是“任务已经在编译期创建完成”,而是:

任务所需内存是静态分配的。

也就是说:

任务栈不从 heap 申请;
任务 TCB 不从 heap 申请;
用户提前提供任务所需内存。

它主要解决的是:

不用 malloc
不依赖 FreeRTOS heap
避免任务创建造成 heap 碎片
内存占用更确定
更适合工业控制、车规、功能安全场景

但是它并不表示任务在编译期已经进入调度系统。

更准确地说:

FreeRTOS 支持任务内存静态分配;
但任务创建动作仍然发生在运行时。

八、Zephyr 的静态线程定义:K_THREAD_DEFINE()

Zephyr 也支持运行时创建线程:

k_thread_create();

但 Zephyr 还有一种更典型的静态线程定义方式:

K_THREAD_DEFINE();

示例:

#define STACK_SIZE 1024
#define PRIORITY 5

void my_thread(void *p1, void *p2, void *p3)
{
    while (1)
    {
        // 线程逻辑
        k_sleep(K_MSEC(500));
    }
}

K_THREAD_DEFINE(my_tid, STACK_SIZE,
                my_thread,
                NULL, NULL, NULL,
                PRIORITY, 0, 0);

这里最大的不同是:

不需要在 main() 中手动调用 k_thread_create()

K_THREAD_DEFINE() 会静态定义线程栈、线程对象和初始化信息。Zephyr 系统启动时,会自动处理这些静态定义的线程,并将其加入调度系统。

因此,Zephyr 的 K_THREAD_DEFINE() 可以理解为:

线程栈:静态定义
线程对象:静态定义
线程初始化信息:静态定义
线程注册:系统启动阶段自动完成

九、任务创建机制的核心对比

对比项FreeRTOS xTaskCreateStatic()Zephyr K_THREAD_DEFINE()
任务栈是否静态分配是是
TCB / 线程对象是否静态分配是是
是否使用 heap否否
是否避免 heap 碎片是是
是否需要用户手动调用创建函数需要不需要
创建动作发生时间程序运行到 API 时系统启动阶段自动处理
工程风格显式创建声明式定义
适合场景手动控制初始化顺序模块化、自注册、复杂系统

最核心的区别是:

FreeRTOS 的 xTaskCreateStatic():
静态分配任务内存,但仍然需要用户在运行时手动调用 API 创建任务。

Zephyr 的 K_THREAD_DEFINE():
静态定义线程,系统启动时自动创建并加入调度。

用更直观的话说:

FreeRTOS:提前准备好任务栈和 TCB,但还要在 main() 里手动登记任务。

Zephyr:在代码里声明这个线程,系统启动时自动完成登记。

十、任务创建时机对实际运行的影响

如果 FreeRTOS 和 Zephyr 都静态分配了任务栈和任务控制块,那么任务真正运行起来之后,调度效果通常不会因为创建方式不同而产生本质差别。

任务进入调度系统后,调度器关心的是:

任务优先级
任务状态
任务栈
任务控制块
同步对象
调度策略

至于任务是手动 API 创建,还是系统启动阶段自动创建,对后续调度本身影响不大。

真正的差异主要体现在:

初始化顺序
模块组织方式
运行时灵活性
系统对象管理方式

十一、影响一:初始化顺序

FreeRTOS 中,用户通常在 main() 中显式控制初始化顺序:

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_SPI1_Init();

    xTaskCreateStatic(SensorTask, ...);
    xTaskCreateStatic(CommTask, ...);
    xTaskCreateStatic(LogTask, ...);

    vTaskStartScheduler();

    while (1)
    {
    }
}

流程非常清晰:

先初始化硬件;
再创建任务;
最后启动调度器。

因此用户可以明确保证:

UART 初始化完成后,再创建串口任务;
SPI 初始化完成后,再创建传感器任务;
Flash 初始化完成后,再创建存储任务。

Zephyr 的 K_THREAD_DEFINE() 是静态定义线程,线程会在系统启动阶段自动创建。Zephyr 更依赖自己的系统初始化机制,例如:

devicetree
device model
SYS_INIT
device_is_ready()
初始化优先级

Zephyr 应用中常见写法是:

if (!device_is_ready(dev))
{
    return;
}

因此可以理解为:

FreeRTOS:用户自己手动保证初始化顺序;
Zephyr:更多依赖系统框架管理初始化顺序。

十二、影响二:运行时条件创建任务

FreeRTOS 手动创建任务非常适合运行时条件判断:

if (config.enable_ble)
{
    xTaskCreateStatic(BleTask, ...);
}

if (config.enable_log)
{
    xTaskCreateStatic(LogTask, ...);
}

典型场景包括:

读取 Flash 配置后决定是否开启日志任务;
检测到某个模块存在后创建对应任务;
进入 Bootloader 模式时不创建业务任务;
根据设备型号创建不同任务。

这种方式非常灵活。

Zephyr 的 K_THREAD_DEFINE() 更偏向静态声明,线程是否存在通常在编译期就已经确定。如果不希望线程马上运行,可以使用:

延迟启动
挂起线程
等待信号量
运行时 k_thread_create()

因此:

FreeRTOS 手动创建:更适合运行时决定任务是否存在;
Zephyr 静态定义:更适合固定架构和模块化系统。

十三、影响三:模块化组织方式

Zephyr 的 K_THREAD_DEFINE() 更适合大型工程中的模块自注册。

例如某个传感器模块可以直接定义自己的线程:

K_THREAD_DEFINE(sensor_tid, STACK_SIZE,
                sensor_thread,
                NULL, NULL, NULL,
                PRIORITY, 0, 0);

只要该模块被编译进系统,传感器线程就自动存在。

再配合 Kconfig:

CONFIG_SENSOR_SERVICE=y
CONFIG_LOG_SERVICE=y
CONFIG_BLE_SERVICE=y

可以形成如下机制:

模块启用 → 模块被编译 → 模块线程被定义 → 系统启动时自动创建线程
模块关闭 → 模块不编译 → 对应线程也不存在

FreeRTOS 通常需要用户集中组织任务创建:

void app_tasks_create(void)
{
    SensorTask_Create();
    CommTask_Create();
    LogTask_Create();
}

或者直接在 main() 中创建:

int main(void)
{
    hardware_init();

    SensorTask_Create();
    CommTask_Create();
    LogTask_Create();

    vTaskStartScheduler();
}

这种方式简单直观,但项目变大后,任务创建入口可能变得越来越集中,初始化逻辑也可能逐渐复杂。


十四、静态分配不等于不会栈溢出

一个常见误区是:

任务栈已经静态分配,是否就不会栈溢出?

答案是否定的。

静态分配任务栈只能说明:

任务栈空间提前分配好了;
不需要从 heap 中动态申请。

但如果任务运行时使用的栈超过了这块空间,仍然会发生栈溢出。

例如 FreeRTOS 中:

static StackType_t sensorTaskStack[256];

或者 Zephyr 中:

K_THREAD_STACK_DEFINE(sensor_stack, 1024);

这些只是定义了一块固定大小的任务栈。

如果任务中存在较大的局部变量:

void SensorTask(void *arg)
{
    uint8_t buf[2000];

    while (1)
    {
        // ...
    }
}

而任务栈大小不足,仍然可能发生栈溢出。

因此需要明确:

静态分配解决的是 heap 问题;
栈溢出取决于任务实际栈使用量是否超过分配大小。

影响栈溢出的因素包括:

任务栈大小
局部变量大小
函数调用层级
是否递归
是否使用 printf / sprintf
是否使用浮点格式化
库函数内部栈消耗
中断和异常栈配置

因此,任务是手动创建还是自动创建,并不能决定是否会栈溢出。


十五、静态分配主要解决的问题

无论是 FreeRTOS 的 xTaskCreateStatic(),还是 Zephyr 的 K_THREAD_DEFINE(),静态分配主要解决的是:

避免动态内存分配
避免 heap 碎片
内存占用更确定
避免运行时任务创建 malloc 失败
提高系统可预测性
适合工业控制、车规、功能安全场景

它不能解决:

任务栈给得太小
函数调用层级太深
局部数组太大
递归导致栈持续增长
printf / sprintf 消耗大量栈

因此不能简单地认为:

静态分配 = 不会栈溢出

更准确的理解是:

静态分配避免的是 heap 碎片和动态内存不确定性;
栈溢出需要靠合理估算栈大小和运行时检测来避免。

十六、除任务外,二者还有类似区别吗?

有。

任务只是最典型的例子。除了任务创建之外,信号量、互斥锁、队列、消息队列、软件定时器、work、内存池、设备驱动和系统初始化函数等,都能体现 FreeRTOS 与 Zephyr 在设计风格上的差异。

可以概括为:

FreeRTOS:
很多对象可以静态分配内存,但通常仍然需要运行时手动调用 API 创建或初始化。

Zephyr:
很多对象可以用 XXX_DEFINE 宏静态定义并初始化,部分对象还能被系统启动流程自动处理。

下面分别说明。


十七、信号量与互斥锁

1. FreeRTOS

FreeRTOS 中信号量通常运行时创建:

SemaphoreHandle_t sem;

sem = xSemaphoreCreateBinary();

如果不希望使用 heap,可以静态创建:

static StaticSemaphore_t sem_buf;
SemaphoreHandle_t sem;

sem = xSemaphoreCreateBinaryStatic(&sem_buf);

这里:

StaticSemaphore_t 是用户提供的静态内存;
xSemaphoreCreateBinaryStatic() 仍然需要运行时调用。

互斥锁也类似:

static StaticSemaphore_t mutex_buf;
SemaphoreHandle_t mutex;

mutex = xSemaphoreCreateMutexStatic(&mutex_buf);

2. Zephyr

Zephyr 可以直接静态定义信号量和互斥锁:

K_SEM_DEFINE(my_sem, 0, 1);
K_MUTEX_DEFINE(my_mutex);

这些对象在定义时就已经静态初始化完成。

3. 对比

FreeRTOS:
静态内存 + 运行时 create

Zephyr:
DEFINE 宏 + 对象静态初始化

十八、队列与消息队列

1. FreeRTOS

FreeRTOS 队列动态创建:

QueueHandle_t q;

q = xQueueCreate(10, sizeof(uint32_t));

静态创建:

#define QUEUE_LEN 10

static StaticQueue_t queue_ctrl;
static uint8_t queue_storage[QUEUE_LEN * sizeof(uint32_t)];

QueueHandle_t q;

q = xQueueCreateStatic(
    QUEUE_LEN,
    sizeof(uint32_t),
    queue_storage,
    &queue_ctrl
);

这里需要两部分静态内存:

queue_storage:存放队列消息内容
queue_ctrl:队列控制块

但队列真正被 FreeRTOS 内核识别,仍然发生在运行到 xQueueCreateStatic() 时。

2. Zephyr

Zephyr 可以静态定义消息队列:

K_MSGQ_DEFINE(my_msgq, sizeof(uint32_t), 10, 4);

也可以定义其他队列类对象:

K_QUEUE_DEFINE(my_queue);
K_FIFO_DEFINE(my_fifo);
K_LIFO_DEFINE(my_lifo);

这些对象通过宏完成静态定义和初始化。

3. 对比

FreeRTOS:
用户准备存储区和控制块,再运行时调用 API 创建队列。

Zephyr:
通过宏声明队列对象,对象静态存在并已初始化。

十九、软件定时器

1. FreeRTOS

动态创建软件定时器:

TimerHandle_t timer;

timer = xTimerCreate(
    "timer",
    pdMS_TO_TICKS(1000),
    pdTRUE,
    NULL,
    TimerCallback
);

静态创建:

static StaticTimer_t timer_buf;
TimerHandle_t timer;

timer = xTimerCreateStatic(
    "timer",
    pdMS_TO_TICKS(1000),
    pdTRUE,
    NULL,
    TimerCallback,
    &timer_buf
);

这里的 StaticTimer_t 是用户提供的静态内存,但仍然要运行时调用 xTimerCreateStatic()。

2. Zephyr

Zephyr 可以静态定义 timer:

K_TIMER_DEFINE(my_timer, expiry_fn, stop_fn);

但需要注意:

定义 timer 不等于 timer 已经开始运行。

启动定时器仍然需要运行时调用:

k_timer_start(&my_timer, K_SECONDS(1), K_SECONDS(1));

3. 对比

FreeRTOS:
静态内存 + 运行时创建 timer

Zephyr:
静态定义并初始化 timer 对象,但启动动作仍然是运行时行为

二十、Work / 延迟处理机制

1. FreeRTOS

FreeRTOS 没有完全对应 Zephyr k_work 的统一对象模型。常见做法是使用:

任务
队列
信号量
事件组
软件定时器

自己实现“中断下半部”或延迟处理。

例如中断中释放信号量:

xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken);

任务中等待并处理:

xSemaphoreTake(sem, portMAX_DELAY);

这种方式非常灵活,但需要用户自己组织架构。

2. Zephyr

Zephyr 有统一的 workqueue 机制,可以静态定义 work item:

K_WORK_DEFINE(my_work, my_work_handler);
K_WORK_DELAYABLE_DEFINE(my_delay_work, my_delay_work_handler);

但 work 定义后不会自动执行,仍然需要运行时提交:

k_work_submit(&my_work);

或者延迟提交:

k_work_schedule(&my_delay_work, K_MSEC(100));

3. 对比

FreeRTOS:
通常通过任务 + 队列/信号量手动组织延迟处理。

Zephyr:
提供统一 workqueue 抽象,可静态定义 work 对象,运行时提交执行。

二十一、内存池 / heap / slab

1. FreeRTOS

FreeRTOS 内存管理通常围绕不同的 heap 实现:

heap_1.c
heap_2.c
heap_3.c
heap_4.c
heap_5.c
pvPortMalloc()
vPortFree()

如果希望避免动态内存,通常采用:

任务使用 xTaskCreateStatic()
队列使用 xQueueCreateStatic()
信号量使用 xSemaphoreCreateXXXStatic()
定时器使用 xTimerCreateStatic()
应用 buffer 自己定义全局数组或静态数组

也就是说,FreeRTOS 更强调:

用户自己提供静态内存,再调用 API 创建对象。

2. Zephyr

Zephyr 提供更多静态定义内存对象的宏,例如:

K_MEM_SLAB_DEFINE(my_slab, 64, 16, 4);
K_HEAP_DEFINE(my_heap, 4096);

例如:

K_MEM_SLAB_DEFINE(packet_slab, 128, 10, 4);

表示定义一个内存 slab:

每块 128 字节
一共 10 块
4 字节对齐

运行时从 slab 中申请内存:

void *block;
k_mem_slab_alloc(&packet_slab, &block, K_NO_WAIT);

3. 对比

FreeRTOS:
内存池通常由用户自己组织,或使用 FreeRTOS heap 机制。

Zephyr:
内存池、slab、heap 都可以作为系统对象静态定义。

二十二、设备驱动与设备对象

这一部分是 FreeRTOS 和 Zephyr 工程化差异最明显的地方。

1. FreeRTOS

FreeRTOS 本身通常不管理统一的设备对象。

在 STM32 + FreeRTOS 项目中,常见方式是:

MX_GPIO_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
MX_I2C1_Init();

xTaskCreate(...);
vTaskStartScheduler();

外设初始化通常由 HAL、BSP 或用户代码完成。

例如使用 UART:

HAL_UART_Receive_DMA(&huart1, rx_buf, len);

FreeRTOS 本身并不知道这个 UART 是一个“系统设备对象”。它主要负责任务调度、同步和通信。

2. Zephyr

Zephyr 驱动是系统设备模型的一部分。

驱动通常通过设备树、Kconfig 和驱动宏注册到系统中。应用层通常这样获取设备对象:

const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(uart0));

if (!device_is_ready(dev))
{
    return;
}

Zephyr 中设备对象可以在系统启动阶段按照初始化 level 和 priority 自动初始化。

这体现了 Zephyr 的典型设计:

设备由 devicetree 描述;
驱动由 device model 管理;
系统启动时自动初始化设备;
应用通过 device API 获取和使用设备。

3. 对比

FreeRTOS:
外设初始化通常由用户在 main() 或 board_init() 中手动调用。

Zephyr:
设备对象由 devicetree + driver model 静态声明,系统启动时自动初始化。

Zephyr 也支持延迟初始化模式,即某些设备不在启动时自动初始化,而是在应用运行时手动调用初始化函数。这说明 Zephyr 同样保留了运行时控制能力,只是默认工程风格更偏系统化。


二十三、系统初始化函数

1. FreeRTOS

FreeRTOS 项目一般由用户在 main() 中组织初始化流程:

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    board_init();
    app_init();

    xTaskCreate(...);

    vTaskStartScheduler();
}

初始化顺序主要由用户显式安排。

2. Zephyr

Zephyr 提供 SYS_INIT() 机制,可以让模块自己注册初始化函数:

static int my_module_init(void)
{
    // 初始化模块
    return 0;
}

SYS_INIT(my_module_init, APPLICATION, 50);

这样模块初始化函数会在系统启动过程中,按照指定初始化阶段和优先级自动执行。

这体现了一种模块自注册思想:

模块自己声明初始化函数;
系统启动时按 level / priority 自动调用;
main() 不需要集中管理所有模块初始化。

3. 对比

FreeRTOS:
初始化流程主要靠 main() / app_init() 手动组织。

Zephyr:
模块可以通过 SYS_INIT() 注册初始化函数,由系统启动流程统一调度。

二十四、Zephyr 静态对象不等于一定自动运行

需要特别注意:

Zephyr 中很多 XXX_DEFINE 宏表示对象已经静态定义并初始化,但不一定表示对象会自动运行。

例如:

K_SEM_DEFINE:信号量对象已经初始化好,但 take/give 是运行时操作
K_MUTEX_DEFINE:互斥锁对象已经初始化好,但 lock/unlock 是运行时操作
K_TIMER_DEFINE:timer 对象已经初始化好,但启动还要 k_timer_start()
K_WORK_DEFINE:work 对象已经初始化好,但执行还要 k_work_submit()

因此,Zephyr 的静态宏更准确的含义是:

对象静态存在,并且已经完成必要初始化;
是否自动执行,取决于对象类型。

其中任务线程 K_THREAD_DEFINE() 比较特殊,它会在系统启动阶段自动创建并加入调度。

而定时器、work、信号量、队列等对象,通常只是被静态初始化,具体动作仍然需要运行时调用 API 触发。


二十五、核心设计风格总结

可以用下面这张表总结 FreeRTOS 和 Zephyr 在对象管理上的典型区别:

类型FreeRTOS 风格Zephyr 风格
任务 / 线程xTaskCreateStatic():静态内存 + 运行时创建K_THREAD_DEFINE():静态定义 + 启动时自动处理
信号量xSemaphoreCreateBinaryStatic()K_SEM_DEFINE()
互斥锁xSemaphoreCreateMutexStatic()K_MUTEX_DEFINE()
队列xQueueCreateStatic()K_MSGQ_DEFINE() / K_QUEUE_DEFINE()
定时器xTimerCreateStatic()K_TIMER_DEFINE()
延迟处理通常任务 + 队列/信号量自行组织K_WORK_DEFINE()
内存池用户数组 / heap_x / 自定义内存池K_MEM_SLAB_DEFINE() / K_HEAP_DEFINE()
驱动对象HAL/BSP 手动初始化devicetree + device model
系统初始化main() / app_init() 手动安排SYS_INIT() 自动注册初始化函数

这张表可以进一步抽象为:

FreeRTOS 的静态方式:
    用户把内存提前准备好;
    对象什么时候创建、初始化、加入系统,由用户运行时手动调用 API 决定。

Zephyr 的静态方式:
    用户用宏把对象声明出来;
    系统在构建或启动阶段识别这些对象;
    部分对象由系统自动完成初始化或注册。

二十六、驱动模型与硬件描述差异

除了任务和内核对象外,二者在硬件描述方式上的差别也非常明显。

1. FreeRTOS

FreeRTOS 项目中,硬件信息通常来自:

芯片头文件
HAL 初始化代码
CubeMX 生成代码
board.c
bsp.c
用户配置宏

例如:

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);

这种方式简单直接,但不同项目之间的驱动组织方式差异较大。

2. Zephyr

Zephyr 使用设备树描述硬件,例如:

/ {
    leds {
        compatible = "gpio-leds";

        led0: led_0 {
            gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
            label = "Green LED";
        };
    };

    aliases {
        led0 = &led0;
    };
};

C 代码中通过设备树宏获取硬件信息:

#define LED0_NODE DT_ALIAS(led0)

static const struct gpio_dt_spec led =
    GPIO_DT_SPEC_GET(LED0_NODE, gpios);

Zephyr 的思想是:

硬件信息放在 devicetree;
功能开关放在 Kconfig;
应用逻辑放在 C 代码;
驱动通过统一 device model 管理。

二十七、配置系统差异

1. FreeRTOS

FreeRTOS 主要通过 FreeRTOSConfig.h 配置内核:

#define configUSE_PREEMPTION        1
#define configUSE_TIMERS            1
#define configMAX_PRIORITIES        5
#define configTOTAL_HEAP_SIZE       (10 * 1024)
#define configUSE_MUTEXES           1

这种方式简单直接。

2. Zephyr

Zephyr 使用 Kconfig 和 prj.conf:

CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_LOG=y
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y

构建系统会根据配置决定:

启用哪些内核功能
编译哪些驱动
启用哪些协议栈
占用多少 Flash 和 RAM

因此:

FreeRTOS 配置简单直接;
Zephyr 配置系统更复杂,但更适合大型系统裁剪。

二十八、构建系统差异

FreeRTOS 本身不强制使用某种构建系统,可以使用:

Keil
IAR
STM32CubeIDE
Makefile
CMake
PlatformIO

Zephyr 通常使用:

west
CMake
Kconfig
devicetree
overlay
prj.conf

典型命令为:

west build -b nrf52840dk/nrf52840
west flash

因此:

FreeRTOS 更像一个可以嵌入到工程中的 RTOS 内核库;
Zephyr 更像一个有完整构建体系和系统框架的嵌入式 OS。

二十九、资源占用与适用场景

一般来说:

FreeRTOS 更轻量;
Zephyr 更完整,也相对更复杂。

FreeRTOS 更适合:

Flash 和 RAM 较小
功能相对单一
实时控制为主
外设数量不复杂
希望快速上手
已有 HAL / BSP 工程

典型场景:

电机控制
传感器采集
串口通信
小型控制器
简单数据采集设备
低成本 MCU 项目

Zephyr 更适合:

BLE 设备
IoT 终端
可穿戴设备
多外设数据采集
需要 OTA
需要文件系统
需要低功耗管理
需要跨平台适配
复杂产品级嵌入式系统

三十、学习建议

如果目标是掌握 RTOS 内核基础,建议先学习 FreeRTOS。

重点掌握:

任务是什么
任务栈是什么
优先级调度是什么
抢占式调度是什么
信号量如何同步
队列如何传递数据
互斥锁如何保护共享资源
中断如何通知任务
任务栈水位如何检测

如果已经掌握基本 RTOS 概念,想进一步学习复杂嵌入式系统工程,可以继续学习 Zephyr。

重点掌握:

devicetree
Kconfig
device model
west + CMake
BLE 协议栈
MCUboot OTA
LittleFS / NVS
低功耗管理
模块化设计
多板卡适配

一个较合理的学习路径是:

先用 FreeRTOS 理解 RTOS 内核本质;
再用 Zephyr 学习复杂嵌入式系统的工程化组织方式。

三十一、最终总结

FreeRTOS 和 Zephyr 都是嵌入式实时操作系统,但设计侧重点不同。

最核心的区别可以概括为:

FreeRTOS 是轻量级 RTOS 内核;
Zephyr 是完整嵌入式操作系统生态。

在任务创建机制上:

FreeRTOS 的 xTaskCreateStatic()
强调的是“任务内存静态分配”,
但任务仍然需要运行时手动调用 API 创建。

Zephyr 的 K_THREAD_DEFINE()
强调的是“线程静态定义”,
系统启动时会自动创建并加入调度。

进一步扩展到其他对象:

FreeRTOS:
很多对象可以使用静态内存,但通常仍需要运行时手动 create/init。

Zephyr:
很多对象可以通过 XXX_DEFINE 宏静态定义并初始化,部分对象还能被系统启动流程自动处理。

但需要注意:

静态分配可以避免 heap 碎片;
静态分配不能避免栈溢出;
栈溢出取决于栈大小和运行时栈使用量;
任务创建方式主要影响初始化顺序、模块组织和运行时灵活性;
任务创建完成后,调度效果主要取决于优先级、任务状态和调度策略。

用一句话总结二者风格:

FreeRTOS 偏手动、显式、轻量、可控;
Zephyr 偏声明式、模块化、系统化、工程化。

因此,在学习和项目选择上:

如果目标是理解 RTOS 内核机制,FreeRTOS 更适合入门;
如果目标是构建复杂嵌入式产品,Zephyr 更能体现系统工程能力。
Pharma Labs采取的Anthropic 模型硬件需求 来自IntuitionLabs的论文“Anthropic Model Hardware Requirements for Pharma Labs”。IntuitionLabs 是一家面向药企和生物技术公司的 AI 咨询与软件服务公司(2026 年 9 月 19 日)。 阅读详情

相关推荐

HALCON深度学习模型部署新选择:一份详细的OpenVINO 2021.4 LTS集成与配置避坑指南

本文详细介绍了如何将HALCON深度学习模型与OpenVINO 2021.4 LTS集成,提供环境配置、硬件加速优化及故障排除的实用指南。特别针对Intel独立显卡的AI加速器接口进行优化,帮助机器视觉工程师提升AI推理效率,适用于工业检测等高要求场景。

weixin_42531779的博客 238

用机器码复活Am29000:在x86上模拟RISC处理器并运行窗口化OS

计算机模拟器技术是理解计算机体系结构的重要工具,它通过软件模拟目标硬件的行为,实现跨平台运行。其核心原理是解释执行目标指令集,将访存、中断等操作映射到宿主系统。这项技术的价值在于为系统研究、软件移植和硬件验证提供了灵活环境,广泛应用于嵌入式开发、操作系统教学和复古计算等领域。本文聚焦于一个经典案例:1996年用纯机器码编写的Am29000模拟器,它完整模拟了这颗32位RISC处理器,并成功运行了窗口化操作系统。通过剖析这个项目,可以深入理解模拟器设计、硬件抽象和系统集成等底层技术,特别是机器码级优化和图形输

weixin_30824599的博客 431

Excel数字变成了小数点+E+17?这些方法帮你解决!

本文介绍了三种解决Excel中数字变成小数点+E+17问题的方法:通过下拉箭头或右键菜单设置单元格格式为文本、使用TEXT函数将科学计数法转换为文本。这些方法各有优缺点,适用于不同的场景和需求。在实际应用中,我们可以根据具体情况选择合适的方法来解决问题。通过以上方法,我们可以轻松地从繁琐的小数点+E+17问题中解脱出来,更便捷地处理Excel中的数字。希望本文能够帮助到您,让您在使用Excel时更加得心应手!

nntxthml的博客 1245

arm-none-eabi-gcc与arm-eabi-gcc区别:嵌入式交叉编译工具链选型指南

交叉编译是嵌入式开发的基础技能,而工具链的选择往往决定项目成败。很多开发者混淆arm-none-eabi-gcc与arm-eabi-gcc,导致编译通过后程序却无法运行。本文从EABI规范讲起,拆解工具链命名含义:none代表裸机环境,linux代表目标系统,并深入分析newlib与glibc库差异、启动流程差异及编译产物特征。通过file、readelf等命令可快速验证工具链类型。结合实际应用场景,给出STM32裸机、Linux开发板等工程的选型参考,同时提供arm-none-eabi-gcc官方下载安装

weixin_33850890的博客 329

Cadence元件库介绍

Cadence安装完之后,在安装目录下,会有默认自带的一些常用元件库。例如,我的Candece安装目录为: D:\Cadence\Cadence_SPB_17.2-2016 元件库所在目录为: D:\Cadence\Cadence_SPB_17.2-2016\tools\capture\library 各个元件库对应的内容如下: AMPLIFIER.OLB 共 182 个零件,存放模拟放大器 IC,如 CA3280,TL027C,EL4093 等。 ARITHMETIC.OLB 共 182 个零件,存放

年少时的棉花糖 1万+

WeDetect:基于视觉-语言检索的目标检测新范式

目标检测正从传统回归与分类耦合范式,向开放、灵活、可扩展的多模态检索范式演进。检索式检测(Retrieval-based Detection)将检测任务重构为视觉特征与文本语义的联合匹配过程,依托候选框索引库与双塔架构,天然支持开放词汇、零样本识别与小目标定位。其技术价值在于解耦空间感知与语义理解,消除NMS与anchor匹配等冗余计算,显著提升推理效率与跨场景泛化能力。典型应用场景包括工业质检中的缺陷新增识别、遥感图像小目标检测、低光照鲁棒视觉系统等。WeDetect正是这一范式的代表性实现,深度融合视觉

weixin_30878501的博客 361

自建云手机,ARM服务器和X86服务器怎么选?架构差异、成本、兼容性全对比

小体量、试验证选 X86;大规模、追求稳定和兼容性选 ARM。ARM 原生是云手机技术的主流方向,但不等于人人都适合自建 ARM 集群。评估的时候,不要只看服务器单价,把后续运维、电费、排错成本一起算进去,再决定是自建,还是直接使用成熟的云手机服务。

a598652的博客 312

玩转ubuntu18 --机器视觉方面(yolo算法识别及摄像头和cmake问题解决)

玩转ubuntu18 --机器视觉方面(yolo v3算法识别) 1.YOLO的简介 工作原理 先前的检测系统重新调整分类器或本地化器以执行检测。他们将模型应用于多个位置和比例尺的图像。图像的高分区域被视为检测。 我们使用完全不同的方法。我们将单个神经网络应用于整个图像。该网络将图像划分为区域,并预测每个区域的边界框和概率。这些边界框按预测概率加权。 我们的模型比基于分类器的系统具有几个优势。它在测试时间查看整个图像,因此其预测由图像中的全球上下文提供信息。它还通过单一网络评估进行预测,这与R-CNN等系统

qq_53951823的博客 1556

为什么学习率预热(warmup)能显著改善Transformer等大模型的训练稳定性?

t$ 的分母 $(1-\beta_2^t)$ 极小,导致方差估计值波动剧烈。若此时 $\eta$ 较大,更新步长 $\Delta \theta$ 将不可控。Adam 优化器的参数更新公式为 $\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}{warmup}$ 时人为压低 $\eta_t$,从而保证 $\Delta \theta$ 的数值稳定性。在训练初期($t$ 很小),$\hat{v}{warmup}}$,在 $t < T。

2402_84593233的博客 179

PyCharm 接入 OpenAI Codex CLI 的 4 步配置与调试实录

揭示 Codex CLI 在 PyCharm 中上下文失稳的四大根源:CLI 启动模式与 IDE 进程模型冲突、stdin/stdout 流式处理不兼容、环境变量污染、AST 注释缺失,并给出基于 Shell 脚本桥接与结构化上下文注入的稳定接入方案。

weixin_42627902的博客 372

【信息系统项目管理师】第1章:信息化发展 考点梳理

本文主要详细梳理高项:信息化、新型基础设施建设、两化融合、数字中国、数字化转型与元宇宙等内容。

数据知道的博客 8569

轨交车载 ARM+FPGA+AI 控制单元:宽温、振动、EMC、可靠性设计

❌ 把 EtherCAT 主站、安全采集放在 ARM Linux 里,PREEMPT_RT 再怎么调,振动 + EMC 叠加下依然有概率丢帧 ✅ 总线时序、同步采集交给 FPGA,ARM 只做协议栈解析❌ AI 算力卡直接挂在 ARM 总线上、无隔离,NPU 大电流波动污染电源,导致 FPGA 采集漂移 ✅ AI 卡独立隔离电源,PCIe 增加隔离 / 缓冲,FPGA 侧做电源质量监控❌ 盲目上全机冗余,成本暴涨;

szxinmai的博客 354

17|CI/CD 集成与质量门禁:让精准测试进入发布流水线

本文讲解精准测试如何接入 CI/CD 流水线,并通过质量门禁辅助发布决策。测试开发工程师。Java 后端开发工程师。质量平台 / DevOps 平台建设者。想了解精准测试落地方法的技术负责人。

专注 AI 精准测试平台实战落地,分享从代码 Diff 到回归决策的完整工程闭环。主张“先建数据链路,再上 AI 智能”,让测试从“靠经验”走向“基于风险”。 65

Zynq 的 AXI GPIO,是“ARM 通过一条国际标准高速公路(AXI 总线),去遥控 FPGA 里的一个寄存器”。---deepseek

同学,你现在看 AXI GPIO,它还仅仅是个点灯的工具吗?它是Zynq 软硬协同的“微缩景观”。它告诉我们:在 Zynq 的世界里,ARM 通过 AXI 总线,把“软件控制”变成了“硬件动作”。你懂了 AXI GPIO,就懂了 AXI DMA、AXI 中断的底层原理。它们全都是通过 AXI 总线,把数据或信号从 PS 端(软件)搬到 PL 端(硬件)。内存映射 + AXI 握手。你的代码之所以可靠,是因为背后有极其严密的总线协议在兜底。

AlibabaApple的博客 473

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Dharma Field Extractor 解读:让 2B 专用模型按 Schema 从文档图像抽取 JSON

解读 Dharma Field Extractor 的 schema 字段抽取、训练路线、评测指标与部署边界。

m_aigc2022的博客 398

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

AllData 可定义数据中台完成信创适配|银河麒麟 + 海光X86 CPU / 华为鲲鹏ARM CPU+ OceanBase / 人大金仓国产化环境部署落地

银河麒麟 + 海光X86 CPU / 华为鲲鹏ARM CPU+ OceanBase / 人大金仓国产化环境部署落地

AllDataDC的博客 260

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

RS485半双工通信模块驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

本文详细分析了RK3568平台RS485串口在单板自发自收失败的常见原因,强调半双工特性下A/B悬空或无对端时RX为空属正常现象。重点指出:write成功仅表示数据进入UART FIFO,不代表信号已发送;需通过A/B短接回环验证链路通断;正确配置DE/RE控制(GPIO或硬件自动方向)、设备树、权限及串口参数;避免与RS232混淆。推荐使用uart_rs485.c工具进行完整测试,确保收发器供电、极性正确、tcdrain完成,最终以“发回显”作为过单板标准。

wanwuzhineng_01的博客 372

ARM 函数调用时参数和返回值到底放在哪里?AAPCS 协议详解与实验平台实战

但在底层的 ARM 汇编层面,这些参数是如何从调用者传递给被调用者的?返回值又是如何带回的?│ ADD R0, R0, R2 ← 从 R2 取第3个参数 │。│ ADD R0, R0, R1 ← 从 R0, R1 取前两个参数 │。

teach2004的博客 84

ARM 内存管理

最早期的计算机,程序是打在卡片上的,CPU 读一张卡片执行一条指令,效率极低。后来有了内存,程序要先加载到内存再执行,也就是 “存储程序” 概念,这才开启了操作系统的发展。1. 单道编程的内存管理2. 多道编程的内存管理:解决 “同时跑多个程序” 的问题为了提升资源利用率,出现了。

qq_52240295的博客 330

FPGA 脉冲伺服 + 编码器采集,ARM 主控数据交互完整方案(Zynq / 独立 FPGA+NXP i.MX/STM32 通用)

本文系统阐述FPGA与ARM协同实现高精度脉冲伺服控制的架构设计,核心为“FPGA做硬实时信号处理,ARM负责轨迹规划与上层逻辑”。重点解决丢脉冲、Z相误判、数据撕裂等工程痛点:采用四倍频正交解码、硬件Z相锁存、双口乒乓缓冲及跨时钟域CDC防护;推荐Zynq平台使用AXI-Lite交互,独立方案用DPRAM+FMC;强调参数整帧交换、差分走线与实时系统部署,确保5Mpps高速下稳定运行,适用于多轴高精度运动控制场景。

XMAIPC_Robot的博客 308

ARM 不懂寄存器,它只懂内存地址---deepseek

您这样一说,我之前在FPGA端使用AXI总线去读写DDR外设,就联系上了;请老师,可以分析对比下,ARM与FPGA交互使用AXI,与直接使用FPGA读写ddr(AXI接口)区别;同学,坐!你能把“FPGA直接读写 DDR”和“ARM 通过与 FPGA 进行 AXI 交互”联系起来,说明你的“任督二脉”已经打通了。这两件事,在底层的物理世界里,确实走的是同一条高速公路(AXI 总线)。但它们的和有着本质的区别。

AlibabaApple的博客 303

OpenHarmony 应用无感读写公共目录方案:不改设备形态,跳过权限弹窗

方案用户感知是否改设备形态适用场景运行时权限弹窗有感(需点允许)否标准形态设备,用户可接受交互改 devicetype=tablet无感是(全 ROM 迁移,风险大)需整机形态迁移的产品系统预授权(推荐)完全无感否需公共目录读写且用户不可感知的场景应用沙箱完全无感否仅供本应用使用的私有文件最终建议:在不修改设备形态标识的前提下,通过系统配置文件按“包名 + 签名指纹”做安装时预授权,是实现 OpenHarmony 应用无感读写公共目录的最务实路径。

qq_44160056的博客 254

React Native for OpenHarmony 实战:react-native-file-logger 日志库鸿蒙化适配

本文记录了将 react-native-file-logger 适配至 OpenHarmony 的实战过程。基于鸿蒙 7.0 模拟器,通过 5 个 TurboModule 方法实现日志落盘、轮转、列举与删除,采用 fileIo 实现核心功能,支持按大小和跨天轮转,归档文件命名精确至毫秒。邮件发送功能因平台限制按能力边界处理,明确拒绝并提示用户手动导出。验证通过,日志文件结构清晰,内容完整,产出 HAP 示例与独立 HAR 库,中英双语文档齐全,为鸿蒙应用日志管理提供可靠方案。

m0_62930060的博客 252

ARM Cortex-A9 核心架构(演化后的“缝合怪”)--deepseek

同学,你看,无论是多么高大上的芯片,它的底层无外乎就是这四样东西在跳舞。你在写 Zynq 的 PS 端代码时,不要觉得自己只是在敲 C 语言。你要想象你是一个坐在指挥室里的将军你敲下,就相当于打开了总机的门铃;你敲下主循环,就是让千万个 RISC 指令在流水线上狂奔。了解了这些,当你在 Vitis 调试时,发现程序莫名其妙跑到一个完全不认识的地址(通常是 HardFault),你就不会觉得见鬼了。你会知道:“哎呀,肯定是堆栈溢出,或者中断没配置好,导致流水线拿错了指令地址。

AlibabaApple的博客 383

手机该下哪个APK?ARM 与 x86 区别

本文通俗解析安卓APK中ARM与x86架构的区别:ARM(如arm64-v8a)主用于手机、平板,强调低功耗续航;x86多见于旧款Intel平板及模拟器,侧重高性能。普通用户应根据设备选择对应架构安装包——绝大多数安卓设备选arm64-v8a版,避免安装失败或闪退。通过CPU-Z等工具可快速识别设备架构,匹配正确APK即可顺畅使用。

小猫三两只。 415

ARM核心核心运行机制--deepseek

同学,你现在回头看看你列出的这 5 点。RISC 和流水线,保证了 ARM 的绝对速度。MMU 和 Cache,保证了 ARM 能跑庞大的操作系统,解决了速度与容量的矛盾。异常与中断,让 ARM 拥有了应对突发事件的敏捷能力。SCU,让 ARM 迈入了多核并行的时代,算力翻倍。这就是为什么 Zynq 里的这颗 Cortex-A9 虽然是个“缝合怪”,却能成为工业界霸主的原因。它把所有的机制缝合得极其精妙,既兼顾了低功耗,又拥有了极高的算力。作为嵌入式开发工程师,以后你写代码时,脑子里不应该只是for。

AlibabaApple的博客 226
上一篇: 嵌入式存储结构详解:text、data、bss、heap、stack 到底是什么?
下一篇: Windows + Ubuntu 双系统扩容实践:从 Windows 划出 300GB,将 /home 迁移到独立 ext4 分区
JSF不秃
博客等级 码龄6年 1粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JSF不秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值