Overview
The ring buffer is one of the commonly used data structures in embedded software development, storing content in a first-in-first-out manner, and is used to implement asynchronous “stream” replication of data. Zephyr provides a struct ring_buf
abstraction to manage such data structures. Various drivers in Zephyr (UART, Modem, I2S, etc.), as well as shell, USB, net, and tracking, utilize ring buffers.
Zephyr’s ring buffer implementation supports two modes:
-
Byte Mode -
Item Mode
Usage Restrictions:
-
The two modes cannot be mixed within the same buffer. -
The ring buffer cannot exceed #define RING_BUFFER_MAX_SIZE 0x80000000U
-
It does not support concurrency. -
The maximum size of an item cannot exceed 1020 bytes.
Initialization
struct ring_buf
is used to manage the ring buffer, with its internal data hidden from the user. This structure is placed in memory accessible to the user and must be initialized with ring_buf_init ( )
or ring_buf_item_init ( )
before use. During initialization, the user must provide a segment of memory as the actual storage space for the ring buffer.
Byte Mode
Byte mode is initialized using ring_buf_init ( )
, as shown in the example below:
# define RING_BUF_BYTE_SIZE 320
struct ring_buf rb_byte;
uint8_t ring_buf_byte [RING_BUF_BYTE_SIZE];
ring_buf_init ( &rb_byte, RING_BUF_BYTE_SIZE, ring_buf_byte ) ;
You can also use a macro to statically define and initialize the ring buffer, which produces the same effect as above:
RING_BUF_DECLARE ( rb_byte, RING_BUF_BYTE_SIZE ) ;
Item Mode
Item mode is initialized using ring_buf_item_init ( )
, where the minimum operating unit of the initialized ring buffer is a word (4 bytes), as shown in the example below:
# define RING_BUF_ITEM_SIZE 128
struct ring_buf rb_item;
uint32_t ring_buf_item [RING_BUF_ITEM_SIZE];
ring_buf_item_init ( &rb_item, RING_BUF_ITEM_SIZE, ring_buf_item ) ;
You can also use a macro to statically define and initialize the ring buffer, which produces the same effect as above:
RING_BUF_ITEM_DECLARE ( rb_item, RING_BUF_ITEM_SIZE ) ;
Operations
Writing Data
Byte Mode
Data can be copied to the byte mode ring buffer by calling ring_buf_put ( )
. When the data length exceeds the available space in the buffer, only part of the data will be copied, and the return value will be the length of the copied data. An example is shown below:
uint8_t my_data [MY_RING_BUF_BYTES];
uint32_t ret;
ret = ring_buf_put ( &ring_buf, my_data, MY_RING_BUF_BYTES ) ;
if ( ret != MY_RING_BUF_BYTES ) {
// When the data length exceeds the buffer length, only part of the data will be copied
// Here, handle the incomplete copy, for example, wait and copy the remaining data again
}
Additionally, the byte mode ring buffer supports a request+commit two-step operation similar to MPSC/SPSC, which can reduce copy time and improve efficiency. The following example operation is equivalent to put:
uint32_t size;
uint32_t rx_size;
uint8_t *data;
int err;
// Request space size MY_RING_BUF_BYTES from the ring buffer, the address of the space is stored in data, and the return value is the actual requested space size
size = ring_buf_put_claim ( &ring_buf, &data, MY_RING_BUF_BYTES ) ;
// Receive data from UART into the space requested from the ring buffer
rx_size = uart_rx ( data, size ) ;
// Commit data
err = ring_buf_put_finish ( &ring_buf, rx_size ) ;
if ( err != 0 ) {
// This should not happen under normal circumstances, unless an incorrect rx_size greater than the requested size is passed
}
Item Mode
Item mode only supports copy operations. By calling ring_buf_item_put ( )
, data items can be copied to the ring buffer. When the data length exceeds the available space in the buffer, no copying will occur, and it will return -EMSGSIZE
. Each data item in the ring buffer will occupy an additional 32 bits for management of that item.
struct ring_element {
uint32_t type :16; // Type of data item specified by the application
uint32_t length :8; // Length of the data item in words
uint32_t value :8; // Value specified by the application
};
Since the length of the data item only uses 8 bits for storage, the maximum length of the data item is 255*4=1020 bytes.
An example is shown below:
uint32_t data [MY_DATA_WORDS];
int ret;
// Write data item data, type is TYPE_FOO, value is 0. Type and value are user-defined for identifying the data item
ret = ring_buf_item_put ( &ring_buf, TYPE_FOO, 0, data, MY_DATA_WORDS ) ;
if ( ret == -EMSGSIZE ) {
// Not enough space
...
}
Note: Do not write empty data items (size = 0).
Reading Data
Byte Mode
Data can be read from the byte mode ring buffer by calling ring_buf_get ( )
. When the requested amount exceeds the valid data in the buffer, valid data will be copied, and the return value will be the length of the copied data. An example is shown below:
uint8_t my_data [MY_RING_BUF_BYTES];
size_t ret;
ret = ring_buf_get ( &ring_buf, my_data, sizeof ( my_data )) ;
if ( ret != sizeof ( my_data )) {
// Handle the case where data is insufficient, wait until there is data to continue reading
}
Additionally, the byte mode ring buffer supports a request+release two-step operation similar to MPSC/SPSC, which can reduce copy time and improve efficiency. The following example operation is equivalent to get:
uint32_t size;
uint32_t proc_size;
uint8_t *data;
int err;
// Request data, the address of the requested data is stored in data, return value is the valid data length
size = ring_buf_get_claim ( &ring_buf, &data, MY_RING_BUF_BYTES ) ;
// Process data
proc_size = process ( data, size ) ;
// Notify that data processing is complete and release the processed data space
err = ring_buf_get_finish ( &ring_buf, proc_size ) ;
if ( err != 0 ) {
// This should not happen under normal circumstances, unless an incorrect proc_size greater than the requested size is passed
...
}
Item Mode
Item mode only supports copy operations. By calling ring_buf_item_get ( )
, data items can be copied from the ring buffer to user memory space. When the length of the data item to be obtained exceeds the space provided by the user, the read will fail and return -EMSGSIZE
. If there are no data items to read, it will return -EAGAIN
, and when obtaining the data item, the type and value written to that data item can be obtained.
An example is shown below:
uint32_t my_data [MY_DATA_WORDS];
uint16_t my_type;
uint8_t my_value;
uint8_t my_size;
int ret;
my_size = MY_DATA_WORDS;
ret = ring_buf_item_get ( &ring_buf, &my_type, &my_value, my_data, &my_size ) ;
if ( ret == -EMSGSIZE ) {
printk ( "Buffer is too small, need %d uint32_t\n", my_size ) ;
} else if ( ret == -EAGAIN ) {
printk ( "Ring buffer is empty\n" ) ;
} else {
printk ( "Got item of type %u value %u of size %u dwords\n",
my_type, my_value, my_size ) ;
...
}
Other APIs
In addition to the APIs mentioned above, the ring buffer also provides the following APIs:
// Peek data, copy data of length size from the ring buffer buf to data, returning the actual copied length
// The difference from ring_buf_get is that ring_buf_peek retains the data in the ring buffer after copying, and the space will not be released
uint32_t ring_buf_peek ( struct ring_buf *buf, uint8_t *data, uint32_t size ) ;
// Clear data in the ring buffer
static inline void ring_buf_reset ( struct ring_buf *buf ) ;
// Return the maximum byte capacity of the ring buffer buf
static inline uint32_t ring_buf_capacity_get ( struct ring_buf *buf ) ;
// Return the size of valid data in bytes in the ring buffer buf
static inline uint32_t ring_buf_size_get ( struct ring_buf *buf ) ;
// Return the size of free space in bytes in the ring buffer buf
static inline uint32_t ring_buf_space_get ( struct ring_buf *buf ) ;
// Return the size of free space in items in the ring buffer buf
static inline uint32_t ring_buf_item_space_get ( struct ring_buf *buf ) ;
// Check if the ring buffer is empty
static inline bool ring_buf_is_empty ( struct ring_buf *buf ) ;
References
https://docs.zephyrproject.org/3.5.0/kernel/data_structures/ring_buffers.html