在Zephyr内核对象–数据传递对象简介一文中已经大概介绍了FIFO/LIFO的特性,从这篇文章也知道FIFO和LIFO都是通过queue实现,这也就是为什么要把FIFO/LIFO放在同一篇文章中分析的原因。本文将继续说明FIFO/LILO的使用和实现。
使用
API
相似API
FIFO和LIFO API都是用宏包装queue的API实现,它们有一组类似的发送接收API,这里放在一起介绍
#define k_fifo_init(fifo)
#define k_lifo_init(lifo)
作用:初始化一个struct k_fifo/strut k_lifo
fifo/lifo: struct k_fifo/strut k_lifo
#define k_fifo_put(fifo, data)
#define k_lifo_put(lifo, data)
作用:将数据放入fifo/lifo
fifo/lifo:数据要放入的fifo/lifo
data: 要放入fifo/lifo的数据
#define k_fifo_alloc_put(fifo, data)
#define k_lifo_alloc_put(lifo, data)
作用:fifo/lifo分配一个空间,将数据放入
fifo/lifo:数据要放入的fifo/lifo
data: 要放入fifo/lifo的数据
#define k_fifo_get(fifo, timeout)
#define k_lifo_get(lifo, timeout)
作用:从fifo/lifo读出数据
fifo/lifo:要读的fifo/lifo
data: 读出的数据
FIFO特殊API
FIFO比LIFO多一些API
#define k_fifo_cancel_wait(fifo)
作用:放弃等待,让第一个等待的fifo的thread退出pending,k_fifo_get的数据将是NULL
fifo: 操作的fifo
#define k_fifo_put_list(fifo, head, tail)
作用:将一个单链表加入到fifo中
fifo: 操作的fifo
head: 单链表的第一个节点
tail: 单链表的最后一个节点
#define k_fifo_put_slist(fifo, list)
作用:将一个单链表加入到fifo中,这里单链表对象为sys_slist_t
fifo: 操作的fifo
list: sys_slist_t 单链表
#define k_fifo_is_empty(fifo)
作用:检查fifo是否为空
fifo: 要检查的fifo
#define k_fifo_peek_head(fifo)
作用:peek fifo 头部上的节点(下个将会被read的数据),但不会将数据从fifo删除
fifo: 被peek的fifo
返回:返回peek的数据
#define k_fifo_peek_tail(fifo)
作用:peek fifo 尾部上的节点(FIFO中最后进入的数据),但不会将数据从fifo删除
fifo: 被peek的fifo
返回:返回peek的数据
使用说明
可以在ISR中put fifo/lifo.也可在ISR内get fifo/fifo,但不能等待。
fifo/lifo不限制数据项的多少。
初始化
经过初始化的fifo/lifo才能被使用,下面两种方法是一样的,显示定义
1 | struct k_fifo my_fifo; |
隐式定义:
1 | K_FIFO_DEFINE(my_fifo); |
写数据
这里用fifo演示put,用Lifo演示alloc_put
1 | //使用put发送数据,最开始的word必须保留,用于存放queue,这也就是为什么要求4对其的原因 |
读数据
1 | void consumer_fifo_thread(int unused1, int unused2, int unused3) |
实现
前面已经提到过fifo/lifo都是使用queue实现,fifo/lifo是直接包queue的API ,每一个fifo/lifo API都对应一个queue的API,可以说fifo/lifo就是queue的使用方法不一样,先看结构体就可以看出使用的就是queue
FIFO/LIFO
1 | struct k_fifo { |
每个fifo/lifo都有一个对应的queue API,这里列出来,更详细的可以看include/kernel.h,下面斜体加粗的就是fifo/lifo区别所在
k_fifo_init->k_queue_init
k_fifo_cancel_wait->k_queue_cancel_wait
k_fifo_put->k_queue_append
k_fifo_alloc_put->k_queue_alloc_append
k_fifo_put_list->k_queue_append_list
k_fifo_put_slist->k_queue_merge_slist
k_fifo_get->k_queue_get
k_fifo_is_empty->k_queue_is_empty
k_fifo_peek_head->k_queue_peek_head
k_fifo_peek_tail->k_queue_peek_tail
k_lifo_init->k_queue_init
k_lifo_put->k_queue_prepend
k_lifo_alloc_put->k_queue_alloc_prepend
k_lifo_get->k_queue_get
Queue
从上一节可以看出FIFO就是用queue实现的,因此我们继续分析queue
Queue初始化
初始化:建立一个wait_q,建立一个单项标志链表
添加数据到queue
添加数据到queue基本都是通过queue_insert完成
k_queue_append->queue_insert: 在链表尾部插入
k_queue_insert->queue_insert: 在指定节点后插入
k_queue_prepend->queue_insert:在链表头部插入
z_impl_k_queue_alloc_append->queue_insert:从thread pool中分配节点,并插入到尾部
z_impl_k_queue_alloc_prepend->queue_insert:从thread pool中分配节点,并插入到头部
z_impl_k_queue_peek_head -> z_queue_node_peek: 从链表中读取头,但不删除该节点
z_impl_k_queue_peek_tail -> z_queue_node_peek: 从链表中读取尾,但不删除该节点
1 | static s32_t queue_insert(struct k_queue *queue, void *prev, void *data, |
当传入参数data是净数据时(没有在data的最开始预留node位置),那么在insert的使用需要用alloc指定要分配节点
从Queue中读取数据
使用k_queue_get->z_impl_k_queue_get从queue中读取数据,读取数据时数据从queue中删除,实际的删除动作就是从链表中将节点移除
1 | void *z_impl_k_queue_get(struct k_queue *queue, s32_t timeout) |
从node中提取数据
从链表中peek或者移出的node,需要使用下面API从node中提取数据
1 | void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free) |
如果是移出的node在Insert的时候是从线程池alloc出来的,那么在这里需要needs_free=true指定要free空间。如果是Peek的node数据时,则只用提取数据,例如:
1 | static inline void *z_impl_k_queue_peek_head(struct k_queue *queue) |
放弃等待数据
k_queue_get的时候如果queue内没有数据,允许进行等待。等待期间可以使用k_queue_cancel_wait放弃等待
1 | void z_impl_k_queue_cancel_wait(struct k_queue *queue) |
关于poll
一般情况下queue等待数据是通过让其thread 等待queue的wait_q实现,一旦配置poll后将不再使用该机制,而是通过poll,queue get使用k_queue_poll等待queue有数据,因此可能和其它thread等待poll条件发生冲突
1 | static void *k_queue_poll(struct k_queue *queue, s32_t timeout) |
插入链表数据
queue也提供2个API将数据以链表的形式插入到queue中,区别是k_queue_append_list插入后不会影响被插入链表,而k_queue_merge_slist插入链表后会把链表清空
1 | int k_queue_append_list(struct k_queue *queue, void *head, void *tail) |
1 | int k_queue_merge_slist(struct k_queue *queue, sys_slist_t *list) |
图例
下面用一张图简单说明FIFO/LIFO和queue操作之间的关系
可以看到FIFO是通过k_queue_appen将数据加入到queue的链表尾,LIFO通过k_queue_prepend将数据加入到queue的链表头,而FIFO/LIFO读数据都是用k_queue_get从链表头读数据,所以达到了FIFO先进先出,LIFO后进先出的目的。
参考
https://docs.zephyrproject.org/latest/reference/kernel/data_passing/fifos.html
https://docs.zephyrproject.org/latest/reference/kernel/data_passing/lifos.html