0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

关于RTOS任务间通信和全局变量之间的区别解析

strongerHuang 来源:CSDN技术社区 作者:Mculover666 2021-04-19 09:36 次阅读

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]

”, (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

”);

tos_msg_q_post(&msg_q, msg_prio_2);

printf(“sender: post a message 1 without priority

”);

tos_msg_q_post(&msg_q, msg_prio_1);

printf(“sender: post a message 0 without priority

”);

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]

”, (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

”);

tos_prio_msg_q_post(&prio_msg_q, msg_prio_2, 2);

printf(“sender: post a message with priority 1

”);

tos_prio_msg_q_post(&prio_msg_q, msg_prio_1, 1);

printf(“sender: post a message with priority 0

”);

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]

”, 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]

”, 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

”);

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

”);

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

”);

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

”);

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机制可以拥有任务等待和唤醒的机制,用于解决队列已满或队列为空的问题。
编辑:lyn

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • fifo
    +关注

    关注

    3

    文章

    387

    浏览量

    43635
  • RTOS
    +关注

    关注

    22

    文章

    811

    浏览量

    119570
  • 消息队列
    +关注

    关注

    0

    文章

    33

    浏览量

    2972

原文标题:RTOS任务间通信和全局变量有什么区别?

文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    freertos和rtos区别是什么

    FreeRTOS 和 RTOS(实时操作系统)是两个不同的概念,但它们之间有紧密的联系。FreeRTOS 是一个特定的开源实时操作系统,而 RTOS 是实时操作系统的一般概念。 概念定义 RT
    的头像 发表于 09-02 14:18 1138次阅读

    LABVIEW调用DLL,DLL中包含全局变量不识别的问题

    头文件中写法如上,.cpp文件中写法如下 导入DLL时,错误如下 这个报错就很没有道理 我在同样的文件中按同样的写法,写一个add(a,b,c)函数,同样写全局变量的话,它就不会报这样的错,所以我可以排除是头文件或者预处理定义的问题。 很头疼,有没有大神指导一下。
    发表于 05-31 09:37

    建立更多的全局变量的时候,如何使得PROGRAM SIZE不增大呢?

    今天发现,建立更多的全局变量的时候,PROGRAM SIZE同时也增大了,如何使得PROGRAM SIZE不增大呢?我对全局变量的初始化无要求。
    发表于 05-15 06:30

    COSMIC在外部中断中修改全局变量后,发现在主程序中,修改的值又变回来了,为什么?

    我用的COSMIC,在外部中断中修改全局变量后,发现在主程序中,修改的值又变回来了(比如说我想计数外部中断的次数)。这是怎么回事?而我在定时中断中却可以修改全局变量
    发表于 05-13 08:45

    使用IAR定义全局变量出现两个同名不同地址变量是什么原因导致的?

    使用IAR定义全局变量出现两个同名不同地址变量 systickCount和systickFlag都在另一个c文件里定义的,假设a.c,然后在a.h里声明为外部变量,main.c
    发表于 05-10 06:09

    请问ucos中全局变量OSTime最终能累加到多少呢?

    在ucos-ii 中全局变量 OSTime 总是++请问最终能累加到多少呢? 若加到65530后 会自动归零吗?
    发表于 05-09 06:22

    请问stm32程序中如何优化大量的编译开关和全局变量

    刚接手一个程序,发现里面存在大量的编译开关和定义了大量的全局变量,感觉这些显得很是臃肿,有什么方法可以优化一下这些编译开关和全局变量全局变量是一个个的标志位,有时候还会有条件嵌套。
    发表于 05-06 06:35

    你是不是也没躲过这个坑?用了太多全局变量......

    全局变量太多有哪些弊端?该如何规避,以及如何管理全局变量等。一、全局变量太多有哪些弊端?真正做过项目的同学应该都能明白,项目中全局变量太多,会存在很多问题。这里给大家罗列一些太多
    的头像 发表于 05-01 08:10 519次阅读
    你是不是也没躲过这个坑?用了太多<b class='flag-5'>全局变量</b>......

    STM8L使用中全局变量自动更改是怎么回事?

    问题是这样的,我在使用STM8L的时候,定义了一个全局变量A,只在初始化的时候赋了一个初值A=5,在整个程序生命过程中,没有任何一个地方改变这个初值。目前遇到在程序运行中,读出的这个A的值为0,请问是否可以确定为内存溢出或者其他什么问题 有遇到类似情况的吗,求指导
    发表于 04-28 06:03

    全局变量太多有哪些弊端?

    随着全局变量的增多,不同模块的变量名可能会产生冲突或混淆,导致代码难以理解和维护。同时,全局变量使得代码中的依赖关系变得复杂,难以追踪和理解。这增加了新开发人员的学习成本,也增加了修改和调试的难度。
    发表于 04-24 09:15 869次阅读

    求助,关于FreeRTOS的相关疑问求解

    1.最近在学习FreeRTOS(stm32下),虽然好像知道了队列和信号量是用来做任务之间通信的,但是不太理解为什么要用这些东西,我觉得好像用rtos的队列和信号量要实现的功能,我定
    发表于 04-24 07:08

    请问ModusToolbox下针对CYW20719B2编程,能否指定全局变量地址?

    请问ModusToolbox 下针对CYW20719B2编程,能否指定全局变量地址? 谢谢
    发表于 03-01 11:13

    请问core2里的程序可以直接使用core1里的全局变量吗?

    如题,core2里的程序可以直接使用core1里的全局变量吗?就是不同核之前可以直接通信吗?是否还需要配置一些东西才能实现核通信
    发表于 02-20 08:05

    Tc38x中全局变量访问只能是不带cache的地址吗?

    全局变量只能放置在不带cache的地址么?
    发表于 02-06 08:17

    使用Tasking编译器生成的map文件中找不到静态全局变量的地址怎么解决 ?

    使用Tasking编译器生成的map文件中找不到静态全局变量的地址(变量在函数中已经使用),请问怎么解决
    发表于 02-06 07:21