寫在前面
環形緩衝區是嵌入式應用(醫療電子、消費電子、工控)中常用的數據結構模型,如音視頻流、通信總線數據收發等。一些操作系統、第三方庫都提供現成的環形緩衝區接口API,性能和安全性都有保障,多線程訪問互斥機制,空內存訪問保護等。
1.基本原則
先進先出(FIFO),重複使用。
2.實現原理
環形緩衝區,顧名思義就是一段循環使用的一段內存。通過寫指針向“空白內存”(未寫入過或者已經被讀出的內存)寫入數據並記錄,讀指針從已寫的內存讀取數據並記錄,當指針訪問到內存最後一個位置時,再折回第一個內存位置,達到循環效果。
3.注意事項
1)環形緩衝區本質是“生產者消費者”模型,必須先有生產(寫)後有消費(讀),不可提前消費(讀);
2)注意程序效率問題,如果讀效率過低,導致“生產”過剩,從而覆蓋未讀出的數據,導致出錯,此時需增加環形緩衝區內存大小或者優化代碼效率;
3)單一線程訪問時是安全的,多線程訪問時,爲保證數據安全性必須加互斥鎖機制;
4.應用場合
處理串口(RS232)接收數據命令:
串口中斷--->數據接收--->寫入環形緩衝區--->數據處理線程讀取環形緩衝區--->處理有效數據。
5.實現方式
頭文件
#ifndef _FIFO_H_
#define _FIFO_H_
#include <stdbool.h>
#include <stdint.h>
typedef struct
{
uint8_t *buf; /* 緩衝區 */
uint32_t buf_size; /* 緩衝區大小 */
uint8_t *pwrite; /* 寫指針 */
uint8_t *pread; /* 讀指針 */
uint8_t write_cover; /* 讀覆蓋標識 */
uint8_t read_cover; /* 寫覆蓋標識 */
void (*lock)(void); /* 互斥上鎖 */
void (*unlock)(void); /* 互斥解鎖 */
}_fifo_t;
extern void fifo_register(_fifo_t *pfifo, uint8_t *pfifo_buf, uint32_t size);
extern void fifo_release(_fifo_t *pfifo);
extern uint32_t fifo_write(_fifo_t *pfifo, const uint8_t *pbuf, uint32_t size);
extern uint32_t fifo_read(_fifo_t *pfifo, uint8_t *pbuf, uint32_t size);
extern uint32_t fifo_get_total_size(_fifo_t *pfifo);
extern uint32_t fifo_get_free_size(_fifo_t *pfifo);
extern uint32_t fifo_get_occupy_size(_fifo_t *pfifo);
#endif
源文件
#include <stddef.h>
#include "fifo.h"
/**
* @brief 註冊一個fifo
* @param pfifo: fifo結構體指針
pfifo_buf: fifo內存塊
size: 長度
* @retval none
*/
void fifo_register(_fifo_t *pfifo, uint8_t *pfifo_buf, uint32_t size)
{
pfifo->buf_size = size;
pfifo->buf = pfifo_buf;
pfifo->pwrite = pfifo->buf;
pfifo->pread = pfifo->buf;
pfifo->write_cover = 0;
pfifo->read_cover = 0;
}
/**
* @brief 釋放fifo
* @param pfifo: fifo結構體指針
* @retval none
*/
void fifo_release(_fifo_t *pfifo)
{
pfifo->buf_size = 0;
pfifo->buf = NULL;
pfifo->pwrite = 0;
pfifo->pread = 0;
pfifo->write_cover = 0;
pfifo->read_cover = 0;
}
/**
* @brief 往fifo寫數據
* @param pfifo: fifo結構體指針
pbuf: 待寫數據
size: 待寫數據大小
* @retval 實際寫大小
*/
uint32_t fifo_write(_fifo_t *pfifo, const uint8_t *pbuf, uint32_t size)
{
uint32_t w_size= 0,free_size = 0;
if ((size==0) || (pfifo==NULL) || (pbuf==NULL))
{
return 0;
}
free_size = fifo_get_free_size(pfifo);
if(free_size == 0)
{
return 0;
}
if(free_size < size)
{
size = free_size;
}
w_size = size;
while(w_size-- > 0)
{
*pfifo->pwrite++ = *pbuf++;
if (pfifo->pwrite >= &(pfifo->buf[pfifo->buf_size]))
{
pfifo->pwrite = pfifo->buf;
pfifo->write_cover = ~pfifo->write_cover;
}
}
return size;
}
/**
* @brief 從fifo讀數據
* @param pfifo: fifo結構體指針
pbuf: 待讀數據緩存
size: 待讀數據大小
* @retval 實際讀大小
*/
uint32_t fifo_read(_fifo_t *pfifo, uint8_t *pbuf, uint32_t size)
{
uint32_t r_size = 0,occupy_size = 0;
if ((size==0) || (pfifo==NULL) || (pbuf==NULL))
{
return 0;
}
occupy_size = fifo_get_occupy_size(pfifo);
if(occupy_size == 0)
{
return 0;
}
if(occupy_size < size)
{
size = occupy_size;
}
r_size = size;
while(r_size-- > 0)
{
*pbuf++ = *pfifo->pread++;
if (pfifo->pread >= &(pfifo->buf[pfifo->buf_size]))
{
pfifo->pread = pfifo->buf;
pfifo->read_cover = ~pfifo->read_cover;
}
}
return size;
}
/**
* @brief 獲取fifo空間大小
* @param pfifo: fifo結構體指針
* @retval fifo大小
*/
uint32_t fifo_get_total_size(_fifo_t *pfifo)
{
if (pfifo==NULL)
return 0;
return pfifo->buf_size;
}
/**
* @brief 獲取fifo空閒空間大小
* @param pfifo: fifo結構體指針
* @retval 空閒空間大小
*/
uint32_t fifo_get_free_size(_fifo_t *pfifo)
{
uint32_t size;
if (pfifo==NULL)
return 0;
if (pfifo->pwrite == pfifo->pread)
{
if(pfifo->write_cover == pfifo->read_cover)
size = pfifo->buf_size;
else
size = 0;
}
else
{
if (pfifo->pwrite > pfifo->pread)
size = (uint32_t)&(pfifo->buf[pfifo->buf_size]) - (uint32_t)&(pfifo->buf[0]) - (uint32_t)(pfifo->pwrite) + (uint32_t)(pfifo->pread);
else
size = pfifo->pread - pfifo->pwrite;
}
return size;
}
/**
* @brief 獲取fifo已用空間大小
* @param pfifo: fifo結構體指針
* @retval fifo已用大小
*/
uint32_t fifo_get_occupy_size(_fifo_t *pfifo)
{
uint32_t size;
if (pfifo==NULL)
return 0;
size = pfifo->buf_size - fifo_get_free_size(pfifo);
return size;
}
其中,注意點是環形緩衝區“滿”與“空”狀態處理,這裏使用了讀、寫指針的“翻轉”標識,即當讀、寫指針訪問到緩衝區最後地址時,訪問地址折回初始地址,此時做一個翻轉標識;當讀地址和寫地址相等時,如果讀、寫翻轉標識相等,說明此時緩衝區爲“空狀態”,否則緩衝區爲“滿”狀態。
5.應用例子
註冊一個512字節大小環形緩衝區,然後往緩衝區寫入數據,接着讀出來。
#include "fifo.h"
static uint8_t buf_0[512];
static _fifo_t fifo_0;
int main(void)
{
uint8_t w_data[] = {0x01,0x02,0x03};
uint8_t r_data[10];
fifo_register(&fifo_0, buf_0, sizeof(buf_0));
fifo_write(&fifo_0, (const uint8_t*)w_data, sizeof(data));
fifo_read(&fifo_0, r_data, 1);
fifo_read(&fifo_0, &r_data[1], 2);
return 0;
}