当前位置:首页 > > strongerHuang
[导读]队列(queue)是一种只能在一端插入元素、在另一端删除元素的数据结构,遵循先入先出(FIFO)的规则。


转自 | Mculover666

1. 知识点回顾

队列(queue)是一种只能在一端插入元素、在另一端删除元素的数据结构,遵循先入先出(FIFO)的规则。

环形队列(ring queue)可以方便的重复利用这段内存空间,同样遵循先入先出(FIFO)的规则。

优先级队列(prio queue)不遵循FIFO,而是根据元素的优先级进行出队,优先级最高的先出队。

「本文的所有内容都是基于这两个数据结构」,TencentOS-tiny中环形队列和优先级队列的实现和使用示例请阅读文章:

  • 数据结构 | TencentOS-tiny中队列、环形队列、优先级队列的实现及使用

2. 消息队列

2.1. 什么是消息队列

消息队列,Message Queue,顾名思义包含两部分:消息+队列,或者可以理解为消息的队列。

① 消息是什么?

两个不同的任务之间传递数据时,这个数据就称之为消息,这个消息可以是一个整型值,浮点值,甚至一个结构体,一个指针……所以,在使用不同的RTOS的消息队列时,「一定要注意传递的是值还是该值的地址」

传递值的缺点是值的长度有大有小,导致整个消息队列的长度有大有小。

一个指针的长度是固定的4字节,传递值的时候,无论值是什么类型,只传递该值的地址。

传递地址当然也有缺陷,当动态任务task1中定义了一个局部变量,然后把该局部变量的地址传给了task2,随即task1因为某种原因被销毁,内存回收,导致指向该局部变量的指针变为野指针,非常危险,不过不用慌,小问题,在编程的时候注意避免即可。

「在TencentOS-tiny中,消息队列中传递的消息指的是地址,邮箱队列传递的消息是值」

② 队列是什么?

消息队列如果底层使用环形队列存储消息,则成为消息队列,遵循:先送入的消息先被取出。

消息队列如果底层使用优先级队列存储消息,则成为优先级消息队列,遵循:优先级最高的消息最先被取出。

「在TencentOS-tiny中,这两种消息队列都有,下面一一讲述。」

③ pend-post机制

无论是什么队列,都存在两种情况:当队列满了的时候,元素再入队会发生错误;当队列为空的时候,元素出队同样会发生错误。

这种问题可以巧妙的在队列基础之上用pend-post机制解决,即等待-释放机制。

当队列「满了」的时候,前来入队的task1可以选择pend一段时间或者永久等待,「一旦有元素被task2出队」,调用post释放一个信号,「唤醒等待中的task1」

同样,当队列「空了」的时候,前来出队的task1可以选择pend一段时间或者永久等待,「一旦有元素被task2入队」,调用post释放一个信号,「唤醒等待中的task1」

是不是很巧妙?

接下来上源码!上Demo!一看便知~

2.2. 消息队列的实现

TencentOS-tiny中消息队列的实现在 tos_message_queue.h和tos_message_queue.c中。

typedef struct k_message_queue_st { knl_obj_t knl_obj; pend_obj_t pend_obj; k_ring_q_t ring_q;
} k_msg_q_t;

一个pend_obj对象用来实现pend-post机制,一个ring_q环形队列用来存储消息。

是不是和我讲述的没错?学透了之后,其实一切都没有那么神秘的~

再来看看从消息队列中获取消息的API实现:

__API__ k_err_t tos_msg_q_pend(k_msg_q_t *msg_q, void **msg_ptr, k_tick_t timeout) { //省略了部分源码 TOS_CPU_INT_DISABLE(); if (tos_ring_q_dequeue(&msg_q->ring_q, msg_ptr, K_NULL) == K_ERR_NONE) {
        TOS_CPU_INT_ENABLE(); return K_ERR_NONE;
    }
    pend_task_block(k_curr_task, &msg_q->pend_obj, timeout);
    TOS_CPU_INT_ENABLE();
    knl_sched(); return err;
}

向消息队列中存放消息的API实现如下:

__STATIC__ k_err_t msg_q_do_post(k_msg_q_t *msg_q, void *msg_ptr, opt_post_t opt) { //省略了部分源码 TOS_CPU_INT_DISABLE(); if (pend_is_nopending(&msg_q->pend_obj)) {
        err = tos_ring_q_enqueue(&msg_q->ring_q, &msg_ptr, sizeof(void*)); if (err != K_ERR_NONE) {
            TOS_CPU_INT_ENABLE(); return err;
        }
        TOS_CPU_INT_ENABLE(); return K_ERR_NONE;
    } if (opt == OPT_POST_ONE) {
        msg_q_task_recv(TOS_LIST_FIRST_ENTRY(&msg_q->pend_obj.list, k_task_t, pend_list), msg_ptr);
    } else { // OPT_POST_ALL TOS_LIST_FOR_EACH_ENTRY_SAFE(task, tmp, k_task_t, pend_list, &msg_q->pend_obj.list) {
            msg_q_task_recv(task, msg_ptr);
        }
    }
    TOS_CPU_INT_ENABLE();
    knl_sched(); return K_ERR_NONE;
}

从源码中可以看到,如果opt标志为 OPT_POST_ONE,表示唤醒一个,则唤醒该消息队列等待列表上任务优先级最高的那个;如果opt标志为 OPT_POST_ALL,则全部唤醒。

2.3. 消息队列的使用示例

#define MESSAGE_MAX     10 uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)]; k_msg_q_t msg_q; void entry_task_receiver(void *arg) { k_err_t err; void *msg_received; while (K_TRUE) {
        err = tos_msg_q_pend(&msg_q, &msg_received, TOS_TIME_FOREVER); if (err == K_ERR_NONE) { printf("receiver: msg incoming[%s]\n", (char *)msg_received);
        }
    }
} void entry_task_sender(void *arg) { char *msg_prio_0 = "msg 0 without priority"; char *msg_prio_1 = "msg 1 without priority"; char *msg_prio_2 = "msg 2 without priority"; printf("sender: post a message 2 without priority\n");
    tos_msg_q_post(&msg_q, msg_prio_2); printf("sender: post a message 1 without priority\n");
    tos_msg_q_post(&msg_q, msg_prio_1); printf("sender: post a message 0 without priority\n");
    tos_msg_q_post(&msg_q, msg_prio_0);
}

执行结果如下:

TencentOS-tiny Port on STM32L431RCT6 By Mculover666
sender: post a message 2 without priority
sender: post a message 1 without priority
sender: post a message 0 without priority
receiver: msg incoming[msg 2 without priority]
receiver: msg incoming[msg 1 without priority]
receiver: msg incoming[msg 0 without priority]

3. 优先级消息队列

3.1. 优先级消息队列的实现

实现和消息队列类似,通过在优先级队列的基础上加上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实现与消息队列的实现思想一模一样,这里不再讲解。

3.2. 优先级消息队列的使用示例

#define MESSAGE_MAX     10 uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)]; k_prio_msg_q_t prio_msg_q; void entry_task_receiver(void *arg) { k_err_t err; void *msg_received; while (K_TRUE) {
        err = tos_prio_msg_q_pend(&prio_msg_q, &msg_received, TOS_TIME_FOREVER); if (err == K_ERR_NONE) { printf("receiver: msg incoming[%s]\n", (char *)msg_received);
        }
    }
} void entry_task_sender(void *arg) { char *msg_prio_0 = "msg with priority 0"; char *msg_prio_1 = "msg with priority 1"; char *msg_prio_2 = "msg with priority 2"; printf("sender: post a message with priority 2\n");
    tos_prio_msg_q_post(&prio_msg_q, msg_prio_2, 2); printf("sender: post a message with priority 1\n");
    tos_prio_msg_q_post(&prio_msg_q, msg_prio_1, 1); printf("sender: post a message with priority 0\n");
    tos_prio_msg_q_post(&prio_msg_q, msg_prio_0, 0);
}

运行结果如下:

TencentOS-tiny Port on STM32L431RCT6 By Mculover666
sender: post a message with priority 2 sender: post a message with priority 1 sender: post a message with priority 0 receiver: msg incoming[msg with priority 0]
receiver: msg incoming[msg with priority 1]
receiver: msg incoming[msg with priority 2]

将第2节的结果和第3节的结果对比,就会发现同样的消息发送顺序,因为使用不同的消息队列,任务获取到的消息顺序截然不同。

4. 邮箱队列

4.1. 不同之处

消息队列和邮箱队列的不同之处,在于底层队列每个元素类型不一样,看一眼源码便知。

消息队列传递的消息是地址,所以在初始化消息队列的时候,环形队列中每个元素都是空指针类型:

__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;
}

而邮箱队列传递的是值,所以在初始化底层用到的环形队列时,每个元素的大小是由用户指定的:

__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;
}

4.2. 邮箱队列的实现

这有什么好实现的~一个环形队列+pend-post对象即可。

TencentOS-tiny中邮箱队列的实现在tos_mail_queue.h和tos_mail_queue.c中。

typedef struct k_mail_queue_st { knl_obj_t knl_obj; pend_obj_t pend_obj; k_ring_q_t ring_q; } k_mail_q_t;

是不是没什么区别~至于操作的API,更没啥区别,不写了,划水划水。

4.3. 邮箱队列的使用示例


#define MAIL_MAX    10 typedef struct mail_st { char *message; int payload;
} mail_t; uint8_t mail_pool[MAIL_MAX * sizeof(mail_t)]; k_mail_q_t mail_q; void entry_task_receiver_higher_prio(void *arg) { k_err_t err; mail_t mail; size_t mail_size; while (K_TRUE) {
        err = tos_mail_q_pend(&mail_q, &mail, &mail_size, TOS_TIME_FOREVER); if (err == K_ERR_NONE) {
            TOS_ASSERT(mail_size == sizeof(mail_t)); printf("higher: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
        }
    }
} void entry_task_receiver_lower_prio(void *arg) { k_err_t err; mail_t mail; size_t mail_size; while (K_TRUE) {
        err = tos_mail_q_pend(&mail_q, &mail, &mail_size, TOS_TIME_FOREVER); if (err == K_ERR_NONE) {
            TOS_ASSERT(mail_size == sizeof(mail_t)); printf("lower: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
        }
    }
} void entry_task_sender(void *arg) { int i = 1; mail_t mail; while (K_TRUE) { if (i == 2) { printf("sender: send a mail to one receiver, and shoud be the highest priority one\n");
            mail.message = "1st time post";
            mail.payload = 1;
            tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
        } if (i == 3) { printf("sender: send a message to all recevier\n");
            mail.message = "2nd time post";
            mail.payload = 2;
            tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
        } if (i == 4) { printf("sender: send a message to one receiver, and shoud be the highest priority one\n");
            mail.message = "3rd time post";
            mail.payload = 3;
            tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
        } if (i == 5) { printf("sender: send a message to all recevier\n");
            mail.message = "4th time post";
            mail.payload = 4;
            tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
        }
        tos_task_delay(1000);
        ++i;
    }
}



运行结果为:


TencentOS-tiny Port on STM32L431RCT6 By Mculover666

sender: send a mail to one receiver, and shoud be the highest priority one
higher: msg incoming[1st time post], payload[1]

sender: send a message to all recevier
higher: msg incoming[2nd time post], payload[2]
lower: msg incoming[2nd time post], payload[2]

sender: send a message to one receiver, and shoud be the highest priority one
higher: msg incoming[3rd time post], payload[3]

sender: send a message to all recevier
higher: msg incoming[4th time post], payload[4]
lower: msg incoming[4th time post], payload[4]


此示例主要演示了两点:1. 如何使用邮箱队列直接传递值;2. 唤醒一个等待任务和唤醒所有等待任务的区别。

5. 优先级邮箱队列

看到这里,这个不能再讲了吧~

TencentOS-tiny中实现在tos_priority_mail_queue.c和tos_priority_mail_queue.h中。

可以自己尝试根据前面的demo,编写出一个使用优先级邮箱队列的demo,测试高优先级的邮件是否会被先收到,然后将结果与第4节的实验结果进行对比。

越到文末我越浪,划水已经不能满足了,博主要去摸鱼~

6. 总结

按照惯例,对本文所讲的内容进行一个总结。

本文主要讲述了用于任务间通信的一些内核对象,主要有四个:消息队列和优先级消息队列,邮箱队列和优先级邮箱队列。

接下来列出一些重要的点:

「在使用RTOS中的一些用于任务间通信的量时,要注意传递的是值还是地址。TencentOS-tiny中消息队列传输的是地址,而邮箱队列传递的是值。」

「消息队列和邮箱队列基于环形队列实现,遵循FIFO规则;而优先级消息队列和优先级邮箱队列基于优先级队列实现,遵循按照元素优先级取出的规则。」

最后来回答题目中的问题:任务间通信为什么不使用全局变量?

① 无论是消息队列还是邮箱队列,都是利用了全局变量可以被随意访问的特性,所以使用时都会被定义为全局变量。

② 普通全局变量可用于一些简单的任务间通信场合。

③ 相较于普通全局变量,加入队列机制可以存储多个消息,加入pend-post机制可以拥有任务等待和唤醒的机制,用于解决队列已满或队列为空的问题。

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭