『7x24小时有问必答』
关注+星标公众,不错过精彩内容
转自 |  Mculover666

1. 知识点回顾

队列(queue)是一种只能在一端插入元素、在另一端删除元素的数据结构,遵循先入先出(FIFO)的规则。
环形队列(ring queue)可以方便的重复利用这段内存空间,同样遵循先入先出(FIFO)的规则。
优先级队列(prio queue)不遵循FIFO,而是根据元素的优先级进行出队,优先级最高的先出队。
「本文的所有内容都是基于这两个数据结构」,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.htos_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.htos_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.htos_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.ctos_priority_mail_queue.h中。
可以自己尝试根据前面的demo,编写出一个使用优先级邮箱队列的demo,测试高优先级的邮件是否会被先收到,然后将结果与第4节的实验结果进行对比。
越到文末我越浪,划水已经不能满足了,博主要去摸鱼~

6. 总结

按照惯例,对本文所讲的内容进行一个总结。
本文主要讲述了用于任务间通信的一些内核对象,主要有四个:消息队列和优先级消息队列,邮箱队列和优先级邮箱队列。
接下来列出一些重要的点:
「在使用RTOS中的一些用于任务间通信的量时,要注意传递的是值还是地址。TencentOS-tiny中消息队列传输的是地址,而邮箱队列传递的是值。」
「消息队列和邮箱队列基于环形队列实现,遵循FIFO规则;而优先级消息队列和优先级邮箱队列基于优先级队列实现,遵循按照元素优先级取出的规则。」
最后来回答题目中的问题:任务间通信为什么不使用全局变量?
① 无论是消息队列还是邮箱队列,都是利用了全局变量可以被随意访问的特性,所以使用时都会被定义为全局变量。
② 普通全局变量可用于一些简单的任务间通信场合。
③ 相较于普通全局变量,加入队列机制可以存储多个消息,加入pend-post机制可以拥有任务等待和唤醒的机制,用于解决队列已满或队列为空的问题。
------------  END  ------------

推荐阅读:

迎关注我的公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。

欢迎关注我的视频号:

点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码