关于RTOS的全部真相。 第23条 队列:简介和基本服务



在上一篇文章(第5篇)中提到了队列。 与邮箱相比,它们提供了一种更灵活的方式来在任务之间传输简单消息。

该系列中的先前文章:
第22条 邮箱:辅助服务和数据结构
第21条。 邮箱:简介和基本服务
第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类型的元素,并且可以安全地访问,以便多个任务可以使用它。 任务可以将数据写入队列,直到所有区域都已满为止。 任务可以从队列中读取数据,数据通常基于FIFO(先进先出)输入。 尝试将数据写入拥挤的队列或从空队列中读取数据会导致错误或任务暂停,具体取决于所选的API调用参数和Nucleus SE配置。

队列和数据链接


Nucleus SE支持数据通道,在上一篇文章(#5)中也提到了这一点,下面将对它们进行详细讨论。 队列和通道之间的主要区别在于消息的大小。 队列包含由ADDR类型的单个变量(通常是指针)组成的消息。 该通道包含任意大小的消息,这些消息对于应用程序中的每个通道都是独立的,并在参数设置期间分配。

队列设定


队列数


与大多数Nucleus SE对象一样,队列配置主要由nuse_config.h文件中的#define指令控制。 主要参数是NUSE_QUEUE_NUMBER ,它确定在应用程序中配置的队列数。 缺省值为零(即应用程序中没有队列),并且可以接受的值最多为16。不正确的值将导致编译期间发生错误,该错误将在nuse_config_check.h文件的验证过程中生成(包含在nuse_config.c文件中并已编译) ),这将触发#error指令。

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

激活API调用


Nucleus SE中的每个API函数(实用程序调用)在nuse_config.h中都有#define enable指令。 对于队列,这些指令是:

NUSE_QUEUE_SEND NUSE_QUEUE_RECEIVE NUSE_QUEUE_JAM NUSE_QUEUE_RESET NUSE_QUEUE_INFORMATION NUSE_QUEUE_COUNT 

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

以下是nuse_config.h文件中的代码片段:

 #define NUSE_QUEUE_NUMBER 0 /* Number of queues in the system - 0-16 */ /* Service call enablers */ #define NUSE_QUEUE_SEND FALSE #define NUSE_QUEUE_RECEIVE FALSE #define NUSE_QUEUE_JAM FALSE #define NUSE_QUEUE_RESET FALSE #define NUSE_QUEUE_INFORMATION FALSE #define NUSE_QUEUE_COUNT FALSE 

如果队列API的功能已激活,但应用程序中没有队列( NUSE_Queue_Count()始终启用),则将出现编译错误。 如果您的代码使用尚未激活的API调用,则将导致布局错误,因为实现代码未包含在应用程序中。

呼叫排队


Nucleus RTOS支持十个队列服务调用,这些调用提供以下功能:

  • 排队留言。 Nucleus SE在NUSE_Queue_Send()函数中实现。
  • 接受来自队列的消息。 Nucleus SE实现了NUSE_Queue_Receive()函数。
  • 发布到队列的开头。 在Nucleus SE中,在NUSE_Queue_Jam()中实现
  • 通过释放所有挂起的任务(重置)将队列恢复到未使用状态。 Nucleus SE在NUSE_Queue_Reset()中实现
  • 提供有关特定队列的信息。 Nucleus SE在NUSE_Queue_Information()中实现
  • 返回当前在应用程序中配置的队列数。 在Nucleus SE中,在NUSE_Queue_Count()中实现
  • 将新队列添加到应用程序(创建队列)。 未实施Nucleus SE。
  • 从应用程序中删除队列。 未实施Nucleus SE。
  • 返回指向应用程序中所有队列的指针。 未实施Nucleus SE。
  • 向队列中暂停的所有任务发送消息(广播)。 未实施Nucleus SE。

这些开销调用中的每一个的实现将在下面详细描述。

服务呼叫以从队列进行读写


在队列上执行的基本操作是写入(有时称为排队消息)和读取(也称为接收消息)。 也可以写入队列的开头(阻塞)。 Nucleus RTOS和Nucleus SE为这些操作提供了三个基本的API调用,下面将对其进行讨论。

排队


Nucleus RTOS API实用程序用于写入队列的调用非常灵活,它允许您隐式暂停任务,或者如果操作无法立即完成(例如,尝试写入完整队列时),则暂停特定的超时。 Nucleus SE提供了相同的功能,但是任务暂停是可选的,并且未实现超时。

Nucleus RTOS中的呼叫排队

服务电话原型:

状态NU_Send_To_Queue(NU_QUEUE *队列,VOID *消息,UNSIGNED大小,UNSIGNED挂起);

参数:

queue-指向用户提供的队列控制块的指针;
message-指向要发送的消息的指针;
size-消息中UNSIGNED数据元素的数量。 如果队列支持可变长度的消息,则此参数必须等于消息的大小或小于队列支持的消息的大小。 如果队列支持固定大小的消息,则此参数必须与队列支持的消息大小完全匹配;
挂起 -任务挂起的规范,可以采用值NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:

NU_SUCCESS-调用成功完成;
NU_INVALID_QUEUE-无效的队列指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SIZE-消息大小与队列支持的消息大小不兼容;
NU_INVALID_SUSPEND-暂停是从与任务无关的线程执行的;
NU_QUEUE_FULL-队列已满,未指定挂起;
NU_TIMEOUT-即使在指定的超时时间内任务被挂起,队列也已满;
NU_QUEUE_DELETED-任务挂起时删除队列;
NU_QUEUE_RESET-任务挂起时重置了队列。

在Nucleus SE中排队消息
该API服务调用支持Nucleus RTOS API的核心功能。

服务电话原型:

状态NUSE_Queue_Send(NUSE_QUEUE队列,ADDR *消息,U8挂起);

参数:

队列 - 队列索引(ID);
message-指向要发送的消息的指针,是ADDR类型的一个变量;
暂停 -暂停任务的规范;它可以采用值NUSE_NO_SUSPEND或NUSE_SUSPEND

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_QUEUE-无效的队列索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从与任务无关的线程中暂停任务,或者在禁用API服务调用以阻止任务时暂停该任务;
NUSE_QUEUE_FULL-队列已满,未指定挂起;
NUSE_QUEUE_WAS_RESET-挂起任务时重置了队列。

在Nucleus SE中实现排队
根据是否激活对任务锁定的支持,使用条件编译选择API函数代码变体NUSE_Queue_Send() (在检查参数之后)。 我们将考虑这两种选择。

如果未激活任务锁定,则此服务调用的代码非常简单:

 if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue]) /* queue full */ { return_value = NUSE_QUEUE_FULL; } else /* queue element available */ { NUSE_Queue_Data[queue][NUSE_Queue_Head[queue]++] = *message; if (NUSE_Queue_Head[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Head[queue] = 0; } NUSE_Queue_Items[queue]++; return_value = NUSE_SUCCESS; } 

该函数只是检查队列中是否有可用空间,并使用NUSE_Queue_Head []索引将消息存储在队列的数据区域中。

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

 do { if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue]) /* queue full */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_QUEUE_FULL; } else { /* block task */ NUSE_Queue_Blocking_Count[queue]++; NUSE_Suspend_Task(NUSE_Task_Active, (queue << 4) | NUSE_QUEUE_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } else { /* queue element available */ NUSE_Queue_Data[queue][NUSE_Queue_Head[queue]++] = *message; if (NUSE_Queue_Head[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Head[queue] = 0; } NUSE_Queue_Items[queue]++; if (NUSE_Queue_Blocking_Count[queue] != 0) { U8 index; /* check whether a task is blocked on this queue */ NUSE_Queue_Blocking_Count[queue]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_QUEUE_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == queue)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } } while (suspend == NUSE_SUSPEND); 

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

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

如果队列已满,并且挂起NUSE_NO_SUSPEND ,则API调用以NUSE_QUEUE_FULL结尾 。 如果suspend参数为NUSE_SUSPEND ,任务将暂停。 完成后(即,任务恢复时),如果返回值为NUSE_SUCCESS ,即由于读取了消息(而不是因为队列已重置)而恢复了任务,则代码将返回循环的开头。
如果队列未满,则使用NUSE_Queue_Head []索引将提供的消息存储在队列的数据区域中。 它检查队列中是否有挂起的任务(等待消息)。 如果存在此类任务,则将恢复第一个任务。 暂变量设置为NUSE_NO_SUSPEND ,并且API调用以值NUSE_SUCCESS完成。

从队列中读取


从队列读取的Nucleus RTOS API实用程序调用非常灵活,它允许您隐式暂停任务,或者如果无法立即完成操作(例如,尝试从空队列中读取),则暂停特定的超时。 Nucleus SE提供了相同的功能,但是任务暂停是可选的,并且未实现超时。

调用以从Nucleus RTOS中的队列接收消息
服务电话原型:

状态NU_Receive_From_Queue(NU_QUEUE *队列,VOID *消息,UNSIGNED大小,UNSIGNED * Actual_size,UNSIGNED挂起);

参数:

queue-指向用户提供的队列控制块的指针;
message-指向存储已接收消息的指针;
size-消息中UNSIGNED数据元素的数量。 此数字应与创建队列时定义的消息大小相匹配;
挂起 -任务挂起的规范,可以采用值NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:

NU_SUCCESS-调用成功完成;
NU_INVALID_QUEUE-无效的队列指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SUSPEND-尝试从与任务无关的线程中暂停任务;
NU_QUEUE_EMPTY-队列为空,未指定挂起;
NU_TIMEOUT-指示队列仍然为空,即使在任务被暂停指定的时间后也是如此;
NU_QUEUE_DELETED-任务挂起时删除队列;
NU_QUEUE_RESET-任务挂起时重置了队列。

呼叫以接收来自Nucleus SE队列的消息
该API调用支持Nucleus RTOS API的核心功能。

服务电话原型:

状态NUSE_Queue_Receive(NUSE_QUEUE队列,ADDR *消息,U8挂起);

参数:

队列 - 队列索引(ID);
message-指向接收到的消息的存储库的指针;它是ADDR类型的一个变量;
挂起 -任务挂起的规范,可以采用值NUSE_NO_SUSPENDNUSE_SUSPEND

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_QUEUE-无效的队列索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从与任务无关或禁用任务阻止支持的线程中挂起任务;
NUSE_QUEUE_EMPTY-队列为空,未指定挂起;
NUSE_QUEUE_WAS_RESET-挂起任务时重置了队列。

在Nucleus SE中从队列接收消息的实现
使用条件编译选择API函数代码变体NUSE_Queue_Receive() (在检查参数之后),具体取决于是否激活了对任务锁定的支持。 考虑这两种选择。

如果激活了锁支持,则此API调用的代码非常简单:

 if (NUSE_Queue_Items[queue] == 0) /* queue empty */ { return_value = NUSE_QUEUE_EMPTY; } else { /* message available */ *message = NUSE_Queue_Data[queue][NUSE_Queue_Tail[queue]++]; if (NUSE_Queue_Tail[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Tail[queue] = 0; } NUSE_Queue_Items[queue]--; return_value = NUSE_SUCCESS; } 

该函数只是检查队列中是否有消息,并使用NUSE_Queue_Tail []索引从队列中检索消息,并使用指向该消息的指针返回数据。

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

 do { if (NUSE_Queue_Items[queue] == 0) /* queue empty */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_QUEUE_EMPTY; } else { /* block task */ NUSE_Queue_Blocking_Count[queue]++; NUSE_Suspend_Task(NUSE_Task_Active, (queue << 4) | NUSE_QUEUE_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } else { /* message available */ *message = NUSE_Queue_Data[queue][NUSE_Queue_Tail[queue]++]; if (NUSE_Queue_Tail[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Tail[queue] = 0; } NUSE_Queue_Items[queue]--; if (NUSE_Queue_Blocking_Count[queue] != 0) { U8 index; /* check whether a task is blocked */ /* on this queue */ NUSE_Queue_Blocking_Count[queue]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_QUEUE_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == queue)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } } while (suspend == NUSE_SUSPEND); 

一些澄清将是有帮助的。

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

如果队列为空,并且挂起为NUSE_NO_SUSPEND ,则API调用以NUSE_QUEUE_EMPTY结尾 。 如果suspend参数为NUSE_SUSPEND ,任务将暂停。 完成后(即,任务恢复时),如果返回值为NUSE_SUCCESS ,即,由于已发送消息(而不是因为队列已重置)而恢复了任务,则代码将返回循环的开头。

如果队列中包含消息,则使用NUSE_Queue_Tail []索引返回存储的消息。 它检查此队列中是否有任何暂停(挂起)的任务。 如果存在此类任务,则将恢复第一个任务。 将suspend变量设置为NUSE_NO_SUSPEND ,并且该API调用以NUSE_SUCCESS代码终止。

写到行首


Nucleus RTOS API实用程序调用非常灵活,可以将消息写入队列头,并且允许您隐式暂停任务,或者如果操作无法立即完成(例如,尝试写入拥挤的队列),则暂停特定的超时。 Nucleus SE提供了相同的功能,但任务暂停是可选的,并且未实现超时。

调用以将消息写入Nucleus RTOS队列的头部
服务电话原型:

状态NU_Send_To_Front_Of_Queue(NU_QUEUE *队列,VOID *消息,UNSIGNED大小,UNSIGNED挂起);

参数:

queue-指向用户提供的队列控制块的指针;
message-指向要发送的消息的指针;
size-消息中UNSIGNED数据元素的数量。 如果队列支持可变长度消息,则此参数必须等于消息大小或小于队列支持的消息大小。 如果队列支持固定长度的消息,则此参数必须与队列支持的消息的大小完全匹配;
挂起 -任务挂起的规范,可以采用值NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:

NU_SUCCESS-调用成功完成;
NU_INVALID_QUEUE-无效的队列指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SIZE-消息大小与队列支持的消息大小不兼容;
NU_INVALID_SUSPEND-尝试从非任务流暂停
NU_QUEUE_FULL-队列已满,未指定挂起;
NU_TIMEOUT-队列已满,即使在任务暂停了一定超时后也是如此;
NU_QUEUE_DELETED-任务挂起时删除队列;
NU_QUEUE_RESET-任务挂起时重置了队列。

在Nucleus SE中将消息写入消息队列的调用
该API调用支持Nucleus RTOS API的核心功能。

服务电话原型:

状态NUSE_Queue_Jam(NUSE_QUEUE队列,ADDR *消息,U8挂起);

参数:

队列 - 队列索引(ID);
message-指向消息的指针,是ADDR类型的一个变量;
挂起 -任务挂起的规范,可以采用值NUSE_NO_SUSPENDNUSE_SUSPEND

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_QUEUE-无效的队列索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从与任务无关或禁用任务阻止支持的线程中挂起任务;
NUSE_QUEUE_FULL-队列已满,未指定挂起;
NUSE_QUEUE_WAS_RESET-挂起任务时重置了队列。

在Nucleus SE中实现队列最高记录
API函数代码变体NUSE_Queue_Jam()NUSE_Queue_Send()非常相似,仅使用NUSE_Queue_Tail []索引存储数据,因此:

 if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue]) /* queue full */ { return_value = NUSE_QUEUE_FULL; } else /* queue element available */ { if (NUSE_Queue_Tail[queue] == 0) { NUSE_Queue_Tail[queue] = NUSE_Queue_Size[queue] - 1; } else { NUSE_Queue_Tail[queue]--; } NUSE_Queue_Data[queue][NUSE_Queue_Tail[queue]] = *message; NUSE_Queue_Items[queue]++; return_value = NUSE_SUCCESS; } 

下一篇文章将研究与队列关联的其他API调用以及数据结构。

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

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


All Articles