文章目录
简介 队列数据结构
队列的基本操作 void k_queue_init(struct k_queue *queue) void k_queue_cancel_wait(struct k_queue *queue) queue_insert void k_queue_append(struct k_queue *queue, void *data) int32_t k_queue_alloc_append(struct k_queue *queue, void *data) void k_queue_prepend(struct k_queue *queue, void *data) int32_t k_queue_alloc_prepend(struct k_queue *queue, void *data) void k_queue_insert(struct k_queue *queue, void *prev, void *data) int k_queue_append_list(struct k_queue *queue, void *head, void *tail) int k_queue_merge_slist(struct k_queue *queue, sys_slist_t *list) void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free) void *k_queue_get(struct k_queue *queue, k_timeout_t timeout) bool k_queue_remove(struct k_queue *queue, void *data) bool k_queue_unique_append(struct k_queue *queue, void *data) int k_queue_is_empty(struct k_queue *queue) void *k_queue_peek_head(struct k_queue *queue) void *k_queue_peek_tail(struct k_queue *queue)
总结
简介
Zephyr 内核中支持队列操作,该队列是由带头指针和尾指针的单链表组成。 并且提供了队列的常见操作方式,和内核中的其他对象一样,向队列中插入数据是不会进行深拷贝的,用户向其中插入数据时需要自己申请动态空间,否则就会造成内存错误。
队列数据结构
alloc_node
# ifdef __LP64__
typedef uint64_t unative_t ;
# else
typedef uint32_t unative_t ;
# endif
struct _sfnode {
unative_t next_and_flags;
} ;
typedef struct _sfnode sys_sfnode_t ;
struct alloc_node {
sys_sfnode_t node;
void * data;
} ;
struct alloc_node 是队列中的节点,它由一个 sys_sfnode_t 类型的 node 和一个数据指针构成,其中 node 是一个32位/64位的变量,用于保存下一个节点的指针,相当于单链表的next指针,data用于保存用户传入的数据首地址。
k_queue
struct _sflist {
sys_sfnode_t * head;
sys_sfnode_t * tail;
} ;
typedef struct _sflist sys_sflist_t ;
struct k_queue {
sys_sflist_t data_q;
struct k_spinlock lock;
_wait_q_t wait_q;
_POLL_EVENT;
SYS_PORT_TRACING_TRACKING_FIELD ( k_queue)
} ;
k_queue 中包含一个 sys_sflist_t 类型的 data_q 节点,里面就是单链表的头指针和尾指针,用于队列的头部和尾部的插入操作。 lock 是一个自旋锁,用于多核之间实现互斥操作,确保队列中的各种操作是安全的,如果是在单核CPU中,其功能类似互斥锁。 wait_q 是一个双链表,用于保存那些因为等待队列数据而被挂起的线程,当向队列中插入数据时,会将优先级最高的被挂起线程从双链表中取出,将其恢复为就绪态,等待下一次被唤醒。 最后是 _POLL_EVENT:
# ifdef CONFIG_POLL
# define _POLL_EVENT_OBJ_INIT ( obj) \
. poll_events = SYS_DLIST_STATIC_INIT ( & obj. poll_events) ,
# define _POLL_EVENT sys_dlist_t poll_events
# else
# define _POLL_EVENT_OBJ_INIT ( obj)
# define _POLL_EVENT
# endif
当CONFIG_POLL使能之后会开启POLL机制,该机制用于同时等待多个条件被满足, poll_events 也是一个双链表,此处不讨论 poll 机制。
队列的基本操作
void k_queue_init(struct k_queue *queue)
该函数用于初始化队列成员变量,例如将单链表 data_q 中的头指针和尾指针设置为NULL,将双链表 wait_q 的两个指针指向 wait_q 本身。
void k_queue_cancel_wait(struct k_queue *queue)
queue_insert
在Zephyr的队列中,所有的数据插入操作都由 queue_insert 函数完成,下面是该函数的实现:
static int32_t queue_insert ( struct k_queue * queue, void * prev, void * data, bool alloc, bool is_append)
{
struct k_thread * first_pending_thread;
k_spinlock_key_t key = k_spin_lock ( & queue-> lock) ;
SYS_PORT_TRACING_OBJ_FUNC_ENTER ( k_queue, queue_insert, queue, alloc) ;
if ( is_append) {
prev = sys_sflist_peek_tail ( & queue-> data_q) ;
}
first_pending_thread = z_unpend_first_thread ( & queue-> wait_q) ;
if ( first_pending_thread != NULL ) {
SYS_PORT_TRACING_OBJ_FUNC_BLOCKING ( k_queue, queue_insert, queue, alloc, K_FOREVER) ;
prepare_thread_to_run ( first_pending_thread, data) ;
z_reschedule ( & queue-> lock, key) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, queue_insert, queue, alloc, 0 ) ;
return 0 ;
}
if ( alloc) {
struct alloc_node * anode;
anode = z_thread_malloc ( sizeof ( * anode) ) ;
if ( anode == NULL ) {
k_spin_unlock ( & queue-> lock, key) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, queue_insert, queue, alloc,
- ENOMEM) ;
return - ENOMEM;
}
anode-> data = data;
sys_sfnode_init ( & anode-> node, 0x1 ) ;
data = anode;
}
else {
sys_sfnode_init ( data, 0x0 ) ;
}
SYS_PORT_TRACING_OBJ_FUNC_BLOCKING ( k_queue, queue_insert, queue, alloc, K_FOREVER) ;
sys_sflist_insert ( & queue-> data_q, prev, data) ;
handle_poll_events ( queue, K_POLL_STATE_DATA_AVAILABLE) ;
z_reschedule ( & queue-> lock, key) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, queue_insert, queue, alloc, 0 ) ;
return 0 ;
}
此处讲解一下 struct alloc_node 中的 node 成员变量的作用:
充当单链表中的next指针 标记node来源,低2位非0代表节点由 z_thread_malloc 动态申请,否则视为用户提供
void k_queue_append(struct k_queue *queue, void *data)
void k_queue_append ( struct k_queue * queue, void * data)
{
SYS_PORT_TRACING_OBJ_FUNC_ENTER ( k_queue, append, queue) ;
( void ) queue_insert ( queue, NULL , data, false, true) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, append, queue) ;
}
该函数用于从队列尾部插入数据,此时 data 的前4字节必须是 sys_sfnode_t 类型,在 sys_sfnode_t 之后定义用户数据,以此来保证data的头部与链表节点是对齐的,常见的使用方式如下:
struct bt_mesh_test_msg {
sys_snode_t _node;
size_t len;
uint8_t seq;
struct bt_mesh_msg_ctx ctx;
} ;
void func ( void )
{
struct bt_mesh_test_msg * msg;
if ( k_mem_slab_alloc ( & msg_pool, ( void * * ) & msg, K_NO_WAIT) ) {
test_stats. recv_overflow++ ;
return - EOVERFLOW;
}
msg-> len = 0 ;
msg-> seq = 0 ;
msg-> ctx = 0 ;
k_queue_append ( & recv, msg) ;
}
此处需要注意,虽然32位系统用的比较多,但是也不能用 int 替换 sys_snode_t,一旦所使用的系统由32位变为64位将造成严重错误,下面是驱动文件 zephyr/drivers/modem/hl7800.c:400 中的错误:
struct stale_socket {
int reserved;
enum net_sock_type type;
uint8_t id;
bool allocated;
} ;
int32_t k_queue_alloc_append(struct k_queue *queue, void *data)
k_queue_alloc_append 和 k_queue_append 唯一的差别是增加了动态申请节点的步骤,如果使用的是不带alloc的插入函数,data代表的就是整个节点,如果使用带alloc的版本,data代表的只是数据部分,不包含头部的 sys_snode_t 。
void k_queue_prepend(struct k_queue *queue, void *data)
k_queue_prepend 调用queue_insert从头部插入整个节点,data 是 sys_snode_t 子类的一个对象指针。
int32_t k_queue_alloc_prepend(struct k_queue *queue, void *data)
k_queue_prepend 调用queue_insert从头部插入整个节点,data 仅代表数据。
void k_queue_insert(struct k_queue *queue, void *prev, void *data)
从某个节点后插入新节点,data 是 sys_snode_t 子类的一个对象指针。
int k_queue_append_list(struct k_queue *queue, void *head, void *tail)
将单链表添加到当前队列中,该操作实际上就是改变了节点的next指针,并未进行深拷贝。
int k_queue_merge_slist(struct k_queue *queue, sys_slist_t *list)
k_queue_merge_slist 功能和 k_queue_insert 一致,也是将list添加到队列中,可以将 k_queue_merge_slist 当作 k_queue_append_list 的重载版本。
void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free)
void * z_queue_node_peek ( sys_sfnode_t * node, bool needs_free)
{
void * ret;
if ( ( node != NULL ) && ( sys_sfnode_flags_get ( node) != ( uint8_t ) 0 ) ) {
struct alloc_node * anode;
anode = CONTAINER_OF ( node, struct alloc_node , node) ;
ret = anode-> data;
if ( needs_free) {
k_free ( anode) ;
}
} else {
ret = ( void * ) node;
}
return ret;
}
z_queue_node_peek用于从队列中取出数据,取出时会使用 sys_sfnode_flags_get 判断低2位的值与 needs_free 来决定是否需要真的释放占用的内存。
void *k_queue_get(struct k_queue *queue, k_timeout_t timeout)
void * z_impl_k_queue_get ( struct k_queue * queue, k_timeout_t timeout)
{
k_spinlock_key_t key = k_spin_lock ( & queue-> lock) ;
void * data;
SYS_PORT_TRACING_OBJ_FUNC_ENTER ( k_queue, get, queue, timeout) ;
if ( likely ( ! sys_sflist_is_empty ( & queue-> data_q) ) ) {
sys_sfnode_t * node;
node = sys_sflist_get_not_empty ( & queue-> data_q) ;
data = z_queue_node_peek ( node, true) ;
k_spin_unlock ( & queue-> lock, key) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, get, queue, timeout, data) ;
return data;
}
SYS_PORT_TRACING_OBJ_FUNC_BLOCKING ( k_queue, get, queue, timeout) ;
if ( K_TIMEOUT_EQ ( timeout, K_NO_WAIT) ) {
k_spin_unlock ( & queue-> lock, key) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, get, queue, timeout, NULL ) ;
return NULL ;
}
int ret = z_pend_curr ( & queue-> lock, key, & queue-> wait_q, timeout) ;
SYS_PORT_TRACING_OBJ_FUNC_EXIT ( k_queue, get, queue, timeout,
( ret != 0 ) ? NULL : _current-> base. swap_data) ;
return ( ret != 0 ) ? NULL : _current-> base. swap_data;
}
k_queue_get 函数等待队列中数据,调用该函数会将数据从队列中移除。
bool k_queue_remove(struct k_queue *queue, void *data)
根据提供的data查找链表中是否存在对应节点,存在则将其移除, data 必须是继承自 sys_sfnode_t 的节点。
bool k_queue_unique_append(struct k_queue *queue, void *data)
插入一个节点,前提是该节点不在链表中,保证该节点唯一,data 必须是继承自 sys_sfnode_t 的节点。
int k_queue_is_empty(struct k_queue *queue)
void *k_queue_peek_head(struct k_queue *queue)
void *k_queue_peek_tail(struct k_queue *queue)
总结
Zephyr 中的队列是链式队列,队列的插入和并不会进行数据的深拷贝,使用时应避免使用栈上的内存,如果传入的对象来自于栈需要仔细考虑同步问题,否则会造成严重错误。 提供alloc版本的插入函数,会从当前线程的资源池中分配空间作为节点,但是不会拷贝用户数据,当用户取出数据时会自动释放申请的内存。 链表中的数据可直接合并到队列中,因为队列本身就是由链表组成的。