任务间通信和同步有三种广泛的范式:
任务拥有的设施 ——RTOS 赋予提供通信(输入)设施的任务的属性。我们将再看的例子是信号。
内核对象 ——由 RTOS 提供的工具,代表独立的通信或同步工具。示例包括:事件标志、邮箱、队列/管道、信号量和互斥体。
消息传递 ——一种合理化的方案,其中 RTOS 允许创建消息对象,这些对象可以从一个任务发送到另一个任务或多个其他任务。这是内核设计的基础,并导致将此类产品描述为“消息传递 RTOS”。
适合每种应用的设施会有所不同。它们的功能也有一些重叠,一些关于可扩展性的思考是值得的。例如,如果一个应用程序需要多个队列,但只需要一个邮箱,那么实现具有单项队列的邮箱可能会更高效。这个对象会有点不理想,但所有邮箱处理代码都不会包含在应用程序中,因此,可伸缩性将减少 RTOS 内存占用。
共享变量或内存区域
任务间通信的一种简单方法是只拥有所有相关任务都可以访问的变量或内存区域。虽然它非常原始,但这种方法可能适用于某些应用程序。需要控制访问。如果变量只是一个字节,那么对它的写入或读取可能是“原子”(即不可中断)操作,但如果处理器允许对内存字节进行其他操作,则需要小心,因为它们可能是可中断的并且可能会导致时间问题。实现锁定/解锁的一种方法是在短时间内禁用中断。
如果您正在使用内存区域,当然您仍然需要锁定。使用第一个字节作为锁定标志是可能的,假设内存体系结构促进对该字节的原子访问。一个任务将数据加载到内存区域,设置标志,然后等待它清除。另一个任务等待设置标志,读取数据并清除标志。使用中断禁用作为锁定不太明智,因为移动整个数据缓冲区可能需要时间。
这种类型的共享内存使用方式类似于在多核系统中实现许多处理器间通信设施的方式。在某些情况下,硬件锁和/或中断被合并到处理器间共享存储器接口中。
信号
信号可能是传统 RTOS 中提供的最简单的任务间通信工具。它们由一组位标志组成——可能有 8、16 或 32 个,具体取决于具体实现——与特定任务相关联。
任何任务都可以使用 OR 类型的操作设置一个信号标志(或多个标志)。只有拥有信号的任务才能读取它们。读取过程通常是破坏性的——即标志也被清除。
在某些系统中,信号以更复杂的方式实现,以便在设置任何信号标志时自动执行由信号拥有任务指定的特殊功能。这消除了任务监控标志本身的必要性。这有点类似于中断服务程序。
在以后的文章中将有更多关于信号的信息,其中描述了它们在 Nucleus SE 中的实现。
事件标志组
事件标志组类似于信号,因为它们是面向位的任务间通信设施。它们可以类似地以 8、16 或 32 位的组来实现。它们与信号的不同之处在于它们是独立的内核对象;它们不“属于”任何特定任务。
任何任务都可以使用 OR 和 AND 操作设置和清除事件标志。同样,任何任务都可以使用相同类型的操作询问事件标志。在许多 RTOS 中,可以对事件标志组合进行阻塞 API 调用;这意味着任务可能会暂停,直到设置了特定的事件标志组合。当询问事件标志时,还可能有一个“使用”选项可用,以便清除所有读取标志。
在以后的文章中提供有关事件标志组的更多信息,其中描述了它们在 Nucleus SE 中的实现。
信号
量 信号量是独立的内核对象,它提供了一种标记机制,通常用于控制对资源的访问。大致有两种类型:二进制信号量(只有两种状态)和计数信号量(具有任意数量的状态)。一些处理器支持便于轻松实现二进制信号量的(原子)指令。二进制信号量也可以被视为计数限制为 1 的计数信号量。
任何任务都可能尝试获取信号量以获取对资源的访问权。如果当前信号量值大于0,则获取成功,信号量值递减。在许多操作系统中,可以通过阻塞调用来获取信号量;这意味着一个任务可能会被挂起,直到另一个任务释放信号量。任何任务都可以释放一个信号量,这会增加它的值。
在以后的文章中有更多关于信号量的信息,其中描述了它们在 Nucleus SE 中的实现。
邮箱
邮箱是独立的内核对象,它为任务提供了一种传输消息的方法。消息大小取决于实现,但通常是固定的。一到四个指针大小的项目是典型的消息大小。通常,指向一些更复杂数据的指针是通过邮箱发送的。一些内核实现了邮箱,因此数据只存储在一个常规变量中,内核管理对它的访问。邮箱也可以称为“交换”,尽管这个名字现在已经不常见了。
任何任务都可以发送到邮箱,然后邮箱已满。如果一个任务然后尝试发送到一个完整的邮箱,它将收到一个错误响应。在许多 RTOS 中,可以进行阻塞调用以发送到邮箱;这意味着一个任务可能会被挂起,直到邮箱被另一个任务读取。任何任务都可以从邮箱中读取,这会再次使其为空。如果任务尝试从空邮箱读取,它将收到错误响应。在许多 RTOS 中,可以进行阻塞调用以读取邮箱;这意味着一个任务可能会被挂起,直到邮箱被另一个任务填满。
一些 RTOS 支持“广播”功能。这使消息能够发送到当前在读取特定邮箱时暂停的所有任务。
某些 RTOS 根本不支持邮箱。建议改为使用单条目队列(见下文)。这在功能上是等效的,但会带来额外的内存和运行时开销。
在以后的文章中会提供有关邮箱的更多信息,该文章描述了它们在 Nucleus SE 中的实现。
队列
队列是独立的内核对象,它为任务提供了一种传输消息的方法。它们比邮箱更灵活、更复杂。消息大小取决于实现,但通常是固定大小和面向字/指针的。
任何任务都可能发送到队列,并且这可能会重复发生,直到队列已满,此后任何发送尝试都将导致错误。队列的深度通常是用户在创建或配置系统时指定的。在许多 RTOS 中,可以进行阻塞调用以发送到队列;这意味着,如果队列已满,一个任务可能会被挂起,直到队列被另一个任务读取。任何任务都可以从队列中读取。消息的读取顺序与发送顺序相同——先进先出 (FIFO)。如果一个任务试图从一个空队列中读取,它将收到一个错误响应。在许多 RTOS 中,可以进行阻塞调用以从队列中读取;这意味着,如果队列为空,则任务可能会暂停,直到另一个任务将消息发送到队列。
RTOS 可能会支持将消息发送到队列前面的功能——这也称为“干扰”。一些 RTOS 还支持“广播”功能。这使消息能够发送到在读取队列时暂停的所有任务。此外,RTOS 可以支持可变长度消息的发送和读取;这提供了更大的灵活性,但会带来一些额外的开销。
许多 RTOS 支持另一种称为“管道”的内核对象类型。管道本质上与队列相同,但处理面向字节的数据。
队列的内部操作在这里不感兴趣,但应该理解它们在内存和运行时的开销比邮箱要多。这主要是因为需要维护两个指针——指向队列的头部和尾部。
在以后的文章中有更多关于队列和管道的信息,这些文章描述了它们在 Nucleus SE 中的实现。
互斥
信号量互斥信号量——互斥量——是独立的内核对象,其行为方式与正常的二进制信号量非常相似。它们稍微复杂一些,并包含临时所有权的概念(资源的,对其的访问受到控制)。如果一个任务获得了一个互斥锁,那么只有同一个任务才能再次释放它——互斥锁(以及资源)暂时归任务所有。
并非所有 RTOS 都提供互斥锁,但调整常规二进制信号量非常简单。有必要编写一个“互斥量获取”函数,该函数获取信号量并记录任务标识符。然后一个互补的“互斥释放”函数将检查调用任务的标识符,只有当它与存储的值匹配时才释放信号量,否则它将返回错误。
Colin Walls 在电子行业拥有超过 30 年的经验,主要致力于嵌入式软件。Colin 经常在会议和研讨会上发表演讲,并着有大量技术文章和两本关于嵌入式软件的书籍,他是 Mentor Embedded [Mentor Graphics Embedded Software Division] 的嵌入式软件技术专家,常驻英国。
全部0条评论
快来发表一下你的评论吧 !