LwRB 使用说明

LwRB 使用说明

LwRB (lightweight ring buffer) 是一个轻量级的环形缓冲区,除了支持 ring buffer 常规的读写操作,它还有自己的特色,比如支持读写事件通知,支持 DMA 零拷贝收发数据。

  • LwRB 项目工程为:https://github.com/MaJerle/lwrb。
  • RT-Thread 移植版本为:https://github.com/Jackistang/lwrb2rtt 。

下面依次介绍相应功能的使用方式:

LwRB 原理浅析

详细内容请参考 LwRB - How it works,以下仅写出我的理解。

LwRB 内部有一块地址连续的缓冲区,这个缓冲区是用户调用 lwrb_init() 时指定的,接口如下:

uint8_t     lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size);

而且 LwRB 内部维护了一个读(R)、写(W)指针,分别指向下一个可读下一个可写的位置。写入一个字节数据时,将数据放入写指针的位置,并将写指针加 1 ,若超出缓冲区范围,则回滚到缓冲区起始处。

Different buffer corner cases

例如上图例程,缓冲区大小 S 为 8 :

  • 初始时:RW 相等,代表 LwRB 为空。
  • 写入 4 个字节:W 移动到 4 的位置,此时 LwRB 存储了 4 个字节。
  • 写入 3 个字节:W 移动到 7 的位置,此时 LwRB 存储了 7 个字节,而且 W == R-1 , 或者说 W == (R + S - 1) % S,代表 LwRB 已满。
  • 读出 5 个字节,并再次写入 4 个字节:W 移动到 3,R 移动到 5,此时 LwRB 存储了 6 个字节。
  • 写入 1 个字节:W 移动到 4,此时 LwRB 存储了 7 个字节,而且 W == R-1 ,或者说 W == (R + S - 1) % S,代表 LwRB 已满。

需要注意的是,lwrb 内部 W == R 代表 LwRB 为空,W == (R + S - 1) % S 代表 LwRB 为满。

读写操作

LwRB 里的读写操作接口为:

/* Read/Write functions */
size_t      lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw);
size_t      lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr);
size_t      lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp);
  • lwrb_write() 用于向 LwRB 里写入数据,并移动写指针。
  • lwrb_read() 用于从 LwRB 里读出数据,并移动读指针。
  • lwrb_peek() 用于从 LwRB 里读出数据,不移动读指针。

这里需要区分 lwrb_read()lwrb_peek() 的不同之处,read 是真正地从 LwRB 里把数据取出来,会修改读指针,read 之后该数据就被 LwRB 移除了;而 peek 操作从 LwRB 里读数据,但并不取出来,不会修改读指针,peek 之后数据仍在 LwRB 里。

lwrb_peek() 函数还有一些特殊的用法,函数原型如下:

size_t      lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp);
  • buff - LwRB 对象
  • skip_count - 跳过几个数据再开始读取
  • data - 读取数据缓冲区
  • btp - 需要读取的字节数(bytes to peek)

同时该函数返回实际读取的字节数。以下来理解如何使用:

假设 LwRB 内部状态如下:W 处于 4 的位置,R 处于 6 的位置。

image

此时我们想要从 LwRB 里读取 4 个字节,调用 lwrb_peek(&buff, 0, data, 4); ,则最终得到的数据为:6, 7, 0, 1 ,该函数内部帮我们处理了读指针回滚的情况,这是目前许多 ring buffer 不具备的功能。而且该函数还支持跳过最开始处的字节,例如调用 lwrb_peek(&buff, 2, data, 4); ,则最终得到的数据为:0, 1, 2, 3,该函数内部自动跳过了读指针对开始处的 6, 7 这两个字节数据。

例程可参考:getting_started.c 和 lwrb_peek.c 。

读写事件通知

详细内容请参考 LwRB - Events,以下仅写出我的理解。

读写事件通知接口如下:

typedef void (*lwrb_evt_fn)(LWRB_VOLATILE struct lwrb* buff, lwrb_evt_type_t evt, size_t bp);
void        lwrb_set_evt_fn(LWRB_VOLATILE lwrb_t* buff, lwrb_evt_fn fn);

lwrb 的事件通知功能是用户调用 lwrb_set_evt_fn() 向 lwrb 注册一个回调函数,然后 lwrb 每次进行读写数据时,都会通过这个回调函数通知用户当前写入了多少个字节,或读取了多少个字节。

该功能可用于打印日志释放信号量等,更多使用场景还待挖掘。

例程可参考:event.c 。

DMA 零拷贝收发数据

详细内容请参考 LwRB - DMA for embedded systems,以下仅写出我的理解。

LwRB 提供的零拷贝功能,按照工作方式可分为两种类型:

  • 从 LwRB 里零拷贝读取数据
  • 向 LwRB 里零拷贝写入数据

先介绍从 LwRB 里零拷贝读取数据,相关接口为:

/* Read data block management */
void*       lwrb_get_linear_block_read_address(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_get_linear_block_read_length(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len);

假设当前 LwRB 内部状态如下图所示:

image

LwRB 内部缓冲区已满,W 处于 4 的位置,R 处于 5 的位置。

接口 lwrb_get_linear_block_read_address() 可获取当前 LwRB 内部缓冲区第一个可读取字节的地址,例如上图中的 R,而 lwrb_get_linear_block_read_length() 函数获取当前 LwRB 内部缓冲区可读取的,且地址连续的字节数,例如上图中会得到结果 3,表示可连续读取后续的数据 5, 6, 7

利用这两个函数,我们就得到了一个指针 p 和一个大小 lenp 指向数据的起始地址,len 代表数据的字节大小,将 plen 传给 DMA,即可直接数据发送了。

image

DMA 发送完成后,我们还需要更新 LwRB 里的 R 指针,通过 lwrb_skip() 函数移动 R 指针,如下图所示:

image

至此,我们就完成了从 LwRB 里零拷贝读取数据的流程,例程可参考:zero_copy_from_lwrb_memory.c 。

向 LwRB 里零拷贝写入数据的操作也类似,相关接口为:

/* Write data block management */
void*       lwrb_get_linear_block_write_address(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_get_linear_block_write_length(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_advance(LWRB_VOLATILE lwrb_t* buff, size_t len);

假设当前 LwRB 内部状态如下图所示:

image

LwRB 内部缓冲区为空,RW 都处于 4 的位置。

接口 lwrb_get_linear_block_write_address() 可获取 LwRB 内部缓冲区第一个可写入字节的地址,例如上图中的 W,而 lwrb_get_linear_block_read_length() 函数返回 LwRB 当前可连续地址写入的大小,例如上图中的结果为 4,表示可连续写入 4, 5, 6, 7 处的数据。

利用这两个函数,我们就得到了一片地址连续的缓冲区,包括其首地址 p 和大小 len,将其传入 DMA 即可开始接收数据了。

DMA 接收完成后,我们还需要更新 LwRB 内部的 W 指针,通过 lwrb_advance() 函数移动 W 指针,如下图所示:

image

至此,我们就完成了向 LwRB 里零拷贝写入数据的流程,例程可参考:zero_copy_to_lwrb_memory.c 。

线程安全

当只有一个写线程和一个读线程时,LwRB 是线程安全的,不需要额外加锁。

详细内容请参考 LwRB - Thread safety。

你可能感兴趣的:(LwRB 使用说明)