2021SC@SDUSC
本周我们首先继续上周的消息队列工作分析TencentOS tiny的优先级消息队列机制,对消息队列收个尾,之后主要学习了TencentOS tiny的邮箱队列
一. 优先级消息队列
上周我们分析了消息队列,那么优先级消息队列的实现和消息队列是类似的,通过在优先级队列的基础上加上pend-post机制来实现。
TencentOS-tiny中优先级消息队列的实现在tos_priority_message_queue.h
和tos_priority_message_queue.c
中,我们来看一下源码:
typedef struct k_priority_message_queue_st {
knl_obj_t knl_obj;
pend_obj_t pend_obj;
void *prio_q_mgr_array;
k_prio_q_t prio_q;
} k_prio_msg_q_t;
其中pend_obj用于挂载等待该优先级消息队列的任务,prio_q和prio_q_mgr_array合起来实现优先级队列。
消息入队和消息出队的API实现与消息队列的实现思想是一样的,在此就不过多赘述。
下面我们来看TencentOS tiny中更重要和特殊的一种队列:邮箱队列,邮箱队列也是一种用来在任务间传递具有特定大小的内存信息的机制。
二. 邮箱队列
消息队列和邮箱队列的不同之处,在于底层队列每个元素类型不一样,看一眼源码便知:
(这是创建一个消息队列的实现)
__API__ k_err_t tos_msg_q_create(k_msg_q_t *msg_q, void *pool, size_t msg_cnt)
{
//部分源码省略
//重点:队列中每个元素类型大小是sizeof(void*)
err = tos_ring_q_create(&msg_q->ring_q, pool, msg_cnt, sizeof(void *));
if (err != K_ERR_NONE) {
return err;
}
return K_ERR_NONE;
}
消息队列传递的消息是地址,所以在初始化消息队列的时候,环形队列中每个元素都是空指针类型,而邮箱队列传递的是值,所以在初始化底层用到的环形队列时,每个元素的大小是由用户指定的,这也就是为什么邮箱队列在TencentOS-tiny中是不可或缺的:
(这是创建一个邮箱队列的实现)
__API__ k_err_t tos_mail_q_create(k_mail_q_t *mail_q, void *pool, size_t mail_cnt, size_t mail_size)
{
//省略了部分源码
//重点:每个元素的大小是mail_size,由用户传入参数指定
err = tos_ring_q_create(&mail_q->ring_q, pool, mail_cnt, mail_size);
if (err != K_ERR_NONE) {
return err;
}
return K_ERR_NONE;
}
下面解释一下这个创建函数:mail_q表示邮箱队列的句柄,pool表示邮箱队列的buffer池,mail_cnt表示邮件的最大数量,mail_size表示邮件的大小,如果调用这个函数返回一个K_ERR_NONE则表示邮箱队列创建成功,如果返回一个K_ERR_OBJ_PTR_NULL 表示mail_q指针为空。
三. 邮箱队列的其他操作
(一)动态创建邮箱队列
刚才我们在本文的第二部分中看了静态创建邮箱队列的实现,现在我们来看一下邮箱队列的另一种更加灵活地创建方式:动态创建。
直接上源码:
__API__ k_err_t tos_mail_q_create_dyn(k_mail_q_t *mail_q, size_t mail_cnt, size_t mail_size)
{
k_err_t err;
TOS_PTR_SANITY_CHECK(mail_q);
err = tos_ring_q_create_dyn(&mail_q->ring_q, mail_cnt, mail_size);
if (err != K_ERR_NONE) {
return err;
}
pend_object_init(&mail_q->pend_obj);
TOS_OBJ_INIT(mail_q, KNL_OBJ_TYPE_MAIL_QUEUE);
knl_object_alloc_set_dynamic(&mail_q->knl_obj);
return K_ERR_NONE;
}
这个创建函数的参数中,mail_q表示邮箱队列的句柄,mail_cnt表示邮件的最大数量,mail_size表示邮件的大小,和刚才说过的静态创建邮箱队列的方式在参数上就有些许不同,动态创建邮箱队列不需要传入pool参数,也就是不需要人为指定邮箱队列的buffer池,而是内部buffer由动态内存分配,需要注意的是邮件是一片固定大小的内存
当调用创建函数后,如果返回K_ERR_NONE表示邮箱队列创建成功,返回K_ERR_OBJ_PTR_NULL表示mail_q指针为空,创建K_ERR_OUT_OF_MEMORY则是堆内存不足,也就是无法动态的给邮箱队列分配内部buffer导致创建失败
(二)销毁邮箱队列
这里需要注意,刚才我们说到邮箱队列的创建方式是有静态和动态两种方式,所以在这里我们也分析这两种队列的销毁,首先来看静态创建的邮箱队列的销毁:
__API__ k_err_t tos_mail_q_destroy(k_mail_q_t *mail_q)
{
TOS_CPU_CPSR_ALLOC();
k_err_t err;
TOS_PTR_SANITY_CHECK(mail_q);
TOS_OBJ_VERIFY(mail_q, KNL_OBJ_TYPE_MAIL_QUEUE);
if (!knl_object_alloc_is_static(&mail_q->knl_obj)) {
return K_ERR_OBJ_INVALID_ALLOC_TYPE;
}
TOS_CPU_INT_DISABLE();
err = tos_ring_q_destroy(&mail_q->ring_q);
if (err != K_ERR_NONE) {
TOS_CPU_INT_ENABLE();
return err;
}
pend_wakeup_all(&mail_q->pend_obj, PEND_STATE_DESTROY);
pend_object_deinit(&mail_q->pend_obj);
TOS_OBJ_DEINIT(mail_q);
knl_object_alloc_reset(&mail_q->knl_obj);
TOS_CPU_INT_ENABLE();
knl_sched();
return K_ERR_NONE;
}
传入邮箱队列的句柄即可完成销毁,我们发现函数的本质是通过调用tos_ring_q_destroy
来完成对邮箱队列的销毁,而tos_ring_q_destroy
实际上是销毁了一个环形队列,所以说我们就此可以看出邮箱队列是基于环形队列来实现的。
当返回K_ERR_NONE表示邮箱队列销毁成功,返回K_ERR_OBJ_PTR_NULL表示mail_q指针为空,因为函数在销毁之前会通过knl_object_alloc_is_static(&mail_q->knl_obj)
对邮箱队列进行合法性检查,所以如果返回K_ERR_OBJ_INVALID表示mail_q指向的不是一个合法的邮箱队列。销毁之后调用knl_sched
再次进行内核的调度,使内核切换到其他任务中。
下面我们来看一下动态创建的邮箱队列是如何销毁的:
__API__ k_err_t tos_mail_q_destroy_dyn(k_mail_q_t *mail_q)
{
TOS_CPU_CPSR_ALLOC();
k_err_t err;
TOS_PTR_SANITY_CHECK(mail_q);
TOS_OBJ_VERIFY(mail_q, KNL_OBJ_TYPE_MAIL_QUEUE);
if (!knl_object_alloc_is_dynamic(&mail_q->knl_obj)) {
return K_ERR_OBJ_INVALID_ALLOC_TYPE;
}
TOS_CPU_INT_DISABLE();
err = tos_ring_q_destroy_dyn(&mail_q->ring_q);
if (err != K_ERR_NONE) {
TOS_CPU_INT_ENABLE();
return err;
}
pend_wakeup_all(&mail_q->pend_obj, PEND_STATE_DESTROY);
pend_object_deinit(&mail_q->pend_obj);
TOS_OBJ_DEINIT(mail_q);
knl_object_alloc_reset(&mail_q->knl_obj);
TOS_CPU_INT_ENABLE();
knl_sched();
return K_ERR_NONE;
}
通过观察,我们发现和静态创建的邮箱队列销毁方式不同的是合法性检查部分knl_object_alloc_is_dynamic(&mail_q->knl_obj)
与销毁部分tos_ring_q_destroy_dyn
,从函数的命名上我们也能看出这两个API都是具有动态的含义的,第一个是对邮箱队列进行合法性检查:
__KNL__ __STATIC_INLINE__ int knl_object_alloc_is_dynamic(knl_obj_t *knl_obj)
{
return knl_obj->alloc_type == KNL_OBJ_ALLOC_TYPE_DYNAMIC;
}
通过句柄来查看邮箱队列的alloc_type类型是否是KNL_OBJ_ALLOC_TYPE_DYNAMIC类型完成合法性检查。
另一个函数是tos_ring_q_destroy_dyn
,目的是销毁一个动态环形队列:
__API__ k_err_t tos_ring_q_destroy_dyn(k_ring_q_t *ring_q)
{
TOS_PTR_SANITY_CHECK(ring_q);
TOS_OBJ_VERIFY(ring_q, KNL_OBJ_TYPE_RING_QUEUE);
if (!knl_object_alloc_is_dynamic(&ring_q->knl_obj)) {
return K_ERR_OBJ_INVALID_ALLOC_TYPE;
}
tos_mmheap_free(ring_q->pool);
ring_q->head = 0u;
ring_q->tail = 0u;
ring_q->total = 0;
ring_q->pool = K_NULL;
ring_q->item_size = 0u;
ring_q->item_cnt = 0u;
TOS_OBJ_DEINIT(ring_q);
knl_object_alloc_reset(&ring_q->knl_obj);
return K_ERR_NONE;
}
其实我们从源码中可以看出,在销毁动态环形队列时仍然进行了合法性检查knl_object_alloc_is_dynamic(&ring_q->knl_obj),这样做是为了复用性和安全性,避免日后出错。
(三)冲洗邮箱队列
下面我们来看一下邮箱队列的冲洗,冲洗代表在不销毁队列的前提下丢弃队列中的所有邮件。
__API__ k_err_t tos_mail_q_flush(k_mail_q_t *mail_q)
{
TOS_PTR_SANITY_CHECK(mail_q);
TOS_OBJ_VERIFY(mail_q, KNL_OBJ_TYPE_MAIL_QUEUE);
return tos_ring_q_flush(&mail_q->ring_q);
}
通过源码我们可以看出本质上是通过冲洗环形队列来实现的,我们来看一下源码:
__API__ k_err_t tos_ring_q_flush(k_ring_q_t *ring_q)
{
TOS_CPU_CPSR_ALLOC();
TOS_PTR_SANITY_CHECK(ring_q);
TOS_OBJ_VERIFY(ring_q, KNL_OBJ_TYPE_RING_QUEUE);
TOS_CPU_INT_DISABLE();
ring_q->head = 0u;
ring_q->tail = 0u;
ring_q->total = 0;
TOS_CPU_INT_ENABLE();
return K_ERR_NONE;
}
其中最关键的三行代码实现了队列的冲洗,也就是:
ring_q->head = 0u;
ring_q->tail = 0u;
ring_q->total = 0;
这三行利用句柄/也就是指针,将环形队列对象k_ring_q_t的head,tail,total全部清零设为初始值完成了冲洗
以上就是本周的内容了,本周的主要内容还是集中在邮箱队列的学习上,下周我们将继续学习邮箱队列的剩余部分,以邮箱队列存取消息实现开始学习。