2.3 任务间通信

2.3.1 互斥量

概述

互斥量又称互斥锁,一般用于共享资源的互斥排他性访问保护。

互斥量在任意时刻处于且仅会处于解锁或锁定状态,当一个任务获取到一把锁后(互斥量锁定),其他任务再尝试获得这把锁时会失败或进入阻塞状态,当该任务释放持有的锁时(互斥量解锁),会唤醒一个正阻塞等待此互斥量的任务,被唤醒的任务将会获取这把锁。

在多任务运行环境中,有些共享资源不具有多线程可重入性,对于这类不希望被多任务同时访问的资源(临界资源),可以采用互斥量来进行保护,后面的编程实例章节会演示这一编程范式。

API讲解
编程实例

1、在tos_config.h中,配置互斥量组件开关TOS_CFG_MUTEX_EN:

#define TOS_CFG_MUTEX_EN 1u

2、编写main.c示例代码:

  1. #include "tos.h"
  2. #include "mcu_init.h"
  3.  
  4. #define STK_SIZE_TASK_WRITER 512
  5. #define STK_SIZE_TASK_READER 512
  6.  
  7. k_stack_t stack_task_writer[STK_SIZE_TASK_WRITER];
  8. k_stack_t stack_task_reader[STK_SIZE_TASK_READER];
  9.  
  10. k_task_t task_writer;
  11. k_task_t task_reader;
  12.  
  13. extern void entry_task_writer(void *arg);
  14. extern void entry_task_reader(void *arg);
  15.  
  16. k_mutex_t critical_resource_locker;
  17.  
  18. // 一片临界区内存
  19. static uint32_t critical_resource[3];
  20.  
  21. static void write_critical_resource(int salt)
  22. {
  23. size_t i = 0;
  24. // 此函数每次向共享内存中按递增顺序写入三个无符号整数
  25. printf("writting critical resource:\n");
  26. for (i = 0; i < 3; ++i) {
  27. printf("%d\t", salt + i);
  28. critical_resource[i] = salt + i;
  29. }
  30. printf("\n");
  31. }
  32.  
  33. void entry_task_writer(void *arg)
  34. {
  35. size_t salt = 0;
  36. k_err_t err;
  37.  
  38. while (K_TRUE) {
  39. // 在向临界区写入数据之前,先尝试获取临界区保护锁
  40. err = tos_mutex_pend(&critical_resource_locker);
  41. if (err == K_ERR_NONE) {
  42. // 成功获取锁之后,向临界区写入数据
  43. write_critical_resource(salt);
  44. // 写完数据后,释放互斥锁
  45. tos_mutex_post(&critical_resource_locker);
  46. }
  47. tos_task_delay(1000);
  48. ++salt;
  49. }
  50. }
  51.  
  52. static void read_critical_resource(void)
  53. {
  54. size_t i = 0;
  55.  
  56. // 从临界区读取数据
  57. printf("reading critical resource:\n");
  58. for (i = 0; i < 3; ++i) {
  59. printf("%d\t", critical_resource[i]);
  60. }
  61. printf("\n");
  62. }
  63.  
  64. void entry_task_reader(void *arg)
  65. {
  66. k_err_t err;
  67.  
  68. while (K_TRUE) {
  69. // 读取临界区数据之前,先尝试获取临界区保护锁
  70. err = tos_mutex_pend(&critical_resource_locker);
  71. if (err == K_ERR_NONE) {
  72. // 成功获取锁之后,从临界区读取数据
  73. read_critical_resource();
  74. // 读取数据完毕后,释放互斥锁
  75. tos_mutex_post(&critical_resource_locker);
  76. }
  77. tos_task_delay(1000);
  78. }
  79. }
  80.  
  81. int main(void)
  82. {
  83. board_init();
  84. tos_knl_init();
  85. // 创建临界区保护互斥锁
  86. tos_mutex_create(&critical_resource_locker);
  87. (void)tos_task_create(&task_writer, "writer", entry_task_writer, NULL,
  88. 4, stack_task_writer, STK_SIZE_TASK_WRITER, 0);
  89. (void)tos_task_create(&task_reader, "reader", entry_task_reader, NULL,
  90. 4, stack_task_reader, STK_SIZE_TASK_READER, 0);
  91. tos_knl_start();
  92. }
运行效果

writting critical resource:0 1 2reading critical resource:0 1 2writting critical resource:1 2 3reading critical resource:1 2 3writting critical resource:2 3 4reading critical resource:2 3 4writting critical resource:3 4 5reading critical resource:3 4 5writting critical resource:4 5 6reading critical resource:4 5 6writting critical resource:5 6 7reading critical resource:5 6 7writting critical resource:6 7 8reading critical resource:6 7 8writting critical resource:7 8 9reading critical resource:7 8 9

实例代码

2.3.2 信号量

概述

信号量是一种实现任务间同步的机制,一般用于多个任务间有限资源竞争访问。

通常来说,一个信号量中持有一个整形数值,用以表示可用资源的数量。当一个信号量的可用资源数量大于0时,任务尝试获取该信号量成功,信号量的可用资源数减一;当一个信号量的可用资源数等于0时,任务尝试获取该信号量失败或进入阻塞状态。信号量的这一模式,当可用资源数为1时,可将其用于资源的互斥访问;或者解决生产者-消费者问题中的资源生产-消费问题。编程实例章节会演示生产者-消费者问题的解决范式。

API讲解
编程实例

1、在tos_config.h中,配置信号量组件开关TOS_CFG_SEM_EN:

#define TOS_CFG_SEM_EN 1u

2、编写main.c示例代码:

  1. #include "tos.h"
  2. #include "mcu_init.h"
  3.  
  4. #define STK_SIZE_TASK_PRODUCER 512
  5. #define STK_SIZE_TASK_CONSUMER 512
  6.  
  7. k_stack_t stack_task_producer[STK_SIZE_TASK_PRODUCER];
  8. k_stack_t stack_task_consumer[STK_SIZE_TASK_CONSUMER];
  9.  
  10. k_task_t task_producer;
  11. k_task_t task_consumer;
  12.  
  13. extern void entry_task_producer(void *arg);
  14. extern void entry_task_consumer(void *arg);
  15.  
  16. k_mutex_t buffer_locker;
  17. k_sem_t full;
  18. k_sem_t empty;
  19.  
  20. #define RESOURCE_COUNT_MAX 3
  21.  
  22. struct resource_st {
  23. int cursor;
  24. uint32_t buffer[RESOURCE_COUNT_MAX];
  25. } resource = { 0, {0} };
  26.  
  27. static void produce_item(int salt)
  28. {
  29. printf("produce item:\n");
  30.  
  31. printf("%d", salt);
  32. resource.buffer[resource.cursor++] = salt;
  33. printf("\n");
  34. }
  35.  
  36. void entry_task_producer(void *arg)
  37. {
  38. size_t salt = 0;
  39. k_err_t err;
  40.  
  41. while (K_TRUE) {
  42. err = tos_sem_pend(&empty, TOS_TIME_FOREVER);
  43. if (err != K_ERR_NONE) {
  44. continue;
  45. }
  46. err = tos_mutex_pend(&buffer_locker);
  47. if (err != K_ERR_NONE) {
  48. continue;
  49. }
  50.  
  51. produce_item(salt);
  52.  
  53. tos_mutex_post(&buffer_locker);
  54. tos_sem_post(&full);
  55. tos_task_delay(1000);
  56. ++salt;
  57. }
  58. }
  59.  
  60. static void consume_item(void)
  61. {
  62. printf("cosume item:\n");
  63. printf("%d\t", resource.buffer[--resource.cursor]);
  64. printf("\n");
  65. }
  66.  
  67. void entry_task_consumer(void *arg)
  68. {
  69. k_err_t err;
  70.  
  71. while (K_TRUE) {
  72. err = tos_sem_pend(&full, TOS_TIME_FOREVER);
  73. if (err != K_ERR_NONE) {
  74. continue;
  75. }
  76. tos_mutex_pend(&buffer_locker);
  77. if (err != K_ERR_NONE) {
  78. continue;
  79. }
  80.  
  81. consume_item();
  82.  
  83. tos_mutex_post(&buffer_locker);
  84. tos_sem_post(&empty);
  85. tos_task_delay(2000);
  86. }
  87. }
  88.  
  89. int main(void)
  90. {
  91. board_init();
  92. tos_knl_init();
  93. tos_mutex_create(&buffer_locker);
  94. tos_sem_create(&full, 0);
  95. tos_sem_create(&empty, RESOURCE_COUNT_MAX);
  96. (void)tos_task_create(&task_producer, "producer", entry_task_producer, NULL,
  97. 4, stack_task_producer, STK_SIZE_TASK_PRODUCER, 0);
  98. (void)tos_task_create(&task_consumer, "consumer", entry_task_consumer, NULL,
  99. 4, stack_task_consumer, STK_SIZE_TASK_CONSUMER, 0);
  100. tos_knl_start();
  101. }
运行效果

produce iterm:0cosume iterm:0produce iterm:1produce iterm:2cosume iterm:2produce iterm:3produce iterm:4cosume iterm:4produce iterm:5cosume iterm:5produce iterm:6cosume iterm:6produce iterm:7cosume iterm:7produce iterm:8cosume iterm:8produce iterm:9cosume iterm:9produce iterm:10

实例代码

2.3.3 事件

概述

事件提供了一种任务间实现同步和信息传递的机制。一般来说,一个事件中包含了一个旗标,这个旗标的每一位表示一个“事件”。

一个任务可以等待一个或者多个“事件”的发生,其他任务在一定的业务条件下可以通过写入特定“事件”唤醒等待此“事件”的任务,实现一种类似信号的编程范式。

API讲解
编程实例

1、在tos_config.h中,配置事件组件开关TOS_CFG_EVENT_EN:

#define TOS_CFG_EVENT_EN 1u

2、编写main.c示例代码:

  1. #include "tos.h"
  2. #include "mcu_init.h"
  3.  
  4. #define STK_SIZE_TASK_LISTENER 512
  5. #define STK_SIZE_TASK_TRIGGER 512
  6.  
  7. k_stack_t stack_task_listener1[STK_SIZE_TASK_LISTENER];
  8. k_stack_t stack_task_listener2[STK_SIZE_TASK_LISTENER];
  9. k_stack_t stack_task_trigger[STK_SIZE_TASK_TRIGGER];
  10.  
  11. k_task_t task_listener1;
  12. k_task_t task_listener2;
  13. k_task_t task_trigger;
  14.  
  15. extern void entry_task_listener1(void *arg);
  16. extern void entry_task_listener2(void *arg);
  17. extern void entry_task_trigger(void *arg);
  18.  
  19. const k_event_flag_t event_eeny = (k_event_flag_t)(1 << 0);
  20. const k_event_flag_t event_meeny = (k_event_flag_t)(1 << 1);
  21. const k_event_flag_t event_miny = (k_event_flag_t)(1 << 2);
  22. const k_event_flag_t event_moe = (k_event_flag_t)(1 << 3);
  23.  
  24. k_event_t event;
  25.  
  26. void entry_task_listener1(void *arg)
  27. {
  28. k_event_flag_t flag_match;
  29. k_err_t err;
  30.  
  31. while (K_TRUE) {
  32. // 此任务监听四个事件,因为使用了TOS_OPT_EVENT_PEND_ALL选项,因此必须是四个事件同时到达此任务才会被唤醒
  33. err = tos_event_pend(&event, event_eeny | event_meeny | event_miny | event_moe,
  34. &flag_match, TOS_TIME_FOREVER, TOS_OPT_EVENT_PEND_ALL | TOS_OPT_EVENT_PEND_CLR);
  35. if (err == K_ERR_NONE) {
  36. printf("entry_task_listener1:\n");
  37. printf("eeny, meeny, miny, moe, they all come\n");
  38. }
  39. }
  40. }
  41.  
  42. void entry_task_listener2(void *arg)
  43. {
  44. k_event_flag_t flag_match;
  45. k_err_t err;
  46.  
  47. while (K_TRUE) {
  48. // 此任务监听四个事件,因为使用了TOS_OPT_EVENT_PEND_ANY选项,因此四个事件任意一个到达(包括四个事件同时到达)任务就会被唤醒
  49. err = tos_event_pend(&event, event_eeny | event_meeny | event_miny | event_moe,
  50. &flag_match, TOS_TIME_FOREVER, TOS_OPT_EVENT_PEND_ANY | TOS_OPT_EVENT_PEND_CLR);
  51. if (err == K_ERR_NONE) {
  52. printf("entry_task_listener2:\n");
  53. // 有事件到达,判断具体是哪个事件
  54. if (flag_match == event_eeny) {
  55. printf("eeny comes\n");
  56. }
  57. if (flag_match == event_meeny) {
  58. printf("meeny comes\n");
  59. }
  60. if (flag_match == event_miny) {
  61. printf("miny comes\n");
  62. }
  63. if (flag_match == event_moe) {
  64. printf("moe comes\n");
  65. }
  66. if (flag_match == (event_eeny | event_meeny | event_miny | event_moe)) {
  67. printf("all come\n");
  68. }
  69. }
  70. }
  71. }
  72.  
  73. void entry_task_trigger(void *arg)
  74. {
  75. int i = 1;
  76.  
  77. while (K_TRUE) {
  78. if (i == 2) {
  79. printf("entry_task_trigger:\n");
  80. printf("eeny will come\n");
  81. // 发送eeny事件,task_listener2会被唤醒
  82. tos_event_post(&event, event_eeny);
  83. }
  84. if (i == 3) {
  85. printf("entry_task_trigger:\n");
  86. printf("meeny will come\n");
  87. // 发送eeny事件,task_listener2会被唤醒
  88. tos_event_post(&event, event_meeny);
  89. }
  90. if (i == 4) {
  91. printf("entry_task_trigger:\n");
  92. printf("miny will come\n");
  93. // 发送eeny事件,task_listener2会被唤醒
  94. tos_event_post(&event, event_miny);
  95. }
  96. if (i == 5) {
  97. printf("entry_task_trigger:\n");
  98. printf("moe will come\n");
  99. // 发送eeny事件,task_listener2会被唤醒
  100. tos_event_post(&event, event_moe);
  101. }
  102. if (i == 6) {
  103. printf("entry_task_trigger:\n");
  104. printf("all will come\n");
  105. // 同时发送四个事件,因为task_listener1的优先级高于task_listener2,因此这里task_listener1会被唤醒
  106. tos_event_post(&event, event_eeny | event_meeny | event_miny | event_moe);
  107. }
  108. tos_task_delay(1000);
  109. ++i;
  110. }
  111. }
  112.  
  113. int main(void)
  114. {
  115. board_init();
  116. tos_knl_init();
  117. tos_event_create(&event, (k_event_flag_t)0u);
  118. // 这里task_listener1的优先级比task_listener2高,因此在task_trigger发送所有事件时,task_listener1会被唤醒
  119. // 读者可以尝试将task_listener1优先级修改为5(比task_listener2低),此设置下,在task_trigger发送所有事件时,task_listener2将会被唤醒。
  120. (void)tos_task_create(&task_listener1, "listener1", entry_task_listener1, NULL,
  121. 3, stack_task_listener1, STK_SIZE_TASK_LISTENER, 0);
  122. (void)tos_task_create(&task_listener2, "listener2", entry_task_listener2, NULL,
  123. 4, stack_task_listener2, STK_SIZE_TASK_LISTENER, 0);
  124. (void)tos_task_create(&task_trigger, "trigger", entry_task_trigger, NULL,
  125. 4, stack_task_trigger, STK_SIZE_TASK_TRIGGER, 0);
  126. tos_knl_start();
  127. }
运行效果

entry_task_trigger:eeny will comeentry_task_listener2:eeny comesentry_task_trigger:meeny will comeentry_task_listener2:meeny comesentry_task_trigger:miny will comeentry_task_listener2:miny comesentry_task_trigger:moe will comeentry_task_listener2:moe comesentry_task_trigger:all will comeentry_task_listener1:eeny, meeny, miny, moe, they all come

实例代码

2.3.4 队列

概述

队列提供了一种任务间实现同步和数据传递的机制。事件只能用于任务间传递某类“事件”是否发生的信号,无法传递更为复杂的数据,队列弥补了事件的这一不足,可以在任务间传递不定长度的消息。

API讲解
编程实例

1、在tos_config.h中,配置队列组件开关TOS_CFG_QUEUE_EN:

#define TOS_CFG_QUEUE_EN 1u

2、在tos_config.h中,配置消息队列组件开关TOS_CFG_MSG_EN:

#define TOS_CFG_MSG_EN 1u

3、编写main.c示例代码:

  1. #include "tos.h"
  2. #include "mcu_init.h"
  3.  
  4. #define STK_SIZE_TASK_RECEIVER 512
  5. #define STK_SIZE_TASK_SENDER 512
  6.  
  7. #define PRIO_TASK_RECEIVER_HIGHER_PRIO 4
  8. #define PRIO_TASK_RECEIVER_LOWER_PRIO (PRIO_TASK_RECEIVER_HIGHER_PRIO + 1)
  9.  
  10. k_stack_t stack_task_receiver_higher_prio[STK_SIZE_TASK_RECEIVER];
  11. k_stack_t stack_task_receiver_lower_prio[STK_SIZE_TASK_RECEIVER];
  12. k_stack_t stack_task_sender[STK_SIZE_TASK_SENDER];
  13.  
  14. k_task_t task_receiver_higher_prio;
  15. k_task_t task_receiver_lower_prio;
  16. k_task_t task_sender;
  17.  
  18. k_queue_t queue;
  19.  
  20. extern void entry_task_receiver_higher_prio(void *arg);
  21. extern void entry_task_receiver_lower_prio(void *arg);
  22. extern void entry_task_sender(void *arg);
  23.  
  24. void entry_task_receiver_higher_prio(void *arg)
  25. {
  26. k_err_t err;
  27. void *msg_received;
  28. size_t msg_size;
  29.  
  30. while (K_TRUE) {
  31. err = tos_queue_pend(&queue, &msg_received, &msg_size, TOS_TIME_FOREVER);
  32. if (err == K_ERR_NONE) {
  33. printf("entry_task_receiver_higher_prio:\n");
  34. printf("message body: %s\n", (char *)msg_received);
  35. printf("message size: %d\n", msg_size);
  36. }
  37. }
  38. }
  39.  
  40. void entry_task_receiver_lower_prio(void *arg)
  41. {
  42. k_err_t err;
  43. void *msg_received;
  44. size_t msg_size;
  45.  
  46. while (K_TRUE) {
  47. err = tos_queue_pend(&queue, &msg_received, &msg_size, TOS_TIME_FOREVER);
  48. if (err == K_ERR_NONE) {
  49. printf("entry_task_receiver_lower_prio:\n");
  50. printf("message body: %s\n", (char *)msg_received);
  51. printf("message size: %d\n", msg_size);
  52. }
  53. }
  54. }
  55.  
  56. void entry_task_sender(void *arg)
  57. {
  58. int i = 1;
  59. char *msg_to_one_receiver = "message for one receiver[with highest priority]";
  60. char *msg_to_all_receiver = "message for all receivers";
  61.  
  62. // 此任务不断通过队列queue发送消息
  63. while (K_TRUE) {
  64. if (i == 2) {
  65. printf("entry_task_sender:\n");
  66. printf("send a message to one receiver, and shoud be the highest priority one\n");
  67. // 发送消息并唤醒一个等待任务,唤醒的应该是等待任务中优先级最高的
  68. tos_queue_post(&queue, msg_to_one_receiver, strlen(msg_to_one_receiver));
  69. }
  70. if (i == 3) {
  71. printf("entry_task_sender:\n");
  72. printf("send a message to all recevier\n");
  73. // 发送消息并唤醒所有正在等待的任务
  74. tos_queue_post_all(&queue, msg_to_all_receiver, strlen(msg_to_all_receiver));
  75. }
  76. if (i == 4) {
  77. printf("entry_task_sender:\n");
  78. printf("send a message to one receiver, and shoud be the highest priority one\n");
  79. // 发送消息并唤醒一个等待任务,唤醒的应该是等待任务中优先级最高的
  80. tos_queue_post(&queue, msg_to_one_receiver, strlen(msg_to_one_receiver));
  81. }
  82. if (i == 5) {
  83. printf("entry_task_sender:\n");
  84. printf("send a message to all recevier\n");
  85. // 发送消息并唤醒所有正在等待的任务
  86. tos_queue_post_all(&queue, msg_to_all_receiver, strlen(msg_to_all_receiver));
  87. }
  88. tos_task_delay(1000);
  89. ++i;
  90. }
  91. }
  92.  
  93. int main(void)
  94. {
  95. board_init();
  96. tos_knl_init();
  97. tos_queue_create(&queue);
  98. // task_receiver_higher_prio任务的优先级较高
  99. (void)tos_task_create(&task_receiver_higher_prio, "receiver_higher_prio",
  100. entry_task_receiver_higher_prio, NULL, PRIO_TASK_RECEIVER_HIGHER_PRIO,
  101. stack_task_receiver_higher_prio, STK_SIZE_TASK_RECEIVER, 0);
  102. // task_receiver_lower_prio任务的优先级较低
  103. (void)tos_task_create(&task_receiver_lower_prio, "receiver_lower_prio",
  104. entry_task_receiver_lower_prio, NULL, PRIO_TASK_RECEIVER_LOWER_PRIO,
  105. stack_task_receiver_lower_prio, STK_SIZE_TASK_RECEIVER, 0);
  106. (void)tos_task_create(&task_sender, "sender", entry_task_sender, NULL,
  107. 4, stack_task_sender, STK_SIZE_TASK_SENDER, 0);
  108. tos_knl_start();
  109. }
运行效果

entry_task_trigger:send a message to one receiver, and shoud be the highest priority oneentry_task_receiver_higher_prio:message body: message for one receiver[with highest priority]message size: 47entry_task_trigger:send a message to all recevierentry_task_receiver_higher_prio:message body: message for all receiversmessage size: 25entry_task_receiver_lower_prio:message body: message for all receiversmessage size: 25entry_task_trigger:send a message to one receiver, and shoud be the highest priority oneentry_task_receiver_higher_prio:message body: message for one receiver[with highest priority]message size: 47entry_task_trigger:send a message to all recevierentry_task_receiver_higher_prio:message body: message for all receiversmessage size: 25entry_task_receiver_lower_prio:message body: message for all receiversmessage size: 25

实例代码