概述 链接到标题

环形缓存是嵌入式软件开发中常用的数据结构之一, 其内容按先进先出的顺序存储,用于实现数据的异步「流式」复制。Zephyr 提供了一个 struct ring_buf 抽象来管理此类数据结构。 Zephyr 中的驱动 ( UART, Modem,I2S 等 ) ,shell, usb, net, tracking 都有使用环形缓存。

Zephyr 的环形缓存实现支持两种模式:

  • 字节模式
  • 数据项模式

使用限制:

  • 同一个缓存区内两种模式不能混用。
  • 环形缓存最大不能超过 #define RING_BUFFER_MAX_SIZE 0x80000000U
  • 本身不支持并发
  • 数据项最大不超过 1020 字节

初始化 链接到标题

struct ring_buf 用于管理环形缓存,其内部数据对用户隐藏,该结构放置在用户可访问的内存中,必须用 ring_buf_init ( ) ring_buf_item_init ( ) 初始化后才能使用。初始化时需要用户提供一段内存作为环形缓存实际存储数据的空间。

字节模式

字节模式使用 ring_buf_init ( ) 进行初始化,示例如下:

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

也可以使用宏静态的定义&初始化环形缓存,下面这段代码的效果和上面一致

RING_BUF_DECLARE ( rb_byte, RING_BUF_BYTE_SIZE ) ;

数据项模式

数据项模式使用 ring_buf_item_init ( ) 进行初始化,其初始化的环形缓存最小操作单位为字 ( 4 字节 ) ,示例如下:

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

也可以使用宏静态的定义&初始化环形缓存,下面这段代码的效果和上面一致

RING_BUF_ITEM_DECLARE ( rb_item, RING_BUF_ITEM_SIZE ) ;

操作 链接到标题

写入数据 链接到标题

字节模式

通过调用 ring_buf_put ( ) 将字节复制到字节模式环形缓冲区, 当数据长度超过缓存区的空闲空间长度时,会拷贝部分数据,返回值为拷贝的数据长度。示例如下

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 ) {
    //当数据长度超过缓存区的长度时,会拷贝部分数据
    //这里需要做未完整拷贝的处理,例如进行等待,再次拷贝剩余数据
}

除此之外,字节模式的环形缓存还支持类似 MPSC/SPSC 的请求+提交两部操作,这样可以减少一次拷贝时间,提高效率,下面示例的操作等同于 put

uint32_t size;
uint32_t rx_size;
uint8_t *data;
int err;

//从环形缓存中请求空间大小 MY_RING_BUF_BYTES,空间地址放在 data 内,返回值为实际请求空间的大小
size = ring_buf_put_claim ( &ring_buf, &data, MY_RING_BUF_BYTES ) ;

//从 UART 将数据接收到从环形缓存请求的空间内
rx_size = uart_rx ( data, size ) ;

//提交数据
err = ring_buf_put_finish ( &ring_buf, rx_size ) ;
if ( err != 0 ) {
    //正常情况下不应该发生,除非传入了错误的 rx_size 大于请求的 size
}

数据项模式

数据项模式只支持复制方式,通过调用 ring_buf_item_put ( ) 将数据项复制到环形缓冲区内, 当数据长度超过缓存区的空闲空间长度时,不会发生拷贝,直接返回 -EMSGSIZE。 每一个数据项在环形缓存会多占用 32bit 用作该项的管理

struct ring_element {
    uint32_t  type   :16; //应用指定数据项的类型
    uint32_t  length :8;  //数据项的字长
    uint32_t  value  :8;  //应用指定值
};

由于数据项的字长只用了 8bit 存储,因此数据项最大长度为 255*4=1020 字节。

示例如下:

uint32_t data [MY_DATA_WORDS];
int ret;

//写入数据项 data,类型为 TYPE_FOO,值为 0. 类型和值都为应用自定,用于标识数据项
ret = ring_buf_item_put ( &ring_buf, TYPE_FOO, 0, data, MY_DATA_WORDS ) ;
if ( ret == -EMSGSIZE ) {
   //空间不够
    ...
}

注意:不要写入空数据项 ( size = 0 )

读取数据 链接到标题

字节模式

通过调用 ring_buf_gut ( ) 将数据从字节模式环形缓冲区读出, 当需求量超过缓存区的有效数据时,会拷贝有效数据,返回值为拷贝的数据长度。示例如下

uint8_t my_data [MY_DATA_BYTES];
size_t  ret;

ret = ring_buf_get ( &ring_buf, my_data, sizeof ( my_data )) ;
if ( ret != sizeof ( my_data )) {
    //数据没读够的操作,可以等待到有数据了继续读
}

除此之外,字节模式的环形缓存还支持类似 MPSC/SPSC 的请求+释放两部操作,这样可以减少一次拷贝时间,提高效率,下面示例的操作等同于 get

uint32_t size;
uint32_t proc_size;
uint8_t *data;
int err;

//请求数据,请求到数据的地址放在 data, 返回值为有效数据长度
size = ring_buf_get_claim ( &ring_buf, &data, MY_RING_BUF_BYTES ) ;

//处理数据
proc_size = process ( data, size ) ;

//通知数据处理完成,释放已处理数据的空间
err = ring_buf_get_finish ( &ring_buf, proc_size ) ;
if ( err != 0 ) {
    //正常情况下不应该发生,除非传入了错误的 proc_size 大于请求的 size
    ...
}

数据项模式

数据项模式只支持复制方式,通过调用 ring_buf_item_get ( ) 将数据项从环形缓冲区复制到用户内存空间中, 当获取的数据项长度大于用户给予的空间时读取会失败,直接返回 -EMSGSIZE。 如果没有数据项可读会返回 -EAGAIN,获取数据项时能得到写入该数据项时的类型和值。

示例如下:

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 ) ;
    ...
}

其它 API 链接到标题

除了上述 API 外,环形缓存还提供如下 API:

//Peek 数据,从环形缓存 buf 内拷贝长为 size 的数据到 data 内,返回实际的拷贝长度
//与 ring_buf_get 的差异是,ring_buf_peek 拷贝后数据仍然保留在环形缓存中,空间不会被释放
uint32_t ring_buf_peek ( struct ring_buf *buf, uint8_t *data, uint32_t size ) ;

//清空环形缓存中的数据
static inline void ring_buf_reset ( struct ring_buf *buf ) ;

//返回环形缓存 buf 的最大字节容量
static inline uint32_t ring_buf_capacity_get ( struct ring_buf *buf ) ;

//返回环形缓存 buf 中的有效数据字节大小
static inline uint32_t ring_buf_size_get ( struct ring_buf *buf ) ;

//返回环形缓存 buf 中的空闲空间字节大小
static inline uint32_t ring_buf_space_get ( struct ring_buf *buf ) ;

//返回环形缓存 buf 中的空闲空间字大小
static inline uint32_t ring_buf_item_space_get ( struct ring_buf *buf ) 

//判断环形缓存是否为空
static inline bool ring_buf_is_empty ( struct ring_buf *buf ) ;

参考 链接到标题

https://docs.zephyrproject.org/3.5.0/kernel/data_structures/ring_buffers.html