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

2021SC@SDUSC

一. 队列是什么

队列(queue)是一种遵循「先入先出」(FIFO)的规则并且只能在一端插入元素、在另一端删除元素的数据结构。TencentOS tiny暂时不支持后进先出原则LIFO操作队列,但是支持后进先出LIFO操作消息队列。

首先需要说明的是,一般我们认为队列即为消息队列,但是在TencentOS tiny中是将这两个概念分开的,消息队列是队列的底层。

队列是一种常用于进程间通信的数据结构,队列可以在进程与进程间中断和进程间传递消息,实现了进程能够接收来自其他进程或中断的不固定长度的消息。

进程能够从队列里面读取消息,当队列中的消息是空时,读取消息的进程将被阻塞。进程阻塞的方式会有三种,下面我们会说到,其中一种比较常用的就是用户可以指定进程等待消息的时间timeout,在这段时间中,如果队列为空,该进程将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的进程会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,进程也会自动从阻塞态转为就绪态。

(一)异步通信方式

队列就是一种异步的通信方式。那么什么是异步的通信方式?异步通信中,接收方不知道数据(消息)什么时候会到达(队列中),收发双发有各自自己的时钟,发送方发送的时间间隔可以不均,接收方是在数据的起始位和停止位的帮助下实现信息同步的。

(二)队列的实现

队列通过两个指针来实现入队和出队操作:

  • 队头指针:永远指向此队列的第一个数据元素;
  • 队尾指针:永远指向此队列的最后一个数据元素;

那这两个指针是如何实现入队、出队两种操作的呢?

  • 入队:将一个元素添加到队尾,并将队尾指针+1后移
  • 出队:将从队头中取出一个元素,并将队头指针+1后移

(三)队列的存储

队列的数据存储方式有两种:

  • 基于静态连续内存(数组)存储
  • 基于动态内存(链表节点)存储

二. TencentOS-tiny中两种特殊队列的实现

(一)环形队列

TencentOS-tiny中环形队列的实现在tos_ring_queue.htos_ring_queue.c中,代码及注释如下:

​ (实现队列的结构体定义)

typedef struct k_ring_queue_st {
    knl_obj_t   knl_obj;

    uint16_t    head; //队头指针
    uint16_t    tail; //队尾指针
    size_t      total; //记录队列中元素的个数

    uint8_t    *pool; //队列底层的存储结构(一个数组)

    size_t      item_size; //队列中每个元素的大小,单位:字节
    size_t      item_cnt; //队列中可以容纳的元素数量
} k_ring_q_t;

​ (创建并初始化环形队列,将队头指针和队尾置0)

 __API__ k_err_t tos_ring_q_create(k_ring_q_t *ring_q, void *pool, size_t item_cnt, size_t item_size)
{
 //省略了参数合法性检查代码
    ring_q->head        = 0u;
    ring_q->tail        = 0u;
    ring_q->total       = 0;

    ring_q->pool        = (uint8_t *)pool;
    ring_q->item_size   = item_size;
    ring_q->item_cnt    = item_cnt;

    return K_ERR_NONE;
}

​ (判断环形队列是否为满或者是否为空)

__API__ int tos_ring_q_is_empty(k_ring_q_t *ring_q)
{
    TOS_CPU_CPSR_ALLOC();
    int is_empty = K_FALSE;

    //省略了参数合法性检查代码
    TOS_CPU_INT_DISABLE();
    is_empty = (ring_q->total == 0 ? K_TRUE : K_FALSE);
    TOS_CPU_INT_ENABLE();

    return is_empty;
}

__API__ int tos_ring_q_is_full(k_ring_q_t *ring_q)
{
    TOS_CPU_CPSR_ALLOC();
    int is_full = K_FALSE;

  //省略了参数合法性检查代码
    TOS_CPU_INT_DISABLE();
    is_full = (ring_q->total == ring_q->item_cnt ? K_TRUE : K_FALSE);
    TOS_CPU_INT_ENABLE();

    return is_full;
}

​ (环形队列入队操作)

__API__ k_err_t tos_ring_q_enqueue(k_ring_q_t *ring_q, void *item, size_t item_size);
__STATIC_INLINE__ void ring_q_item_increase(k_ring_q_t *ring_q)
{
    ring_q->tail = RING_NEXT(ring_q, ring_q->tail);
    ++ring_q->total;
}

​ (环形队列出队操作)

__API__ k_err_t tos_ring_q_dequeue(k_ring_q_t *ring_q, void *item, size_t *item_size);
__STATIC_INLINE__ void ring_q_item_decrease(k_ring_q_t *ring_q)
{
    ring_q->head = RING_NEXT(ring_q, ring_q->head);
    --ring_q->total;
}

在入队和出队操作的时候都使用了 RING_NEXT 这个宏

作用:用来获取在环形队列中的下一个位置:

#define RING_NEXT(ring_q, index)    ((index + 1) % ring_q->item_cnt)

(二)优先级队列

优先级队列也是一种基于队列的数据结构,但是它**「不遵循FIFO」,而是按照每个元素的优先级进行出队:「最高优先级的先出队」**。

TencentOS-tiny中环形队列的实现在tos_prio_queue.htos_prio_queue.c中。

优先级队列在数据入队的时候,会按照入队元素的优先级进行一次排序「将优先级值最小(优先级最高的元素)放在队头」,出队的时候只需要取第一个元素即可。

正是因为这种特性(入队前排序),优先级队列的底层存储结构不能使用数组(排序太麻烦),而是使用了二项堆的数据结构

二项堆是什么?

二项堆是二项树的集合或是由一组二项树组成。二项堆具有良好的性质。在 O ( l o g 2 n ) O(log_2n) O(log2n)的时间内即可完成两个二项堆合并操作,所以二项堆是可合并堆,而仅仅需要 O ( 1 ) O(1) O(1)的时间,二项堆即可完成插入操作。因此,基于二项堆实现的优先队列和进程调度算法有着很好的时间性能。同时由于二项树的结构特性和性质,二项树在网络优化等诸多领域也应用广泛。

总之,二项堆的优势就是可以快速合并两个堆,快速合并两个优先级队列。

源码分析:

​ (创建优先级队列)

__API__ k_err_t tos_prio_q_create(k_prio_q_t *prio_q, void *mgr_array, void *pool, size_t item_cnt, size_t item_size);
参数描述
prio_q优先级队列控制块指针
mgr_array提供一块缓冲区用于内部管理
pool队列的缓冲区
item_cnt队列可容纳的元素数量
item_size每个元素的大小,单位字节

​ (优先级队列入队操作)

__API__ k_err_t tos_prio_q_enqueue(k_prio_q_t *prio_q, void *item, size_t item_size, k_prio_t prio);

遵循数值越小,优先级越高

​ (优先级队列出队操作)

__API__ k_err_t tos_prio_q_dequeue(k_prio_q_t *prio_q, void *item, size_t *item_size, k_prio_t *prio);

prio需要传入一个地址,用于记录出队元素的优先级

三. 总结

① 普通队列是一种只能在一端入队,在一端出队的数据结构,规则:FIFO。

② 环形队列对内存空间的利用率最高,使用最多,规则:FIFO。

③ 优先级队列不遵循FIFO,每个元素都有自己的优先级,规则:优先级最高的元素先出队。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值