2021SC@SDUSC TencentOS Tiny源码分析(六) 队列模块二

2021SC@SDUSC

一. TencentOS-tiny中实现队列的数据结构

这部分主要讲一下TencentOS-tiny中实现队列的一些数据结构及其他相关知识

(一)队列控制块

TencentOS tiny 通过队列控制块操作队列,在TencentOS tiny中的数据类型为k_queue_t,队列控制块由多个元素组成,主要有 pend_obj_t 类型的pend_obj以及k_msg_queue_t类型的msg_queue消息列表

其实整个队列的实现非常简单,主要靠msg_queue中的queue_head成员变量(这其实是一个消息列表(消息链表)),所有的消息都会被记录在这个消息列表中,当读取消息的时候,会从消息列表读取消息。下面看一下源码中的这些变量是如何定义的:

下面这个是继承自内核对象的数据结构(\kernel\core\include\tos_pend.h 的 35 行)

typedef struct pend_object_st {
    pend_type_t     type;
    k_list_t        list;
} pend_obj_t;

下面这个是消息列表的数据类型(消息队列控制块)(在 \kernel\core\include\tos_msg.h 文件的第 13 行)

typedef struct k_msg_queue_st {
#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    knl_obj_t       knl_obj;
#endif

    k_list_t        queue_head;
} k_msg_queue_t;

下面这个就是我们说的队列控制块的代码实现了( \kernel\core\include\tos_queue.h 文件的第 6 行)

typedef struct k_queue_st {
    pend_obj_t      pend_obj;
    k_msg_queue_t msg_queue;
} k_queue_t;

(二)消息控制块

除了队列控制块外,还有消息控制块,这是因为TencentOS tiny中实现队列是依赖消息队列的,既然队列可以传递数据(消息),则必须存在一种可以存储消息的数据结构,即消息控制块。

消息控制块中记录了消息的存储地址msg_addr,以及消息的大小msg_size,此外还存在一个list成员变量,可以将消息挂载到队列的消息列表中。下面来看源码实现:

消息控制块数据结构(\kernel\core\include\tos_msg.h 文件的第 7 行)

typedef struct k_message_st {
    k_list_t        list;
    void           *msg_addr;
    size_t          msg_size;
} k_msg_t;

(三)进程控制块中的消息成员变量

假设进程A正在队列中等待消息,此时中断或其他进程往进程A等待的队列写入(发送)一个消息,那么这个消息不会被挂载到队列的消息列表中(为提升效率而跳过这个步骤),而是会直接被记录在进程A的进程控制块中,表示进程A从队列中等到了这个消息,因此进程控制块必须也存在一些成员变量用于记录消息相关信息(如消息地址、消息大小等):

进程控制块数据结构(\kernel\core\include\tos_task.h文件的第 90 行)

typedef struct k_task_st {
···
#if TOS_CFG_MSG_EN > 0u
    void               *msg_addr;           /**< 保存接收到的消息地址 */
    size_t              msg_size;			/**< 保存接收到的消息大小 */
#endif
···
} k_task_t;

(四)与消息相关的宏定义

#define TOS_CFG_QUEUE_EN				1u

#define TOS_CFG_MSG_EN					1u

#define TOS_CFG_MSG_POOL_SIZE           3u

tos_config.h文件中,队列组件的宏定义TOS_CFG_QUEUE_EN,消息队列组件宏定义TOS_CFG_MSG_EN,系统支持的消息池中消息的个数宏定义TOS_CFG_MSG_POOL_SIZE

(五)消息池

在TencentOS tiny中定义了一个数组**k_msg_pool[TOS_CFG_MSG_POOL_SIZE]**作为消息池,它的数据类型是消息控制块类型k_msg_t,因为在使用消息队列的时候存取消息比较频繁,而在系统初始化的时候就将这个大数组的各个元素串初始化,并挂载到空闲消息列表中k_msg_freelist,组成我们说的消息池k_msg_pool,而池中的成员变量就是我们所说的消息。

为什么用池化的方式处理信息?

因为高效,复用率高,就像我们在池塘中去一勺水,在使用完毕再将其归还到池塘,这种操作是非常高效的,并且在有限资源的嵌入式中能将资源重复有效地利用起来。

消息池相关的定义(\kernel\core\tos_global.c文件 第 51 行)

#if TOS_CFG_MSG_EN > 0u
TOS_LIST_DEFINE(k_msg_freelist);

k_msg_t             k_msg_pool[TOS_CFG_MSG_POOL_SIZE];

#endif

二. 队列的创建

前面在讲环形队列和优先级队列时,已经提到过这两种队列的创建,现在再说一下最基本的这种队列的创建,下面来看创建的源码:

(kernel\core\tos_queue.c 第 5 行)

__API__ k_err_t tos_queue_create(k_queue_t *queue)
{
    TOS_PTR_SANITY_CHECK(queue);

    pend_object_init(&queue->pend_obj, PEND_TYPE_QUEUE);
    tos_msg_queue_create(&queue->msg_queue);
    return K_ERR_NONE;
}

正如源码中我们见到的:tos_queue_create()函数用于创建一个队列。

每创建一个新的队列都需要为其分配RAM,在创建的时候我们需要自己定义一个队列控制块,其内存是由编译器自动分配的。

在创建的过程中实际上就是将队列控制块的内容进行初始化,将队列控制块的 pend_obj成员变量中的type 属性标识为PEND_TYPE_QUEUE,标识这是一个队列,然后调用消息队列中的API函数tos_msg_queue_create()将队列的消息成员变量msg_queue初始化,实际上就是初始化消息列表。

三. 队列清空

清空队列实际上就是将消息释放回消息池中,本质上还是调用tos_msg_queue_flush()函数。我们可以直接看tos_msg_queue_flush()函数的实现:

(\kernel\core\tos_queue.c 第 41 行)

__API__ k_err_t tos_queue_flush(k_queue_t *queue)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(queue);

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&queue->pend_obj, PEND_TYPE_QUEUE)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();
    tos_msg_queue_flush(&queue->msg_queue);
    TOS_CPU_INT_ENABLE();

    return K_ERR_NONE;
}

四. 队列销毁

在这之前我没提到过队列的销毁,现在来补充一下队列的销毁。

但其实队列销毁的过程中是内含刚才说的队列清空步骤的,先来看一下队列销毁的源码,我们通过分析源码来理解队列销毁的过程:

(\kernel\core\tos_queue.c 第 14 行)

__API__ k_err_t tos_queue_destroy(k_queue_t *queue)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(queue);

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&queue->pend_obj, PEND_TYPE_QUEUE)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (!pend_is_nopending(&queue->pend_obj)) {
        pend_wakeup_all(&queue->pend_obj, PEND_STATE_DESTROY);
    }

    pend_object_deinit(&queue->pend_obj);
    tos_msg_queue_flush(&queue->msg_queue);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

tos_queue_destroy()函数用于销毁一个队列,当队列不在使用是可以将其销毁,销毁的本质其实是将队列控制块的内容进行清除,首先判断一下队列控制块的类型是PEND_TYPE_QUEUE,这个函数只能销毁队列类型的控制块

然后判断是否有进程在等待队列中的消息,如果有则调用pend_wakeup_all()函数将这项进程唤醒,然后调用tos_msg_queue_flush()函数将队列的消息列表的消息全部“清空”,“清空”的意思是将挂载到队列上的消息释放回消息池(如果队列的消息列表存在消息,使用msgpool_free()函数释放消息)

knl_object_deinit()函数是为了确保队列已经被销毁,此时队列控制块的pend_obj成员变量中的type 属性标识为KNL_OBJ_TYPE_NONE。最后在销毁队列后进行一次进程调度,以切换进程(刚刚可能唤醒了进程)。

需要注意的是:因为队列控制块的RAM是由编译器静态分配的,所以即使是销毁了队列,这个内存也是没办法释放的

本周的内容就到此为止了,下周我们将以如何从队列中读消息和往队列里写消息为开始来分析…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值