2021SC@SDUSC
一. 什么是消息队列
消息队列,Message Queue,顾名思义包含两部分:消息+队列,或者可以理解为消息的队列。
(一)什么是消息?
两个不同的任务之间传递数据时,这个数据就称之为消息,这个消息可以是一个整型值,浮点值,甚至一个结构体,一个指针。
注意:「在TencentOS-tiny中,消息队列中传递的消息指的是地址,邮箱队列传递的消息是值」
(二)什么是队列?
这点其实在之前的博客已经介绍过了,在这里简单说一下队列作为消息队列的底层和消息队列之间的具体关系:
- 消息队列如果底层使用环形队列存储消息,则成为消息队列,遵循:先送入的消息先被取出。
- 消息队列如果底层使用优先级队列存储消息,则成为优先级消息队列,遵循:优先级最高的消息最先被取出。
(三)什么是pend-post机制?
这个机制是我在网上查阅资料的时候所涉猎到的,所解决的问题就是队列满的情况下有进程要往队列中存元素和队列空的情况下有进程要从队列中取元素时候该如何处理
- 当队列「满了」的时候,前来入队的进程可以选择pend等待一段时间或者永久等待,「一旦有元素被进程出队」,调用post释放一个信号,「唤醒等待中的进程」。
- 当队列「空了」的时候,前来出队的进程可以选择pend一段时间或者永久等待,「一旦有元素被进程入队」,调用post释放一个信号,「唤醒等待中的进程」。
二. 消息队列的实现
(一)消息池的实现
我们来看源码:
首先是系统内核初始化函数tos_knl_init():
__API__ k_err_t tos_knl_init(void)
{
···
#if (TOS_CFG_MSG_EN) > 0
msgpool_init();
#endif
···
}
之后是消息池初始化函数msgpool_init():
__KERNEL__ void msgpool_init(void)
{
uint32_t i;
for (i = 0; i < TOS_CFG_MSG_POOL_SIZE; ++i) {
tos_list_init(&k_msg_pool[i].list);
tos_list_add(&k_msg_pool[i].list, &k_msg_freelist);
}
}
在系统调用tos_knl_init()函数初始化的时候,系统就会自动将消息池进行初始化,其中, msgpool_init()函数就是用来初始化消息池的,该函数的定义位于 tos_msg.c文件中,函数的实现:通过一个for循环,将消息池k_msg_pool[TOS_CFG_MSG_POOL_SIZE]的成员变量进行初始化,初始化对应的列表节点,并且将它挂载到空闲消息列表上k_msg_freelist
(二)消息队列的创建
TencentOS-tiny中消息队列的实现在文件 tos_message_queue.h
和tos_message_queue.c
中
我们来看tos_message_queue.c
文件中关于创建消息队列的函数tos_msg_queue_create()
:
__API__ k_err_t tos_msg_queue_create(k_msg_queue_t *msg_queue)
{
TOS_PTR_SANITY_CHECK(msg_queue);
#if TOS_CFG_OBJECT_VERIFY_EN > 0u
knl_object_init(&msg_queue->knl_obj, KNL_OBJ_TYPE_MSG_QUEUE);
#endif
tos_list_init(&msg_queue->queue_head);
return K_ERR_NONE;
}
msg_queue表示消息队列句柄,消息队列msg_queue与队列queue的区别在于,queue提供了一种同步等待机制。实际上,queue的底层实现采用了msg_queue的消息管理机制。当返回K_ERR_NONE表示创建消息队列成功。当返回K_ERR_OBJ_PTR_NULL 表示msg_queue是空指针。
(三)消息队列的销毁
消息队列的销毁涉及到两个函数:分别是tos_msg_queue_destroy()
和tos_msg_queue_flush()
__API__ k_err_t tos_msg_queue_destroy(k_msg_queue_t *msg_queue)
{
TOS_PTR_SANITY_CHECK(msg_queue);
#if TOS_CFG_OBJECT_VERIFY_EN > 0u
if (!knl_object_verify(&msg_queue->knl_obj, KNL_OBJ_TYPE_MSG_QUEUE)) {
return K_ERR_OBJ_INVALID;
}
#endif
tos_msg_queue_flush(msg_queue);
tos_list_init(&msg_queue->queue_head);
#if TOS_CFG_OBJECT_VERIFY_EN > 0u
knl_object_deinit(&msg_queue->knl_obj);
#endif
return K_ERR_NONE;
}
返回值K_ERR_NONE表示销毁队列成功,K_ERR_OBJ_PTR_NULL 表示msg_queue是空指针,K_ERR_OBJ_INVALID表示msg_queue指向的不是一个合法的消息队列。
__API__ void tos_msg_queue_flush(k_msg_queue_t *msg_queue)
{
TOS_CPU_CPSR_ALLOC();
k_list_t *curr, *next;
TOS_CPU_INT_DISABLE();
TOS_LIST_FOR_EACH_SAFE(curr, next, &msg_queue->queue_head) {
msgpool_free(TOS_LIST_ENTRY(curr, k_msg_t, list));
}
TOS_CPU_INT_ENABLE();
}
tos_msg_queue_destroy()
函数用于销毁一个消息队列,当消息队列不在使用是可以将其销毁,销毁的本质其实是将消息队列控制块的内容进行清除,首先判断一下消息队列控制块的类型是KNL_OBJ_TYPE_MSG_QUEUE
,这个函数只能销毁队列类型的控制块。然后调用tos_msg_queue_flush()
函数将队列的消息列表的消息全部“清空”,“清空”的意思是冲洗消息队列(复位消息队列,丢弃消息队列中的所有消息),即将挂载到队列上的消息释放回消息池(如果消息队列的消息列表存在消息,使用msgpool_free()函数释放消息),并且通过tos_list_init()
函数将消息队列的消息列表进行初始化。
knl_object_deinit()
函数是为了确保消息队列已经被销毁,此时消息队列控制块的pend_obj成员变量中的type 属性标识为KNL_OBJ_TYPE_NONE。
需要注意的是销毁队列不代表就释放了队列控制块所占的内存,因为队列控制块的内存时由编译器静态分配的
本周的内容就到这里结束了,下周我们将分析消息队列的存取消息实现过程。