Zephyr OS 中的 FIFO 接口应用介绍

发布于:2025-05-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

概述

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)功能说明

  1. 作用

    • 静态定义并初始化一个 FIFO 队列

    • 创建的 FIFO 可以被多个线程安全地访问

    • 支持阻塞和非阻塞操作

  2. 特性

    • 线程安全:内置同步机制,无需额外锁

    • 动态扩展:基于链表实现,理论上无大小限制

    • 支持超时等待:消费者可以阻塞等待数据

3) 内部实现原理

K_FIFO_DEFINE 实际上创建了一个 struct k_fifo 结构体,其核心实现基于:

  1. 链表结构:使用 Zephyr 的 sys_slist_t 单链表

  2. 同步机制:内置内核信号量保证线程安全

  3. 等待队列:当 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) 核心功能

  1. 初始化FIFO内部状态

    • 初始化链表头(用于存储数据项)

    • 初始化等待队列(用于阻塞的消费者线程)

    • 重置所有内部状态标志

  2. 线程安全保证

    • 初始化后的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)核心功能与特性

  1. 线程安全操作

    • 内部使用内核锁保证多线程/中断环境下的安全访问

    • 支持多生产者并发写入

  2. 唤醒机制

    • 如果有线程在 k_fifo_get 上阻塞等待数据,会自动唤醒最早等待的线程

    • 唤醒的线程将获得刚放入的数据项

  3. 无阻塞设计

    • 函数立即返回,不会阻塞调用线程

    • 适合在中断上下文使用(需配合 K_NO_WAIT 内存分配)

  4. 数据所有权转移

    • 数据项所有权从生产者转移到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();
        
        // 放入FIFO
        k_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)核心功能和特性

  1. 线程安全操作

    • 内部使用内核锁保证多线程环境下的安全访问

    • 支持多消费者并发获取

  2. 灵活的等待策略

    • 非阻塞模式(K_NO_WAIT):立即返回,不等待

    • 阻塞模式:可以指定超时时间或无限等待(K_FOREVER)

    • 定时等待:如 K_MSEC(100) 表示最多等待100毫秒

  3. 优先级继承

    • 当多个线程等待同一个FIFO时,高优先级线程会优先被唤醒

  4. 内存所有权转移

    • 获取到的数据项所有权从FIFO转移到消费者

    • 消费者负责后续的内存管理(通常需要释放)

 5)使用范例

-1)基础使用模式

#include <zephyr/kernel.h>

K_FIFO_DEFINE(data_fifo);

void consumer_thread(void)
{
    while (1) {
        // 阻塞等待数据,最多等待200ms
        struct 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)核心功能与特性

  1. 非破坏性检查

    • 仅查询状态,不会修改FIFO内容

    • 不会影响任何等待线程

  2. 线程安全

    • 内部使用内核锁保证多线程环境下的安全访问

    • 可以在任何上下文中调用(包括中断)

  3. 轻量级操作

    • 比 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 20

 struct 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);