一、前言
嵌入式開發中我們要時刻保持代碼的高效與整潔。在嵌入式的開發中緩衝器是非常常用的,比如串口的數據,MCU處理數據的時候,只能先處理先來的,那麼處理完後呢,就會把數據釋放掉,再處理下一個。已經處理的數據的內存就會被浪費掉。因爲後來的數據只能往後排隊,如果要將剩餘的數據都往前移動一次,那麼效率就會低下了,肯定不現實,所以,環形隊列就出現了。
環形緩衝區通常有一個讀指針和一個寫指針。讀指針指向環形緩衝區中可讀的數據,寫指針指向環形緩衝區中可寫的緩衝區。通過移動讀指針和寫指針就可以實現緩衝區的數據讀取和寫入。在通常情況下,環形緩衝區的讀用戶僅僅會影響讀指針,而寫用戶僅僅會影響寫指針。如果僅僅有一個讀用戶和一個寫用戶,那麼不需要添加互斥保護機制就可以保證數據的正確性。如果有多個讀寫用戶訪問環形緩衝區,那麼必須添加互斥保護機制來確保多個用戶互斥訪問環形緩衝區。
二、代碼
經典的環形緩衝器源代碼可以參考linux內核中的kernel/kfifo.c,巧奪天工的kfifo:Linux Kernel中的無鎖環形緩衝,下面代碼是移植的nordic ble SDK中的,相對比較簡單,使用方便。
#include "cola_fifo.h"
#include "utils.h"
static __inline uint32_t fifo_length(cola_fifo_t * p_fifo)
{
uint32_t tmp = p_fifo->read_pos;
return p_fifo->write_pos - tmp;
}
#define FIFO_LENGTH() fifo_length(p_fifo) /**< Macro for calculating the FIFO length. */
/**@brief Put one byte to the FIFO. */
static __inline void fifo_put(cola_fifo_t * p_fifo, uint8_t byte)
{
p_fifo->p_buf[p_fifo->write_pos & p_fifo->buf_size_mask] = byte;
p_fifo->write_pos++;
}
/**@brief Look at one byte in the FIFO. */
static __inline void fifo_peek(cola_fifo_t * p_fifo, uint16_t index, uint8_t * p_byte)
{
*p_byte = p_fifo->p_buf[(p_fifo->read_pos + index) & p_fifo->buf_size_mask];
}
/**@brief Get one byte from the FIFO. */
static __inline void fifo_get(cola_fifo_t * p_fifo, uint8_t * p_byte)
{
fifo_peek(p_fifo, 0, p_byte);
p_fifo->read_pos++;
}
uint32_t cola_fifo_init(cola_fifo_t * p_fifo, uint8_t * p_buf, uint16_t buf_size)
{
// Check buffer for null pointer.
if (p_buf == NULL)
{
return 0;
}
// Check that the buffer size is a power of two.
if (!IS_POWER_OF_TWO(buf_size))
{
return 0;
}
p_fifo->p_buf = p_buf;
p_fifo->buf_size_mask = buf_size - 1;
p_fifo->read_pos = 0;
p_fifo->write_pos = 0;
return 1;
}
uint32_t cola_fifo_put(cola_fifo_t * p_fifo, uint8_t byte)
{
if (FIFO_LENGTH() <= p_fifo->buf_size_mask)
{
fifo_put(p_fifo, byte);
return 1;
}
return 0;
}
uint32_t cola_fifo_get(cola_fifo_t * p_fifo, uint8_t * p_byte)
{
if (FIFO_LENGTH() != 0)
{
fifo_get(p_fifo, p_byte);
return 1;
}
return 0;
}
uint32_t cola_fifo_peek(cola_fifo_t * p_fifo, uint16_t index, uint8_t * p_byte)
{
if (FIFO_LENGTH() > index)
{
fifo_peek(p_fifo, index, p_byte);
return 1;
}
return 0;
}
uint32_t cola_fifo_flush(cola_fifo_t * p_fifo)
{
p_fifo->read_pos = p_fifo->write_pos;
return 1;
}
uint32_t cola_fifo_read(cola_fifo_t * p_fifo, uint8_t * p_byte_array, uint32_t p_size)
{
//VERIFY_PARAM_NOT_NULL(p_fifo);
//VERIFY_PARAM_NOT_NULL(p_size);
if(p_fifo == NULL)
{
return 0;
}
if(p_size == 0)
{
return 0;
}
const uint32_t byte_count = fifo_length(p_fifo);
const uint32_t requested_len = (p_size);
uint32_t index = 0;
uint32_t read_size = MIN(requested_len, byte_count);
(p_size) = byte_count;
// Check if the FIFO is empty.
if (byte_count == 0)
{
return 0;
}
// Check if application has requested only the size.
if (p_byte_array == NULL)
{
return 0;
}
// Fetch bytes from the FIFO.
while (index < read_size)
{
fifo_get(p_fifo, &p_byte_array[index++]);
}
(p_size) = read_size;
return p_size;
}
uint32_t cola_fifo_write(cola_fifo_t * p_fifo, uint8_t const * p_byte_array, uint32_t p_size)
{
//VERIFY_PARAM_NOT_NULL(p_fifo);
//VERIFY_PARAM_NOT_NULL(p_size);
if(p_fifo == NULL)
{
return 0;
}
if(p_size == 0)
{
return 0;
}
const uint32_t available_count = p_fifo->buf_size_mask - fifo_length(p_fifo) + 1;
const uint32_t requested_len = (p_size);
uint32_t index = 0;
uint32_t write_size = MIN(requested_len, available_count);
(p_size) = available_count;
// Check if the FIFO is FULL.
if (available_count == 0)
{
return 0;
}
// Check if application has requested only the size.
if (p_byte_array == NULL)
{
return 0;
}
//Fetch bytes from the FIFO.
while (index < write_size)
{
fifo_put(p_fifo, p_byte_array[index++]);
}
(p_size) = write_size;
return p_size;
}
三、引申
環形串口緩衝器的缺點是同一塊buffer中只能有一個緩衝器工作,如果當前申請的緩衝區沒有在使用那麼也會佔用內存,因此在這將緩衝器的設計思想做一下引申,一個可以同時多個緩衝器共用同一個buffer的例子。
實現原理:
1.申請一塊固定長度的數組用於緩衝區。如:uint8_t buffer[2048],申請2K的總緩衝區
2.將總的緩衝區分成2的整數冪字節大小的塊。如每塊128字節,可分成16塊。
3.使用位圖方式管理每塊,uint32_t bitmap,32位整形最大可管理32塊內存塊。
4.0-127字節標號塊0,128-255字節標號塊2.......,以此類推標號。
5.每次可申請一塊的大小,每次申請空閒且標號最小的塊,這樣可保證每次都是用低位空間。
6.每塊最高位字節記錄下一塊標號,這樣可將多個塊連城一個大塊
7.實現多通道,每個通道包括,最大字節長度,當前字節說,當前寫入的塊號,當前寫入處於某個塊的位置,當前讀出塊號,當前讀出塊位置。
缺點:多個緩衝器公用同一個buffer,在讀寫數據時需要加鎖。