关于RTOS的全部真相。 第25条。 数据通道:简介和基本服务



短暂休息后,我们将继续发布Colin Walls的《关于RTOS的全部真相》的译文。 这次,我们将讨论数据传输通道(以下称为通道),在先前的一篇文章(即#5 )中已经提到过。 与邮箱( #21#22 )或队列( #23#24 )相比,通道提供了更灵活的方式来在任务之间传输简单消息。

该系列中的先前文章:

第24条。 队列:辅助服务和数据结构
第23条 队列:简介和基本服务
第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个通道。 如果在应用程序中未配置任何通道,则与该通道相关的数据结构或服务调用代码均不会包含在该应用程序中。

数据传输通道-一组存储,每个存储的大小允许您放置一个用户指定长度的数据元素(以字节为单位)。 以几种任务可以安全使用它的方式来控制对数据的访问。 任务可以将数据写入通道,直到所有区域都已满为止。 任务可以从通道读取数据,并且根据FIFO原理接收数据。 根据所选的API调用设置和Nucleus SE配置,尝试向拥挤的通道写入数据或从空通道读取数据可能会导致错误或任务挂起。

通道和队列


Nucleus SE还支持在先前文章(#23和#24)中已详细讨论的队列。 通道和队列之间的主要区别在于消息的大小。 队列包含由单个ADDR类型的变量(通常是指针)组成的消息。 该通道包含任意大小的消息,这些消息对于应用程序中的每个通道都是独立的,并在参数设置期间分配。

频道设定


通道数


与大多数Nucleus SE对象一样,通道自定义由nuse_config.h中#define指令控制 。 主要参数是NUSE_PIPE_NUMBER ,它确定在应用程序中配置的通道数。 默认情况下,该值为零(即应用程序中没有通道),并且最多可以使用16个值。不正确的值将导致编译错误,该错误将通过检入nuse_config_check.h文件(此文件包含在nuse_config.c中并生成)来生成。 ),这将触发#error指令。

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

激活API调用


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

NUSE_PIPE_SEND
NUSE_PIPE_RECEIVE
NUSE_PIPE_JAM
NUSE_PIPE_RESET
NUSE_PIPE_INFORMATION
NUSE_PIPE_COUNT

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

以下是默认nuse_config.h文件中的代码片段。

#define NUSE_PIPE_NUMBER 0 /* Number of pipes in the system - 0-16 */ /* Service call enablers */ #define NUSE_PIPE_SEND FALSE #define NUSE_PIPE_RECEIVE FALSE #define NUSE_PIPE_JAM FALSE #define NUSE_PIPE_RESET FALSE #define NUSE_PIPE_INFORMATION FALSE #define NUSE_PIPE_COUNT FALSE 

如果已激活API函数,但应用程序中没有任何通道(始终启用NUSE_Pipe_Count()除外 ,则会发生编译错误。 如果您的代码使用尚未激活的API调用,则会发生布局错误,因为实现代码未包含在应用程序中。

开销频道通话


Nucleus RTOS支持十个通道开销,这些开销提供以下功能:

  • 向频道发送消息。 Nucleus SE在NUSE_Pipe_Send()函数中实现。
  • 接收来自频道的消息。 Nucleus SE在NUSE_Pipe_Receive()函数中实现。
  • 将消息发送到频道顶部。 Nucleus SE在NUSE_Pipe_Jam()中实现
  • 通过释放所有挂起的任务(重置)将通道恢复到未使用状态。 在Nucleus SE中,在NUSE_Pipe_Reset()中实现
  • 获取有关特定频道的信息。 Nucleus SE是使用NUSE_Pipe_Information()实现的
  • 返回应用程序中当前配置的通道的计数器。 Nucleus SE是使用NUSE_Pipe_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_Pipe(NU_PIPE *管道,VOID *消息,未签名的大小,未签名的挂起);

参数:

pipe-指向用户提供的通道控制块的指针;
message-指向要发送的消息的指针;
size-消息中的字节数。 如果通道支持可变长度消息,则此参数必须等于或小于通道支持的消息长度。 如果通道支持固定长度的消息,则此参数必须等于通道支持的消息的大小;
暂停 -任务暂停的规范,可以采用值NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NU_INVALID_PIPE-指向通道的无效指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SIZE-消息大小与通道支持的消息大小不兼容;
NU_INVALID_SUSPEND-尝试从与任务无关的线程挂起;
NU_PIPE_FULL-通道已满,未指定任务挂起的类型;
NU_TIMEOUT-通道已满,即使在任务暂停了指定的时间后也是如此;
NU_PIPE_DELETED-任务挂起时删除了通道;
NU_PIPE_RESET —任务挂起时重置了通道。

呼叫以将消息发送到Nucleus SE中的频道

该API服务调用支持Nucleus RTOS API的核心功能。

服务电话原型:

状态NUSE_Pipe_Send(NUSE_PIPE管道,U8 *消息,U8挂起);

参数:

pipe-使用通道的索引(ID);
message-指向要发送的消息的指针(给定通道允许的长度字节序列);
pause-任务挂起规范,可以使用值NUSE_NO_SUSPENDNUSE_SUSPEND

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_PIPE-无效的频道索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从与任务无关的线程中挂起,或者在禁用任务锁定时挂起;
NUSE_PIPE_FULL-通道已满,未指定任务挂起的类型;
NUSE_PIPE_WAS_RESET-任务挂起时重置了通道。

在Nucleus SE中实施频道发布

根据是否激活对API调用的支持以阻止(暂停)任务,使用条件编译选择API功能代码NUSE_Pipe_Send()的版本 (检查参数之后)。 下面我们考虑这两种选择。

如果禁用了锁定,则此API调用的代码非常简单:

 if (NUSE_Pipe_Items[pipe] == NUSE_Pipe_Size[pipe]) /* pipe full */ { return_value = NUSE_PIPE_FULL; } else /* pipe element available */ { data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Head[pipe]]; for (i=0; i<msgsize; i++) { *data++ = *message++; } NUSE_Pipe_Head[pipe] += msgsize; if (NUSE_Pipe_Head[pipe] == (NUSE_Pipe_Size[pipe] * msgsize)) { NUSE_Pipe_Head[pipe] = 0; } NUSE_Pipe_Items[pipe]++; return_value = NUSE_SUCCESS; } 

该函数检查通道中是否有可用空间,并使用NUSE_Pipe_Head []索引将消息放置在通道数据区域中。

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

 do { if (NUSE_Pipe_Items[pipe] == NUSE_Pipe_Size[pipe]) /* pipe full */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_PIPE_FULL; } else { /* block task */ NUSE_Pipe_Blocking_Count[pipe]++; NUSE_Suspend_Task(NUSE_Task_Active, (pipe << 4) | NUSE_PIPE_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } else /* pipe element available */ { data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Head[pipe]]; for (i=0; i<msgsize; i++) { *data++ = *message++; } NUSE_Pipe_Head[pipe] += msgsize; if (NUSE_Pipe_Head[pipe] == (NUSE_Pipe_Size[pipe] * msgsize)) { NUSE_Pipe_Head[pipe] = 0; } NUSE_Pipe_Items[pipe]++; if (NUSE_Pipe_Blocking_Count[pipe] != 0) { U8 index; /* check whether a task is blocked on this pipe */ NUSE_Pipe_Blocking_Count[pipe]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_PIPE_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == pipe)) { 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时运行

如果通道已满,并且suspend参数为NUSE_NO_SUSPEND ,则API调用以值NUSE_PIPE_FULL结束。 如果suspend参数为NUSE_SUSPEND ,任务将暂停。 完成后(即,任务恢复时),如果返回值为NUSE_SUCCESS ,即由于读取消息(而不是因为通道已重置)而恢复了任务,则代码将返回到循环的开头。

如果通道未满,则使用NUSE_Pipe_Head []索引将提供的消息保存在通道数据区域中。 它检查通道是否已暂停任务(待处理消息)。 如果存在此类任务,则将恢复第一个任务。 变量设置为NUSE_NO_SUSPEND ,并且API调用以值NUSE_SUCCESS完成。

从频道阅读


从通道读取的Nucleus RTOS API调用非常灵活,如果无法立即完成操作(例如,当您尝试读取空通道时),则可以隐式或超时地暂停任务。 Nucleus SE具有类似的实用程序调用,但是任务暂停是可选的,并且未实现超时。

Nucleus RTOS中的频道读取挑战

服务电话原型:

状态NU_Receive_From_Pipe(NU_PIPE *管道,VOID *消息,未签名的大小,未签名的* actual_size,未签名的挂起);

参数:

pipe-指向用户提供的通道控制块的指针;
message-指向接收到的消息的存储的指针;
size-消息中的字节数。 必须与创建频道时指定的消息大小匹配;
挂起 -任务挂起规范,可以采用NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:

NU_SUCCESS-调用成功完成;
NU_INVALID_PIPE-指向通道的无效指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SUSPEND-尝试从与任务无关的线程中暂停任务;
NU_PIPE_EMPTY-通道为空,并且未指定任务挂起的类型;
NU_TIMEOUT-即使在任务暂停指定的超时值后,通道也为空;
NU_PIPE_DELETED-任务挂起时删除了通道;
NU_PIPE_RESET-任务挂起时删除了通道。

Nucleus SE中的频道阅读挑战

该API服务调用支持Nucleus RTOS API的核心功能。

服务电话原型:

状态NUSE_Pipe_Receive(NUSE_PIPE管道,U8 *消息,U8挂起);

参数:

pipe-使用通道的索引(ID);
message-指向接收到的消息的存储器的指针(字节序列,其长度与通道消息的大小一致);
suspend-任务挂起规范,可以采用NUSE_NO_SUSPENDNU_SUSPEND值。

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_PIPE-无效的频道索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从与任务无关的线程中挂起任务,或者禁用了对任务挂起的支持;
NUSE_PIPE_EMPTY-通道为空,并且未指定任务挂起的类型;
NUSE_PIPE_WAS_RESET-任务挂起时重置了通道。

在Nucleus SE中实现Feed阅读器

通过条件编译选择API函数代码NUSE_Pipe_Receive()的版本 (检查参数之后),具体取决于是否激活了对块(暂停任务)的API调用的支持。 我们将在下面考虑这两个选项。

如果禁用了锁定,则此API调用的代码非常简单:

 if (NUSE_Pipe_Items[pipe] == 0) /* pipe empty */ { return_value = NUSE_PIPE_EMPTY; } else { /* message available */ data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Tail[pipe]]; for (i=0; i<msgsize; i++) { *message++ = *data++; } NUSE_Pipe_Tail[pipe] += msgsize; if (NUSE_Pipe_Tail[pipe] == (NUSE_Pipe_Size[pipe] * msgsize)) { NUSE_Pipe_Tail[pipe] = 0; } NUSE_Pipe_Items[pipe]--; *actual_size = msgsize; return_value = NUSE_SUCCESS; } 

该函数检查通道中是否存在消息,并使用NUSE_Pipe_Tail []索引从通道数据区域中获取消息,然后通过消息指针返回数据。

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

 do { if (NUSE_Pipe_Items[pipe] == 0) /* pipe empty */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_PIPE_EMPTY; } else { /* block task */ NUSE_Pipe_Blocking_Count[pipe]++; NUSE_Suspend_Task(NUSE_Task_Active, (pipe << 4) | NUSE_PIPE_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } else { /* message available */ data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Tail[pipe]]; for (i=0; i<msgsize; i++) { *message++ = *data++; } NUSE_Pipe_Tail[pipe] += msgsize; if (NUSE_Pipe_Tail[pipe] == (NUSE_Pipe_Size[pipe] * msgsize)) { NUSE_Pipe_Tail[pipe] = 0; } NUSE_Pipe_Items[pipe]--; if (NUSE_Pipe_Blocking_Count[pipe] != 0) { U8 index; /* check whether a task is blocked */ /* on this pipe */ NUSE_Pipe_Blocking_Count[pipe]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_PIPE_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == pipe)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } *actual_size = msgsize; return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } } while (suspend == NUSE_SUSPEND); 

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

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

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

如果通道包含消息,则使用NUSE_Pipe_Tail []索引返回存储的消息。 检查此通道上是否有任何暂停(等待发送)的任务。 如果存在此类任务,则将恢复第一个任务。 将suspend变量设置为NUSE_NO_SUSPEND ,并且该API调用以NUSE_SUCCESS代码终止。

记录到频道的开头


Nucleus RTOS API实用程序调用非常灵活,可以写入通道的开头,如果无法立即完成操作(例如,尝试写入完整通道),则可以隐式或超时地暂停任务。 Nucleus SE具有类似的实用程序调用,但是任务暂停是可选的,并且未实现超时。

调用以写入Nucleus RTOS中通道的开头

服务电话原型:

状态NU_Send_To_Front_Of_Pipe(NU_PIPE *管道,VOID *消息,UNSIGNED大小,UNSIGINED挂起);

参数:

pipe-指向用户提供的通道控制块的指针;
message-指向要发送的消息的指针;
size-消息中的字节数。 如果通道支持可变长度消息,则此参数必须等于或小于通道支持的消息的大小。 如果通道支持定长消息,则此参数必须与通道支持的消息大小匹配。
挂起 -任务挂起规范,可以采用NU_NO_SUSPENDNU_SUSPEND或超时值。

返回值:

NU_SUCCESS-调用成功完成;
NU_INVALID_PIPE-指向通道的无效指针;
NU_INVALID_POINTER-消息的空指针( NULL );
NU_INVALID_SIZE-消息大小与通道支持的消息大小不兼容;
NU_INVALID_SUSPEND-尝试从与任务无关的线程挂起;
NU_PIPE_FULL-通道已满,未指定任务挂起的类型;
NU_TIMEOUT-通道已满,即使在任务暂停了指定的时间后也是如此;
NU_PIPE_DELETED-任务挂起时删除了通道;
NU_PIPE_RESET —任务挂起时重置了通道。

调用以写入Nucleus SE中通道的开头

该实用程序调用支持Nucleus RTOS API的核心功能。

服务电话原型:

状态NUSE_Pipe_Jam(NUSE_PIPE管道,ADDR *消息,U8挂起);

参数:

pipe-使用通道的索引(ID);
message-指向要发送的消息的指针,该字节的序列等于通道中已配置的消息大小;
pause-任务挂起规范,可以为NUSE_NO_SUSPENDNUSE_SUSPEND

返回值:

NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_PIPE-无效的频道索引;
NUSE_INVALID_POINTER-消息的空指针( NULL );
NUSE_INVALID_SUSPEND-尝试从与任务无关的线程中挂起任务,或者在禁用任务锁时尝试将其挂起;
NUSE_PIPE_FULL-通道已满,未指定任务挂起的类型;
NUSE_PIPE_WAS_RESET-任务挂起时重置了通道。

在Nucleus SE中实现频道条目记录

NUSE_Pipe_Jam()函数的变体代码与NUSE_Pipe_Send()非常相似,除了使用索引NUSE_Pipe_Tail []在其中存储数据外,因此:

 if (NUSE_Pipe_Items[pipe] == NUSE_Pipe_Size[pipe]) /* pipe full */ { return_value = NUSE_PIPE_FULL; } else /* pipe element available */ { if (NUSE_Pipe_Tail[pipe] == 0) { NUSE_Pipe_Tail[pipe] = (NUSE_Pipe_Size[pipe] - 1) * msgsize; } else { NUSE_Pipe_Tail[pipe] -= msgsize; } data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Tail[pipe]]; for (i=0; i<msgsize; i++) { *data++ = *message++; } NUSE_Pipe_Items[pipe]++; return_value = NUSE_SUCCESS; } 

在下一篇文章中,我们将考虑与通道相关的其他服务调用以及相应的数据结构。

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

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


All Articles