Zephyr OS 中的 FIFO 接口应用介绍
目录
概述
1 FIFO的接口函数
1.1 K_FIFO_DEFINE函数
1.2 k_fifo_init函数
1.3 k_fifo_put函数
1.4 k_fifo_get 函数
1.5 k_fifo_is_empty 函数
2 应用验证
2.1 UART中使用FIFO范例
2.2 生产-消费类型范例
3 注意事项
3.1 内存管理
3.2 线程安全边界
概述
Zephyr RTOS 提供了多种 FIFO (First-In-First-Out) 实现方式,适用于不同场景的数据缓冲需求。以下是主要的 FIFO 接口和使用方法。
1 FIFO的接口函数
FIFO 接口主要API如下这些,其具体函数如下:
函数 | 描述 |
---|---|
K_FIFO_DEFINE(name) | 定义并初始化FIFO |
k_fifo_init(fifo) | 运行时初始化FIFO |
k_fifo_put(fifo, data) | 向FIFO放入数据 |
k_fifo_get(fifo, timeout) | 从FIFO获取数据 |
k_fifo_is_empty(fifo) | 检查FIFO是否为空 |
1.1 K_FIFO_DEFINE函数
K_FIFO_DEFINE
是 Zephyr RTOS 中用于定义和初始化 FIFO(先进先出队列)的宏,它是 Zephyr 内核提供的一种线程安全的数据传递机制。
1) 基本语法
K_FIFO_DEFINE(name)
2)功能说明
作用:
静态定义并初始化一个 FIFO 队列
创建的 FIFO 可以被多个线程安全地访问
支持阻塞和非阻塞操作
特性:
线程安全:内置同步机制,无需额外锁
动态扩展:基于链表实现,理论上无大小限制
支持超时等待:消费者可以阻塞等待数据
3) 内部实现原理
K_FIFO_DEFINE
实际上创建了一个struct k_fifo
结构体,其核心实现基于:
链表结构:使用 Zephyr 的
sys_slist_t
单链表同步机制:内置内核信号量保证线程安全
等待队列:当 FIFO 为空时,允许消费者线程阻塞等待
1.2 k_fifo_init函数
k_fifo_init
是 Zephyr RTOS 中用于运行时初始化 FIFO(先进先出队列)的函数,与静态初始化宏 K_FIFO_DEFINE
相对应,提供了动态初始化 FIFO 的能力。
1)函数原型
void k_fifo_init(struct k_fifo *fifo);
2)参数说明
参数说明
参数 | 类型 | 描述 |
---|---|---|
fifo | struct k_fifo* | 指向要初始化的FIFO结构体的指针 |
3) 核心功能
初始化FIFO内部状态:
初始化链表头(用于存储数据项)
初始化等待队列(用于阻塞的消费者线程)
重置所有内部状态标志
线程安全保证:
初始化后的FIFO可安全用于多线程环境
内置同步机制,无需额外锁
3)使用范例
1. 动态创建FIFO
#include <zephyr/kernel.h>struct k_fifo my_fifo;void init_my_fifo(void) {k_fifo_init(&my_fifo); }
2. 模块化设计中的FIFO初始化
// 模块头文件 struct data_processor {void *fifo_reserved;// 其他成员... };// 模块实现 int data_processor_init(struct data_processor *proc) {if (proc == NULL) {return -EINVAL;}k_fifo_init(&proc->input_fifo);// 其他初始化...return 0; }
4) 与K_FIFO_DEFINE对比
特性 | k_fifo_init | K_FIFO_DEFINE |
---|---|---|
初始化方式 | 运行时动态初始化 | 编译时静态初始化 |
存储位置 | 需自行管理内存 | 自动分配在全局数据区 |
使用场景 | 动态创建的对象需要FIFO时 | 全局或模块级FIFO |
线程安全 | 是 | 是 |
初始化时机 | 显式调用时 | 系统启动时自动初始化 |
1.3 k_fifo_put函数
k_fifo_put
是 Zephyr RTOS 中用于向 FIFO (先进先出队列) 放入数据项的核心函数,它实现了线程安全的数据生产者-消费者模式。
1)函数原型
void k_fifo_put(struct k_fifo *fifo, void *data);
2)参数说明
参数 | 类型 | 描述 |
---|---|---|
fifo | struct k_fifo* | 指向已初始化的FIFO对象的指针 |
data | void* | 要放入队列的数据项指针 |
3)核心功能与特性
线程安全操作:
内部使用内核锁保证多线程/中断环境下的安全访问
支持多生产者并发写入
唤醒机制:
如果有线程在
k_fifo_get
上阻塞等待数据,会自动唤醒最早等待的线程唤醒的线程将获得刚放入的数据项
无阻塞设计:
函数立即返回,不会阻塞调用线程
适合在中断上下文使用(需配合
K_NO_WAIT
内存分配)数据所有权转移:
数据项所有权从生产者转移到FIFO,再由消费者获取
放入后生产者不应再访问该数据项
4)使用示例
-1) 基础使用模式
#include <zephyr/kernel.h>// 定义数据结构 struct sensor_data {void *fifo_reserved;int32_t temperature;uint32_t timestamp; };K_FIFO_DEFINE(sensor_fifo);void sampling_thread(void) {while (1) {// 动态分配数据项struct sensor_data *data = k_malloc(sizeof(*data));// 填充数据data->temperature = read_temperature();data->timestamp = k_uptime_get_32();// 放入FIFOk_fifo_put(&sensor_fifo, data);k_sleep(K_MSEC(1000));} }
-2) 中断上下文使用
void isr_handler(const struct device *dev, void *user_data) {static struct event_data *evt;if (!evt) {// 预分配避免ISR中动态分配evt = k_malloc(sizeof(*evt));}if (evt) {evt->event_type = DEVICE_EVENT;evt->event_code = read_event_code();// ISR中只能使用非阻塞操作if (k_fifo_put(&event_fifo, evt) == 0) {evt = NULL; // 成功放入后重置指针}} }
5) 与相关函数对比
函数 | 特点 | 适用场景 |
---|---|---|
k_fifo_put | 单数据项放入 | 通用场景 |
k_fifo_put_list | 批量放入多个数据项 | 批量生产场景 |
k_fifo_put_slist | 使用系统单链表批量放入 | 已有链表结构的场景 |
k_queue_alloc_append | 带内存分配的放入 | 需要自动分配内存的场景 |
1.4 k_fifo_get
函数
k_fifo_get
是 Zephyr RTOS 中用于从 FIFO (先进先出队列) 获取数据项的核心函数,它实现了线程安全的消费者接口,支持阻塞和非阻塞两种模式。
1)函数原型
void *k_fifo_get(struct k_fifo *fifo, k_timeout_t timeout);
2)参数说明
参数 | 类型 | 描述 |
---|---|---|
fifo | struct k_fifo* | 指向已初始化的FIFO对象的指针 |
timeout | k_timeout_t | 指定等待超时时间,可以是:K_NO_WAIT (非阻塞)K_FOREVER (永久阻塞)具体时间值 |
3)返回值
成功时:返回获取到的数据项指针
超时或失败:返回
NULL
4)核心功能和特性
线程安全操作:
内部使用内核锁保证多线程环境下的安全访问
支持多消费者并发获取
灵活的等待策略:
非阻塞模式(
K_NO_WAIT
):立即返回,不等待阻塞模式:可以指定超时时间或无限等待(
K_FOREVER
)定时等待:如
K_MSEC(100)
表示最多等待100毫秒优先级继承:
当多个线程等待同一个FIFO时,高优先级线程会优先被唤醒
内存所有权转移:
获取到的数据项所有权从FIFO转移到消费者
消费者负责后续的内存管理(通常需要释放)
5)使用范例
-1)基础使用模式
#include <zephyr/kernel.h>K_FIFO_DEFINE(data_fifo);void consumer_thread(void) {while (1) {// 阻塞等待数据,最多等待200msstruct sensor_data *data = k_fifo_get(&data_fifo, K_MSEC(200));if (data != NULL) {// 处理数据process_data(data);// 释放内存k_free(data);} else {// 超时处理handle_timeout();}} }
-2)多消费者协作模式
void consumer_group(void) {struct k_fifo *fifo = &shared_fifo;while (1) {void *data = k_fifo_get(fifo, K_FOREVER);// 根据数据类型分发处理if (is_type_a(data)) {process_type_a(data);} else if (is_type_b(data)) {process_type_b(data);}k_free(data);} }
-3)检查返回值
// 错误:未检查返回值 struct data *item = k_fifo_get(&fifo, K_MSEC(100)); item->value = 0; // 可能解引用NULL// 正确:必须检查返回值 struct data *item = k_fifo_get(&fifo, K_MSEC(100)); if (item != NULL) {// 安全访问process(item);k_free(item); }
-4)内存管理责任
void *data = k_fifo_get(&fifo, K_FOREVER); process(data); k_free(data); // 必须释放
1.5 k_fifo_is_empty
函数
k_fifo_is_empty
是 Zephyr RTOS 中用于检查 FIFO (先进先出队列) 是否为空的辅助函数,它提供了一种非破坏性的方式来查询 FIFO 的当前状态。
1)函数原型
bool k_fifo_is_empty(struct k_fifo *fifo);
2)参数说明
参数 | 类型 | 描述 |
---|---|---|
fifo | struct k_fifo* | 指向已初始化的FIFO对象的指针 |
3) 返回值
true
:FIFO 为空(没有数据项)
false
:FIFO 不为空(至少有一个数据项)
4)核心功能与特性
非破坏性检查:
仅查询状态,不会修改FIFO内容
不会影响任何等待线程
线程安全:
内部使用内核锁保证多线程环境下的安全访问
可以在任何上下文中调用(包括中断)
轻量级操作:
比
k_fifo_get
更轻量,适合状态检查无阻塞,立即返回结果
5)使用范例
-1)基本使用模式
#include <zephyr/kernel.h>K_FIFO_DEFINE(data_fifo);void consumer_thread(void) {// 先检查是否有数据if (!k_fifo_is_empty(&data_fifo)) {struct data_item *item = k_fifo_get(&data_fifo, K_NO_WAIT);process_item(item);k_free(item);} else {printk("No data available\n");} }
-2) 中断上下文使用
void isr_handler(const struct device *dev, void *user_data) {// 在ISR中安全检查FIFO状态if (!k_fifo_is_empty(&isr_fifo)) {// 可以安全地从ISR中获取数据(非阻塞)struct isr_event *evt = k_fifo_get(&isr_fifo, K_NO_WAIT);handle_isr_event(evt);} }
-3) 多线程协调
void worker_thread(void) {while (1) {// 先非阻塞检查if (k_fifo_is_empty(&work_queue)) {// 无工作时休眠k_sleep(K_MSEC(100));continue;}// 有工作则获取处理struct work_item *item = k_fifo_get(&work_queue, K_NO_WAIT);process_work_item(item);k_free(item);} }
5) 关键注意事项
-1) 竞态条件:
// 错误用法:检查和使用之间存在时间间隙 if (!k_fifo_is_empty(&fifo)) {// 在这期间可能有其他线程取走数据item = k_fifo_get(&fifo, K_NO_WAIT); // 可能得到NULL }// 正确用法:直接使用k_fifo_get的超时机制 item = k_fifo_get(&fifo, K_NO_WAIT); if (item != NULL) {// 安全处理 }
-2) 性能考虑:不应作为主要的数据获取机制
// 优化:避免不必要的阻塞 if (!k_fifo_is_empty(&fifo)) {item = k_fifo_get(&fifo, K_NO_WAIT);// 处理item... }
-3) 与
k_fifo_get
的关系:
函数 特点 适用场景 k_fifo_is_empty
仅检查状态,不修改FIFO 需要预先知道状态的场景 k_fifo_get
获取数据并修改FIFO状态 实际消费数据的场景
6) 特性
k_fifo_is_empty
作为 Zephyr FIFO 机制的辅助函数,虽然简单但非常实用。它最适合用于状态监控、性能优化和资源管理场景,而不应作为数据消费的主要机制。正确使用时可以构建出更高效和响应性更好的系统,但需要注意避免常见的竞态条件陷阱。
2 应用验证
2.1 UART中使用FIFO范例
Step-1: 定义FIFO
/* UART payload buffer element size. */#define UART_BUF_SIZE 20struct uart_data_t {void *fifo_reserved;uint8_t data[UART_BUF_SIZE];uint16_t len;};static K_FIFO_DEFINE(fifo_uart_tx_data);static K_FIFO_DEFINE(fifo_uart_rx_data);
Step-2: 使用k_fifo_put准备数据
struct uart_data_t *tx = k_malloc(sizeof(*tx));err = uart_tx(uart, tx->data, tx->len, SYS_FOREVER_MS);if (err) {k_fifo_put(&fifo_uart_tx_data, tx);}
Step-3: 使用k_fifo_get消费数据
buf = k_fifo_get(&fifo_uart_tx_data, K_NO_WAIT);if (!buf) {return;}if (uart_tx(uart, buf->data, buf->len, SYS_FOREVER_MS)) {LOG_WRN("Failed to send data over UART");}
2.2 生产-消费类型范例
3 注意事项
3.1 内存管理
// 正确:确保数据项在消费前有效
struct data_item *item = k_malloc(sizeof(*item));
k_fifo_put(fifo, item);// 错误:栈变量在离开作用域后无效
struct data_item item;
k_fifo_put(fifo, &item); // 严重错误!
3.2 线程安全边界
-
FIFO操作本身是线程安全的
-
但数据内容的安全性需要开发者自己保证
示例安全模式:
// 生产者
struct data *item = k_malloc(sizeof(*item));
item->value = compute_value(); // 在放入FIFO前完成所有数据准备
k_fifo_put(fifo, item); // 之后不再修改item// 消费者
struct data *item = k_fifo_get(fifo, K_FOREVER);
use_value(item->value); // 安全使用数据
k_free(item);