目录
[1 核心函数介绍](#1 核心函数介绍)
[1.1 核心函数](#1.1 核心函数)
[1.2 栈的核心概念](#1.2 栈的核心概念)
[1.3 栈与其他数据结构的对比](#1.3 栈与其他数据结构的对比)
[2 函数详解与使用示例](#2 函数详解与使用示例)
[2.1 初始化栈](#2.1 初始化栈)
[2.2 压入数据:k_stack_push()](#2.2 压入数据:k_stack_push())
[2.3 弹出数据:k_stack_pop()](#2.3 弹出数据:k_stack_pop())
[2.4 完整应用示例](#2.4 完整应用示例)
[2.5 高级特性与内存池初始化](#2.5 高级特性与内存池初始化)
[3 应用与配置](#3 应用与配置)
[3.1 Kconfig 配置](#3.1 Kconfig 配置)
[3.2 性能优化技巧](#3.2 性能优化技巧)
[3.3 错误处理模式](#3.3 错误处理模式)
[3.4 栈的典型应用场景](#3.4 栈的典型应用场景)
[4 核心点总结](#4 核心点总结)
概述
在 Zephyr RTOS 中,k_stack 是栈(LIFO - 后进先出) 数据结构的内核实现。它允许多个线程向一个共享的栈空间压入和弹出数据项,是 Zephyr 中基本的 内核对象 之一。
1 核心函数介绍
1.1 核心函数
| 函数 | 功能描述 | 主要用途 |
|---|---|---|
k_stack_init() |
动态初始化栈 | 运行时初始化栈对象 |
k_stack_alloc_init() |
从内存池初始化栈 | 动态分配栈存储空间 |
k_stack_push() |
向栈中压入数据 | 生产者添加数据项 |
k_stack_pop() |
从栈中弹出数据 | 消费者取出数据项 |
k_stack_cleanup() |
清理栈资源 | 释放动态分配的栈 |
1.2 栈的核心概念
k_stack 管理一个固定大小的 数据项数组 (每个数据项是 stack_data_t 类型,实际上是 uint32_t)。它遵循经典的 后进先出 原则:
cpp
push 56 push 78 pop -> 78
┌───┐ ┌───┐ ┌───┐
│ │ │ 78│ │ │
├───┤ ├───┤ ├───┤
│ 12│ ===> │ 56│ ===> │ 56│
├───┤ ├───┤ ├───┤
栈底 ───► │ 34│ │ 34│ │ 34│
└───┘ └───┘ └───┘
初始状态 压入56,78 弹出顶部
1.3 栈与其他数据结构的对比
| 特性 | 栈 (k_stack) | 消息队列 (k_msgq) | 管道 (k_pipe) |
|---|---|---|---|
| 数据顺序 | 后进先出 (LIFO) | 先进先出 (FIFO) | 先进先出 (FIFO) |
| 数据单元 | 32位字 | 任意大小消息 | 字节流 |
| 阻塞行为 | 空时弹出阻塞,满时压入失败 | 空/满时可阻塞 | 空/满时可阻塞 |
| 典型用途 | 撤销操作、函数调用跟踪、临时存储 | 任务间通信、事件传递 | 流数据、串口通信 |
| 内存效率 | 高(固定大小项) | 中等(有消息边界开销) | 高(纯字节缓冲区) |
2 函数详解与使用示例
2.1 初始化栈
Zephyr 提供两种初始化栈的方式:
1) 静态初始化(推荐)
cpp
#include <zephyr/kernel.h>
/* 定义栈:最多存储10个32位数据项 */
#define STACK_SIZE 10
K_STACK_DEFINE(my_stack, STACK_SIZE);
void example_static_init(void) {
printk("栈已静态初始化,容量:%d\n", STACK_SIZE);
}
2) 动态初始化
cpp
/* 动态初始化示例 */
void example_dynamic_init(void) {
stack_data_t stack_buffer[STACK_SIZE];
struct k_stack my_dyn_stack;
/* 动态初始化,提供已有的缓冲区 */
k_stack_init(&my_dyn_stack, stack_buffer, STACK_SIZE);
printk("栈已动态初始化\n");
}
2.2 压入数据:k_stack_push()
1) 函数原型
cpp
int k_stack_push(struct k_stack *stack, stack_data_t data);
参数:
-
stack:栈对象指针 -
data:要压入的32位数据
返回值:
-
0:成功 -
-ENOMEM:栈已满
2) 示例:生产者线程
cpp
/* 生产者:向栈压入数据 */
void producer_thread(void *arg1, void *arg2, void *arg3) {
struct k_stack *stack = (struct k_stack *)arg1;
uint32_t counter = 0;
int ret;
while (1) {
/* 准备数据 */
uint32_t data = counter++ | 0xABCD0000;
/* 尝试压入栈 */
ret = k_stack_push(stack, data);
if (ret == 0) {
printk("[P] 压入: 0x%08x\n", data);
} else {
printk("[P] 栈满! 等待...\n");
k_sleep(K_MSEC(100));
}
k_sleep(K_MSEC(50));
}
}
2.3 弹出数据:k_stack_pop()
1) 函数原型
cpp
int k_stack_pop(struct k_stack *stack, stack_data_t *data, k_timeout_t timeout);
参数:
-
stack:栈对象指针 -
data:指向存储弹出数据的指针 -
timeout:等待超时(栈空时可能阻塞)
返回值:
-
0:成功弹出数据 -
-EAGAIN:栈空且超时
2) 示例:消费者线程
cpp
/* 消费者:从栈弹出数据 */
void consumer_thread(void *arg1, void *arg2, void *arg3) {
struct k_stack *stack = (struct k_stack *)arg1;
stack_data_t data;
int ret;
while (1) {
/* 从栈弹出数据(最多等待100ms) */
ret = k_stack_pop(stack, &data, K_MSEC(100));
if (ret == 0) {
printk("[C] 弹出: 0x%08x\n", data);
/* 处理数据 */
process_data(data);
} else if (ret == -EAGAIN) {
printk("[C] 栈空,等待数据...\n");
}
}
}
/* 数据处理函数 */
void process_data(stack_data_t data) {
uint32_t id = data & 0xFFFF;
uint32_t prefix = (data >> 16) & 0xFFFF;
if (prefix == 0xABCD) {
printk(" 处理有效数据: ID=%u\n", id);
}
}
2.4 完整应用示例
cpp
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
/* 1. 定义栈 */
#define MAX_STACK_ITEMS 8
K_STACK_DEFINE(data_stack, MAX_STACK_ITEMS);
/* 2. 生产者线程 */
void producer(void *stack_ptr, void *unused2, void *unused3) {
struct k_stack *stack = (struct k_stack *)stack_ptr;
uint32_t item_count = 0;
while (1) {
/* 生成数据 */
uint32_t data = (k_cycle_get_32() & 0xFFFF) | (item_count << 16);
/* 尝试压入栈 */
if (k_stack_push(stack, data) == 0) {
item_count++;
printk("Producer: 压入 item %u (0x%08x)\n", item_count, data);
} else {
printk("Producer: 栈满,等待...\n");
}
/* 监控栈状态 */
#if CONFIG_STACK_RUNTIME_STATS
struct k_stack_stats stats;
k_stack_stats_get(stack, &stats);
printk(" 栈使用: %zu/%d\n", stats.used, MAX_STACK_ITEMS);
#endif
k_sleep(K_MSEC(200));
}
}
/* 3. 消费者线程 */
void consumer(void *stack_ptr, void *unused2, void *unused3) {
struct k_stack *stack = (struct k_stack *)stack_ptr;
uint32_t items_processed = 0;
k_sleep(K_MSEC(100)); /* 让生产者先启动 */
while (1) {
stack_data_t data;
/* 弹出数据(永久等待) */
if (k_stack_pop(stack, &data, K_FOREVER) == 0) {
items_processed++;
/* 解析数据 */
uint32_t value = data & 0xFFFF;
uint32_t seq = (data >> 16) & 0xFFFF;
printk("Consumer: 弹出 seq=%u, value=%u (总计处理: %u)\n",
seq, value, items_processed);
/* 模拟处理时间 */
k_busy_wait(50000); /* 50us */
}
/* 每处理几个项目后稍作休息 */
if (items_processed % 5 == 0) {
k_sleep(K_MSEC(10));
}
}
}
/* 4. 主函数 */
int main(void) {
printk("=== 栈示例开始 ===\n");
printk("栈容量: %d 个32位数据项\n", MAX_STACK_ITEMS);
/* 创建生产者和消费者线程 */
k_thread_create(&producer_thread, producer_stack,
K_THREAD_STACK_SIZEOF(producer_stack),
producer, &data_stack, NULL, NULL,
5, 0, K_NO_WAIT);
k_thread_create(&consumer_thread, consumer_stack,
K_THREAD_STACK_SIZEOF(consumer_stack),
consumer, &data_stack, NULL, NULL,
6, 0, K_NO_WAIT);
k_sleep(K_FOREVER);
return 0;
}
2.5 高级特性与内存池初始化
1) 使用内存池动态分配栈
cpp
#include <zephyr/kernel.h>
#include <zephyr/sys/mempool.h>
/* 定义内存池 */
K_MEM_POOL_DEFINE(stack_pool, 64, 512, 4, 4);
void dynamic_stack_example(void) {
struct k_stack *dyn_stack;
int ret;
/* 从内存池分配栈对象 */
ret = k_mem_pool_alloc(&stack_pool, (struct k_mem_block *)&dyn_stack,
sizeof(struct k_stack) + (MAX_ITEMS * sizeof(stack_data_t)),
K_MSEC(100));
if (ret == 0) {
/* 初始化动态分配的栈 */
stack_data_t *buffer = (stack_data_t *)(dyn_stack + 1);
k_stack_alloc_init(dyn_stack, MAX_ITEMS);
/* 使用栈... */
k_stack_push(dyn_stack, 0x12345678);
/* 清理 */
k_stack_cleanup(dyn_stack);
k_mem_pool_free(&stack_pool, dyn_stack);
}
}
2) 多线程竞争场景
cpp
/* 多个生产者,一个消费者 */
void multiple_producers(void) {
#define NUM_PRODUCERS 3
for (int i = 0; i < NUM_PRODUCERS; i++) {
k_thread_create(&producer_tid[i], producer_stacks[i],
K_THREAD_STACK_SIZEOF(producer_stacks[i]),
producer_func, &shared_stack, (void *)(long)i, NULL,
7, 0, K_NO_WAIT);
}
/* 栈本身是线程安全的,无需额外同步 */
}
/* 生产者函数(带生产者ID) */
void producer_func(void *stack_ptr, void *id_ptr, void *unused3) {
struct k_stack *stack = (struct k_stack *)stack_ptr;
int producer_id = (int)(long)id_ptr;
while (1) {
uint32_t data = (producer_id << 24) | (k_cycle_get_32() & 0xFFFFFF);
if (k_stack_push(stack, data) == 0) {
printk("P%d: 压入 0x%08x\n", producer_id, data);
}
k_sleep(K_MSEC(100 * (producer_id + 1)));
}
}
3 应用与配置
3.1 Kconfig 配置
cpp
# 启用栈支持
CONFIG_STACKS=y
# 启用运行时统计(调试用)
CONFIG_STACK_RUNTIME_STATS=y
# 栈的最大数量
CONFIG_MAX_STACK_COUNT=20
# 栈的默认大小(如使用k_stack_alloc_init)
CONFIG_STACK_DEFAULT_SIZE=16
3.2 性能优化技巧
cpp
/* 技巧1:批量操作减少上下文切换 */
void batch_push(struct k_stack *stack, uint32_t *data, size_t count) {
for (size_t i = 0; i < count; i++) {
if (k_stack_push(stack, data[i]) != 0) {
/* 处理栈满情况 */
k_sleep(K_MSEC(1));
i--; /* 重试当前项 */
}
}
}
/* 技巧2:非阻塞弹出尝试 */
int try_pop(struct k_stack *stack, uint32_t *data) {
return k_stack_pop(stack, data, K_NO_WAIT);
}
/* 技巧3:合理的栈深度设置 */
#define CALC_STACK_DEPTH(avg_push_rate, avg_pop_rate, max_latency_ms) \
((avg_push_rate - avg_pop_rate) * max_latency_ms / 1000 + 1)
/* 示例:每秒压入100次,弹出80次,最大延迟100ms */
#define OPTIMAL_DEPTH CALC_STACK_DEPTH(100, 80, 100) /* = 3 */
3.3 错误处理模式
cpp
/* 安全的栈操作包装函数 */
int safe_stack_push(struct k_stack *stack, uint32_t data, int max_retries) {
int retries = 0;
while (retries < max_retries) {
if (k_stack_push(stack, data) == 0) {
return 0; /* 成功 */
}
retries++;
/* 指数退避策略 */
k_sleep(K_MSEC(10 * (1 << (retries - 1))));
if (retries % 5 == 0) {
printk("栈满警告: 已重试 %d 次\n", retries);
}
}
return -ENOMEM; /* 失败 */
}
/* 带超时的弹出 */
int timed_stack_pop(struct k_stack *stack, uint32_t *data,
k_timeout_t base_timeout, int max_attempts) {
int attempts = 0;
k_timeout_t timeout = base_timeout;
while (attempts < max_attempts) {
int ret = k_stack_pop(stack, data, timeout);
if (ret == 0) {
return 0; /* 成功 */
}
attempts++;
/* 每次增加等待时间 */
timeout = K_MSEC(k_timeout_to_ms(timeout) * 2);
printk("弹出尝试 %d 失败,等待 %lld ms\n",
attempts, k_timeout_to_ms(timeout));
}
return -EAGAIN; /* 多次尝试后失败 */
}
3.4 栈的典型应用场景
1) 撤销操作栈
cpp
#define MAX_UNDO_LEVELS 10
K_STACK_DEFINE(undo_stack, MAX_UNDO_LEVELS);
void execute_command(uint32_t command) {
/* 保存当前状态到撤销栈 */
if (k_stack_push(&undo_stack, save_current_state()) == 0) {
/* 执行命令 */
run_command(command);
}
}
void undo_last_command(void) {
uint32_t previous_state;
if (k_stack_pop(&undo_stack, &previous_state, K_NO_WAIT) == 0) {
restore_state(previous_state);
} else {
printk("没有可撤销的操作\n");
}
}
2) 递归算法转迭代
cpp
/* 使用栈实现深度优先搜索 */
void depth_first_search(struct node *start) {
K_STACK_DEFINE(node_stack, 100);
k_stack_push(&node_stack, (stack_data_t)start);
while (1) {
struct node *current;
if (k_stack_pop(&node_stack, (stack_data_t *)¤t, K_NO_WAIT) != 0) {
break; /* 栈空,搜索完成 */
}
process_node(current);
/* 将子节点压栈(后进先出保证深度优先) */
for (struct node *child = current->first_child;
child != NULL;
child = child->next_sibling) {
k_stack_push(&node_stack, (stack_data_t)child);
}
}
}
4 核心点总结
1) Zephyr 的 k_stack 提供了一种简单高效的 LIFO 数据结构,适用于:
撤销/重做功能:保存操作历史
深度优先遍历:替代递归调用
临时数据缓冲:需要后进先出顺序时
线程间简单通信:轻量级数据传递
2) 关键要点:
栈存储的是 32位数据项,不是任意类型数据
操作是 线程安全 的,可被多个线程并发访问
没有内置的溢出保护,需应用程序自己管理栈深度
与消息队列相比更轻量,适合小数据项传输