关于RTOS的全部真相。 第21条。 邮箱:简介和基本服务



以前的一篇文章(第5章)中提到了邮箱。 它们是Nucleus SE支持的第二种最简单的信号间任务通信方法,并提供了一种低成本且灵活的方式来在任务之间传输简单消息。

该系列中的先前文章:
第20条 信号量:辅助服务和数据结构
第十九条 信号灯:简介和基本服务
第十八条 事件标志组:助手服务和数据结构
第十七条 事件标志组:简介和基本服务
第十六条 讯号
第十五条 内存分区:服务和数据结构
第十四条 内存部分:简介和基本服务
第十三条 任务数据结构和不受支持的API调用
第十二条 任务处理服务
第11条 任务:API的配置和介绍
第10条 计划程序:高级功能和上下文保留
第9条。 调度程序:实施
第8条 Nucleus SE:内部设计和部署
第7条 Nucleus SE:简介
第6条。 其他RTOS服务
第5条 任务交互和同步
第4条 任务,上下文切换和中断
第3条 任务与计划
第2条。 RTOS:结构和实时模式
第1条 RTOS:简介。

使用邮箱


在Nucleus SE中,邮箱是在构建阶段定义的。 一个应用程序最多可以有16个邮箱。 如果应用程序没有邮箱,则与邮箱关联的服务呼叫代码和数据结构将不包括在应用程序中。

邮箱只是一个存储数据的地方,其大小足以存储ADDR类型的变量,并以几种任务可以使用它的方式来控制对其的安全访问。 任务可以将数据发送到邮箱。 结果,邮箱将变满,并且在任何任务执行读取邮箱的操作或邮箱为空之前,没有任务将无法向其发送数据。 尝试将数据发送到完整邮箱或尝试读取空邮箱将导致错误或任务暂停,具体取决于所选的API调用设置和Nucleus SE配置。

邮箱和队列


在OS的某些实现中,没有实现邮箱,但是作为替代方案,建议使用队列。 这听起来合乎逻辑,因为这样的队列将提供与邮箱相同的功能。 但是,队列是一个更复杂的数据结构,并承载更多的辅助数据,代码,并具有更长的服务时间。

在Nucleus SE中,就像在Nucleus RTOS中一样,您可以选择任何这些类型的对象。
如果您的应用程序有多个队列和一个邮箱,则考虑用队列替换邮箱是有意义的。 这将稍微增加开销,但是将摆脱与邮箱关联的所有API代码。 或者,您可以使用这两种方法配置应用程序,并比较数据量和性能。

队列将在以后的文章中讨论。

配置邮箱


邮箱数


与大多数Nucleus SE对象一样,邮箱配置主要由nuse_config.h文件中的#define指令指定。 主要参数是NUSE_MAILBOX_NUMBER ,它确定应用程序中的邮箱数。 默认值为零(即没有邮箱),最多可以接受16个值。不正确的值将导致编译期间出错,这将通过检入nuse_config_check.h生成(它包含在文件nuse_config.c中并随其一起编译) ),这将触发#error指令。

选择非零值是邮箱的主要激活器。 定义数据结构时使用此参数,其大小取决于其值(在下一篇文章中将对此进行更多说明)。 此外,非零值会激活API设置。

激活API调用


Nucleus SE中的每个API函数(实用程序调用) 都由nuse_config.h文件中的#define指令激活。 对于邮箱,这些指令是:

NUSE_MAILBOX_SEND NUSE_MAILBOX_RECEIVE NUSE_MAILBOX_RESET NUSE_MAILBOX_INFORMATION NUSE_MAILBOX_COUNT 

默认情况下,它们设置为FALSE ,从而禁用所有服务调用并阻止包含实现它们的代码。 要在应用程序中配置邮箱,您需要选择必要的API调用并将其设置为TRUE

以下是nuse_config.h文件中的代码部分。

 /* Number of mailboxes in the system - 0-16 */ #define NUSE_MAILBOX_NUMBER 0 /* Service call enablers: */ #define NUSE_MAILBOX_SEND FALSE #define NUSE_MAILBOX_RECEIVE FALSE #define NUSE_MAILBOX_RESET FALSE #define NUSE_MAILBOX_INFORMATION FALSE #define NUSE_MAILBOX_COUNT FALSE 

如果邮箱API功能已激活,并且应用程序中没有邮箱(始终启用NUSE_Mailbox_Count()除外),则会发生编译错误。 如果您的代码使用尚未激活的API调用,则会发生布局错误,因为实现代码未包含在应用程序中。

邮箱服务电话


Nucleus RTOS支持与邮箱关联的九个服务调用,并提供以下功能:

  • 将消息发送到邮箱。 Nucleus SE在NUSE_Mailbox_Send()函数中实现。
  • 从邮箱中读取邮件。 Nucleus SE实现了NUSE_Mailbox_Receive()函数。
  • 通过释放所有挂起的任务(重置)将邮箱还原到未使用状态。 在Nucleus SE中,在NUSE_Mailbox_Reset()中实现
  • 提供有关特定邮箱的信息。 Nucleus SE在NUSE_Mailbox_Information()中实现
  • 返回当前在应用程序中配置的邮箱数。 在Nucleus SE中,在NUSE_Mailbox_Count()中实现
  • 添加一个新邮箱(创建)。 未实施Nucleus SE。
  • 删除邮箱。 未实施Nucleus SE。
  • 返回指向应用程序中所有邮箱的指针。 未实施Nucleus SE。
  • 向邮箱中暂停的所有任务发送消息(广播)。 未实施Nucleus SE。

详细考虑每个服务调用的实现。

邮箱读写服务呼叫


邮箱上可以执行的基本操作是写入和读取数据(发送和接收)。 Nucleus RTOS和Nucleus SE为这些操作提供了两个基本的API调用,下面将对其进行介绍。

写信箱


调用Nucleus RTOS API来写入邮箱非常灵活,如果您无法立即完成操作(例如,当您尝试写入完整的邮箱时),则可以隐式或超时地暂停任务。 Nucleus SE提供了类似的服务调用,仅任务暂停是可选的,并且未实现超时。

Nucleus RTOS还提供了将数据广播到邮箱的服务调用; Nucleus SE不支持此调用,将在下一篇文章的“未实现的API调用”部分中进行描述。

调用以写入Nucleus RTOS中的邮箱
服务电话原型:
状态NU_Send_To_Mailbox(NU_MAILBOX *邮箱,VOID *消息,未签名挂起);

参数:
邮箱 -指向邮箱的指针;
message-指向要发送的消息的指针,由四个unsigned类型的元素组成;
暂停 -任务暂停的规范,可以采用值NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:
NU_SUCCESS-调用成功完成;
NU_INVALID_MAILBOX-无效的邮箱指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SUSPEND-尝试从与任务无关的线程挂起;
NU_MAILBOX_FULL-邮箱已满,但未指定挂起类型;
NU_TIMEOUT-邮箱仍然满,即使在指定时间段内挂起也是如此;
NU_MAILBOX_DELETED-任务挂起时邮箱已删除;
NU_MAILBOX_WAS_RESET-挂起任务时重置邮箱。

调用以写入Nucleus SE中的邮箱
该API调用支持Nucleus RTOS API的核心功能。

服务电话原型:
状态NUSE_Mailbox_Send(NUSE_MAILBOX邮箱,ADDR *消息,U8挂起);

参数:
邮箱 - 邮箱索引(ID);
message-指向要发送的消息的指针;它是ADDR类型的一个变量;
挂起 -任务挂起的规范,可以采用值NUSE_NO_SUSPENDNUSE_SUSPEND

返回值:
NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_MAILBOX-无效的邮箱索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从不相关的线程挂起或停用API调用阻止功能;
NUSE_MAILBOX_FULL-邮箱已满,但未指定挂起类型;
NUSE_MAILBOX_WAS_RESET-在挂起任务时重置了邮箱。

在Nucleus SE中实现邮箱条目
使用条件编译选择API函数代码NUSE_Mailbox_Send()的版本 (检查参数之后),具体取决于是否激活了对API调用的支持以阻止(暂停任务)。 考虑这两种选择。

如果禁用了任务锁定,则此API调用的逻辑非常简单,并且代码无需任何解释:

 if (NUSE_Mailbox_Status[mailbox]) /* mailbox full */ { return_value = NUSE_MAILBOX_FULL; } else /* mailbox empty */ { NUSE_Mailbox_Data[mailbox] = *message; NUSE_Mailbox_Status[mailbox] = TRUE; return_value = NUSE_SUCCESS; } 

该消息存储在相应的元素NUSE_Mailbox_Data []中 ,并且该邮箱被标记为已使用。

如果激活了任务锁定,则代码将变得更加复杂:

 do { if (!NUSE_Mailbox_Status[mailbox]) /* mailbox empty */ { NUSE_Mailbox_Data[mailbox] = *message; NUSE_Mailbox_Status[mailbox] = TRUE; if (NUSE_Mailbox_Blocking_Count[mailbox] != 0) { U8 index; /* check whether a task is blocked */ /* on this mailbox */ NUSE_Mailbox_Blocking_Count[mailbox]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_MAILBOX_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == mailbox)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } else /* mailbox full */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_MAILBOX_FULL; } else { /* block task */ NUSE_Mailbox_Blocking_Count[mailbox]++; NUSE_Suspend_Task(NUSE_Task_Active, (mailbox << 4) | NUSE_MAILBOX_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } } while (suspend == NUSE_SUSPEND); 

一些解释可能会有所帮助。

该代码包含在do ... while循环中 ,该循环suspend参数为NUSE_SUSPEND时运行

如果邮箱为空,则会记录该消息,并且邮箱的状态将更改为指示已满。 检查此邮箱上是否有暂停的任务(正在等待读取)。 如果存在此类任务,则将恢复第一个任务。 暂变量设置为NUSE_NO_SUSPEND ,API调用终止并返回NUSE_SUCCESS

如果邮箱已满,并且挂起是NUSE_NO_SUSPEND ,则API调用将返回NUSE_MAILBOX_FULL 。 如果暂停为NUSE_SUSPEND ,则任务暂停。 函数完成后(例如,当任务恢复时),如果返回值为NUSE_SUCCESS (表示由于已读取消息而恢复了任务,而不是邮箱已重置),则循环从头开始。

阅读邮箱


用于读取邮箱的Nucleus RTOS API实用程序调用非常灵活,如果无法立即完成操作(例如,从空邮箱中读取),则可以隐式或超时地暂停任务。 Nucleus SE提供了类似的服务,仅任务挂起是可选的,并且未实现超时。

调用以在Nucleus RTOS中读取邮箱
服务电话原型:
状态NU_Receive_From_Mailbox(NU_MAILBOX *邮箱,VOID *消息,未签名挂起);

参数:
邮箱 -指向用户提供的邮箱控制单元的指针;
message-指向接收到的消息的存储器的指针,其大小等于四个unsigned类型的变量;
挂起 -任务挂起规范,可以采用值NUSE_NO_SUSPENDNUSE_SUSPEND或超时值。

返回值:
NU_SUCCESS-调用成功完成;
NU_INVALID_MAILBOX-无效的邮箱指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SUSPEND-尝试从不正确的流中挂起(与该流的任务无关);
NU_MAILBOX_EMPTY-邮箱为空,未指定挂起类型;
NU_TIMEOUT-邮箱仍然为空,即使在任务暂停了指定的超时值之后也是如此;
NU_MAILBOX_DELETED-任务挂起时邮箱已删除;
NU_MAILBOX_WAS_RESET-挂起任务时重置邮箱。

致电阅读Nucleus SE中的邮箱
该API调用支持Nucleus RTOS API的核心功能。

服务电话原型:
状态NUSE_Mailbox_Receive(NUSE_MAILBOX邮箱,ADDR *消息,U8挂起);

参数:
邮箱 - 邮箱索引(ID);
message-指向存储收到的消息的指针,是ADDR类型的一个变量;
挂起 -任务挂起的规范,可以采用值NUSE_NO_SUSPENDNUSE_SUSPEND

返回值:
NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_MAILBOX-无效的邮箱索引;
NUSE_INDALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从不正确的流中挂起,或者在禁用API调用以阻止任务时挂起;
NUSE_MAILBOX_EMPTY-邮箱为空,并且未指定任务挂起的类型;
NUSE_MAILBOX_WAS_RESET-在挂起任务时重置了邮箱。

在Nucleus SE中实现邮箱阅读器
使用条件编译来选择API功能代码NUSE_Mailbox_Receive()的版本 (检查参数之后),具体取决于是否激活了API调用以进行阻塞(挂起任务)。 我们将考虑这两种选择。

如果禁用了任务锁定,则此API调用的逻辑非常简单,并且代码无需任何解释:

 if (!NUSE_Mailbox_Status[mailbox]) /* mailbox empty */ { return_value = NUSE_MAILBOX_EMPTY; } else { /* mailbox full */ *message = NUSE_Mailbox_Data[mailbox]; NUSE_Mailbox_Status[mailbox] = FALSE; return_value = NUSE_SUCCESS; } 

从相应的NUSE_Mailbox_Data []元素读取该消息,并且该邮箱被标记为空。

如果激活了任务锁定,则代码将变得更加复杂:

 do { if (NUSE_Mailbox_Status[mailbox]) /* mailbox full */ { *message = NUSE_Mailbox_Data[mailbox]; NUSE_Mailbox_Status[mailbox] = FALSE; if (NUSE_Mailbox_Blocking_Count[mailbox] != 0) { U8 index; /* check whether a task is blocked */ /* on this mailbox */ NUSE_Mailbox_Blocking_Count[mailbox]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_MAILBOX_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == mailbox)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } else /* mailbox empty */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_MAILBOX_EMPTY; } else { /* block task */ NUSE_Mailbox_Blocking_Count[mailbox]++; NUSE_Suspend_Task(NUSE_Task_Active, (mailbox << 4) | NUSE_MAILBOX_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } } while (suspend == NUSE_SUSPEND); 

一些澄清可能会有所帮助。

该代码包含在do ... while循环中 ,该循环suspend参数为NUSE_SUSPEND时运行

如果邮箱已满,则将返回存储的消息,并且邮箱的状态将更改为指示它为空。 检查此邮箱上是否有暂停的任务(正在等待记录)。 如果存在此类任务,则将恢复第一个任务。 暂变量设置为NUSE_NO_SUSPEND ,API调用终止并返回NUSE_SUCCESS

如果邮箱为空,并且suspend参数为NUSE_NO_SUSPEND ,则API调用将返回NUSE_MAILBOX_EMPTY 。 如果暂停为NUSE_SUSPEND ,则任务暂停。 如果在调用结束时返回值为NUSE_SUCCESS ,这表明由于已发送消息而恢复了任务(而不是邮箱已重置),则循环从头开始。

下面的文章将查看与邮箱关联的其他API调用以及相应的数据结构。

关于作者: Colin Walls在电子行业工作了30多年,大部分时间用于固件。 他现在是Mentor Embedded(Mentor Graphics的一个部门)的固件工程师。 Colin Walls经常在会议和研讨会上发表演讲,他撰写了许多技术文章并撰写了两本有关固件的书。 居住在英国。 Colin的专业博客 ,电子邮件:colin_walls@mentor.com。

Source: https://habr.com/ru/post/zh-CN430856/


All Articles