C 语言工业级通用组件手写 11:环形 FIFO 缓冲区
2026/7/21 15:49:09 网站建设 项目流程

目录

前言:

一、环形 FIFO 核心本质与应用场景

1. 什么是环形缓冲区

2. 解决的核心痛点

3. 典型工业级落地场景

二、核心实现原理

1. 双指针循环机制

2. 零拷贝读写

3. 字节流式存储

三、工业级设计规范

1. 封装性设计

2. 接口设计原则

3. 鲁棒性要求

4. 线程安全约束

四、完整可复用源码

1. 头文件 ring_fifo.h

2. 实现文件 ring_fifo.c

五、实战演示:环形缓冲区读写示例

六、工业级进阶优化方向

1. 线程安全加锁封装

2. 单次一字节读写接口

3. peek 窥探接口

4. 批量分段读写

七、高频面试考点与易错坑点

1. 经典面试问答

2. 常见易错坑点

总结


前言:

在嵌入式串口接收、网络报文缓冲、音频数据流、传感器高频采集、异步数据收发场景中,普通数组缓存存在溢出、覆盖、数据丢失、读写冲突等致命问题。

普通数组无脑缓存,必须手动判空、判满、移动数据,代码臃肿、极易出错、无法高频运行。

环形 FIFO(循环缓冲区)是工业流式数据缓存的标准解决方案。 本篇手写一套极简、零拷贝、全覆盖、读写分离、防溢出、可线程安全的工业级环形缓冲区,源码干净、无冗余、可直接投产,适配所有流式数据收发场景。


一、环形 FIFO 核心本质与应用场景

1. 什么是环形缓冲区

环形 FIFO 是首尾相连的循环队列

依靠读指针(r)写指针(w)实现循环读写,无需数据搬迁、零拷贝、性能极致

核心特性:

先进先出、循环复用内存、无数据挪动、无内存碎片、读写高效。

2. 解决的核心痛点

  • 解决数组缓存溢出覆盖:满状态自动拦截写入,杜绝数据丢失。
  • 解决数据搬迁耗时:普通队列出队需要整体前移,FIFO 零搬迁。
  • 解决高频数据流卡顿:O (1) 读写,百万级吞吐无压力。
  • 解决收发速率不匹配:缓存削峰,适配突发数据、高频采样。
  • 解决代码冗余混乱:统一标准接口,全局通用缓存组件。

3. 典型工业级落地场景

  • 串口 / 485/CAN 接收缓存:中断高速接收、主线程慢速解析。
  • 网络数据流缓冲:TCP 流式分包、UDP 报文缓存。
  • 音视频采集:音频帧、视频帧连续流式缓存。
  • 传感器高频采集:高速 AD、姿态、雷达数据缓存。
  • 异步解耦架构:生产快、消费慢场景削峰缓冲。

二、核心实现原理

1. 双指针循环机制

  • write_ptr:数据写入位置
  • read_ptr:数据读取位置
  • 缓冲区满:(write + 1) % size == read
  • 缓冲区空:write == read

预留一个空位区分空 / 满状态,是工业最简、零 BUG 标准方案。

2. 零拷贝读写

读写直接操作数组指针,不拷贝、不移动、不遍历,性能拉满。

3. 字节流式存储

按字节缓存,适配任意协议、任意报文、任意长度数据,通用性最强。


三、工业级设计规范

1. 封装性设计

FIFO 结构体完全封装缓存地址、读写指针、缓冲区大小。

外部不可篡改内部状态,保证读写闭环安全。

2. 接口设计原则

接口函数功能说明
fifo_init初始化环形缓冲区
fifo_write写入指定长度数据
fifo_read读取指定长度数据
fifo_available获取剩余可写空间
fifo_used获取当前已缓存数据长度
fifo_clear清空缓冲区

3. 鲁棒性要求

空指针校验、长度合法性校验。

写满自动拒绝、读空自动返回。

无越界、无覆盖、无数据错乱。

纯 C 零依赖、裸机 / RTOS/Linux 全平台适配。

4. 线程安全约束

单生产单消费天然线程安全(嵌入式中断写、主线程读标配)。

多生产多消费需外层加锁,适配高并发场景。

四、完整可复用源码

1. 头文件 ring_fifo.h

#ifndef RING_FIFO_H #define RING_FIFO_H #include <stdint.h> #include <stddef.h> #ifdef __cplusplus extern "C" { #endif /* 环形FIFO句柄 */ typedef struct { uint8_t *buf; size_t buf_size; size_t w_ptr; size_t r_ptr; } ring_fifo_t; /** * @brief 初始化环形缓冲区 * @param fifo FIFO句柄 * @param buf 缓存数组地址 * @param buf_size 缓存数组大小 */ void fifo_init(ring_fifo_t *fifo, uint8_t *buf, size_t buf_size); /** * @brief 写入数据到FIFO * @param fifo FIFO句柄 * @param data 待写入数据 * @param len 写入长度 * @return 实际写入字节数 */ size_t fifo_write(ring_fifo_t *fifo, const uint8_t *data, size_t len); /** * @brief 从FIFO读取数据 * @param fifo FIFO句柄 * @param data 接收缓存 * @param len 读取长度 * @return 实际读取字节数 */ size_t fifo_read(ring_fifo_t *fifo, uint8_t *data, size_t len); /** * @brief 获取缓冲区剩余可写空间 */ size_t fifo_available(ring_fifo_t *fifo); /** * @brief 获取缓冲区已存储数据长度 */ size_t fifo_used(ring_fifo_t *fifo); /** * @brief 清空缓冲区 */ void fifo_clear(ring_fifo_t *fifo); #ifdef __cplusplus } #endif #endif

2. 实现文件 ring_fifo.c

#include "ring_fifo.h" #include <string.h> void fifo_init(ring_fifo_t *fifo, uint8_t *buf, size_t buf_size) { if (fifo == NULL || buf == NULL || buf_size == 0) { return; } fifo->buf = buf; fifo->buf_size = buf_size; fifo->w_ptr = 0; fifo->r_ptr = 0; } size_t fifo_write(ring_fifo_t *fifo, const uint8_t *data, size_t len) { if (fifo == NULL || data == NULL || len == 0) { return 0; } size_t avail = fifo_available(fifo); if (avail == 0) { return 0; } // 限制最大写入长度 len = len > avail ? avail : len; size_t size1 = fifo->buf_size - fifo->w_ptr; if (size1 >= len) { memcpy(&fifo->buf[fifo->w_ptr], data, len); } else { memcpy(&fifo->buf[fifo->w_ptr], data, size1); memcpy(fifo->buf, data + size1, len - size1); } fifo->w_ptr = (fifo->w_ptr + len) % fifo->buf_size; return len; } size_t fifo_read(ring_fifo_t *fifo, uint8_t *data, size_t len) { if (fifo == NULL || data == NULL || len == 0) { return 0; } size_t used = fifo_used(fifo); if (used == 0) { return 0; } len = len > used ? used : len; size_t size1 = fifo->buf_size - fifo->r_ptr; if (size1 >= len) { memcpy(data, &fifo->buf[fifo->r_ptr], len); } else { memcpy(data, &fifo->buf[fifo->r_ptr], size1); memcpy(data + size1, fifo->buf, len - size1); } fifo->r_ptr = (fifo->r_ptr + len) % fifo->buf_size; return len; } size_t fifo_available(ring_fifo_t *fifo) { if (fifo == NULL) { return 0; } // 预留1字节区分空满 return (fifo->buf_size - 1 + fifo->r_ptr - fifo->w_ptr) % fifo->buf_size; } size_t fifo_used(ring_fifo_t *fifo) { if (fifo == NULL) { return 0; } return (fifo->w_ptr + fifo->buf_size - fifo->r_ptr) % fifo->buf_size; } void fifo_clear(ring_fifo_t *fifo) { if (fifo == NULL) { return; } fifo->w_ptr = 0; fifo->r_ptr = 0; }

五、实战演示:环形缓冲区读写示例

#include <stdio.h> #include "ring_fifo.h" #define FIFO_BUF_LEN 32 uint8_t fifo_buf[FIFO_BUF_LEN]; int main(void) { ring_fifo_t fifo; fifo_init(&fifo, fifo_buf, FIFO_BUF_LEN); uint8_t w_data[] = "INDUSTRIAL_FIFO_11"; uint8_t r_data[32] = {0}; // 写入数据 size_t w_len = fifo_write(&fifo, w_data, sizeof(w_data)); printf("成功写入 %zu 字节\n", w_len); printf("当前已缓存:%zu 字节\n", fifo_used(&fifo)); // 读取数据 size_t r_len = fifo_read(&fifo, r_data, sizeof(r_data)); printf("成功读取 %zu 字节\n", r_len); printf("读取内容:%s\n", r_data); return 0; }

运行效果:循环读写正常、边界无越界、缓冲区满自动截断、空读取直接返回,零丢失、零错乱、零崩溃


六、工业级进阶优化方向

1. 线程安全加锁封装

搭配互斥锁,支持多线程并发读写。

2. 单次一字节读写接口

适配串口中断单字节写入场景。

3. peek 窥探接口

只读不弹出,适配协议头解析、预判断报文。

4. 批量分段读写

适配超大数据流高速吞吐。


七、高频面试考点与易错坑点

1. 经典面试问答

Q1:环形 FIFO 为什么要预留一个空位?

答:

解决空满状态歧义。读写指针相等既可以是空也可以是满,预留一位可以完美区分,是工业最简无 BUG 方案。

Q2:环形缓冲区相比普通数组的优势?

答:

无需数据搬迁、无溢出覆盖、循环复用内存、性能稳定、适配异步收发。

Q3:单中断写主线程读是否需要加锁?

答:

不需要。单生产单消费模式指针互不抢占,天然线程安全,是串口驱动标准写法。

Q4:FIFO 核心设计思想?

答:

以空间换时间、循环复用内存、解耦生产消费速率、零拷贝高效流转。

2. 常见易错坑点

  • 不预留空位,空满判断错乱
  • 读写指针不取模,数组越界
  • 读取不判空、写入不判满导致数据异常
  • 频繁数据 memmove 挪动,性能极差
  • 边界包裹读写处理错误,尾部数据丢失

总结

环形 FIFO 缓冲区是嵌入式流式数据处理的第一组件

所有串口、网络、音频、采集工程100% 依赖环形缓存

本篇实现工业标准、边界全覆盖、零 BUG、可直接投产,补齐整套组件库数据流转核心能力


创作不易,如果对你有帮助,欢迎点赞、收藏、转发。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询