来自Colin Walls的关于RTOS的全部真相。 第5条 任务交互和同步



在先前的文章中,我们检查了多任务处理模型,发现每个任务都是一个准独立程序。 尽管嵌入式系统中的任务具有一定程度的独立性,但这并不意味着它们彼此之间并不“了解”。 某些任务将与其他任务真正隔离开来,但是它们之间的交互和同步是常见的要求。 该机制是RTOS的关键功能之一。 功能的范围可能会因RTOS而异,因此,在本文中,我们将考虑公开可用的选项。

该系列中的先前文章:
第4条 任务,上下文切换和中断
第3条 任务与计划
第2条。 RTOS:结构和实时模式
第1条 RTOS:简介。

功能范围


任务间交互和同步有三种模型:

  • 服务与任务相关:RTOS为任务提供的属性可以在它们之间进行交互。 以信号为例。
  • 内核对象是通信或同步的自主方式。 示例:事件标志,邮箱,队列/通道,信号量和互斥量。
  • 消息传递是一种简化的方案,其中RTOS允许您创建消息对象并将其从一个任务转移到另一个或多个任务。 这是内核体系结构的基础,因此这种系统称为“消息传递RTOS”。

适用于不同过程的机制会有所不同。 它们的功能可能会重叠,因此值得考虑这些模型的可伸缩性。 例如,如果一个应用程序需要多个队列,但只需要一个邮箱,则可以实现一个包含一个队列的邮箱。 该对象不是完全理想的,但是邮箱的整个代码不会包含在应用程序中,因此,可伸缩性将减少RTOS所使用的内存量。

公用变量或存储区


任务之间交互的一种简化方法是系统中存在可用于所有任务的变量或存储区。 尽管它很简单,但是它可以应用于多个过程。 访问必须受到控制。 如果变量只是一个字节,则对其进行写入或读取操作很可能是原子操作(即连续操作),但是如果处理器允许对内存字节进行其他操作,则必须小心,因为它们可能会被中断并可能发生同步问题。 实现锁定/解锁的一种方法是在短时间内禁用中断。

如果使用存储区,则仍然需要锁。 假定内存体系结构提供对该字节的原子访问,则可以将第一个字节用作阻塞标志。 一个任务将数据加载到内存区域,设置一个标志,然后等待将其重置。 另一个任务是等待设置标志,读取数据并重置标志。 使用中断禁用作为锁定不太明智,因为移动整个数据缓冲区可能需要一些时间。

共享内存的这种用法类似于多核系统中许多处理器间通信的实现。 在某些情况下,硬件锁定和/或中断内置在共享内存的处理器间接口中。

讯号


信号是传统RTOS提供的任务之间进行交互的最简单机制之一。 它们包含一组与特定任务相关联的位标志(8、16或32,具体取决于特定的应用程序)。
信号标志(或几个标志)可以由任何任务使用逻辑运算“或”来设置。 标志只能由包含信号的任务读取。 读取过程通常是破坏性的,即标志也会被重置。
在某些系统中,信号是以更复杂的方式实现的,因此当设置任何信号标志时,将自动执行由信号的任务所有者分配的特殊功能。 这样就无需执行任务来控制标志本身。 这有点类似于中断处理程序。

事件标志组


事件标志组与信号相似,因为它们是任务之间交互的位导向工具。 同样,它们可以包含8、16或32位。 与信号不同,它们是独立的核心对象,并不“属于”任何特定任务。 任何任务都可以使用逻辑运算“或”和“与”来设置和重置事件标志。 同样,任何任务都可以使用相同的操作检查事件标志。 在许多RTOS中,您可以针对事件标志的组合进行阻塞API调用。 也就是说,可以暂停任务,直到设置了事件标志的特定组合为止。 检查事件标志时,“ consume”选项可能也可用,这会重置所有标志。

信号量


信号量是用于资源核算的独立内核对象。 信号量有两种类型:二进制(只能有两个值)和常规(无限制数量的值)。 一些处理器支持(原子)指令,这些指令有助于快速实现二进制信号量。 二进制信号量可以实现为值为1的一般信号量。

任何任务都可以尝试分配信号量以访问资源。 如果信号量的当前值大于0(信号量是空闲的),则计数器值将减少1,因此,分配将成功。 在许多操作系统中,可以使用锁定机制来分配信号量。 这意味着任务可以处于等待状态,直到信号量被另一个任务释放为止。 任何任务都可以释放该信号量,然后该信号量的值将增加。

邮箱


邮箱是独立的内核对象,提供了发送消息的方法。 消息的大小取决于实现,但通常是固定的。 典型的消息大小是指针大小的一到四个元素。 通常,通过邮箱发送指向更复杂数据的指针。 一些内核以这样的方式实现邮箱:将数据简单地存储在常规变量中,并由内核控制对它的访问。 邮箱也可以称为“交换”,尽管此名称现在很少见。

任何任务都可以将邮件发送到邮箱,然后将其填充。 如果任务尝试将消息发送到完整的邮箱,它将收到错误响应。 在许多RTOS中,可以使用阻止机制将其发送到邮箱。 这意味着该任务将被挂起,直到读取邮箱中的消息。 任何任务都可以从邮箱中读取消息,之后该邮箱为空。 如果任务尝试从空邮箱中读取,它将收到错误响应。 在许多RTOS中,可以使用阻止调用从邮箱中读取。 这意味着该任务将被挂起,直到邮箱中出现新消息。

一些RTOS支持“广播”功能。 这使您可以将消息发送到在读取特定邮箱时当前暂停的所有任务。

一些RTOS根本不支持邮箱。 相反,建议您使用单元素队列。 这在功能上是等效的,但是会增加内存和运行时的开销。

Queue列


队列是独立的内核对象,提供了一种发送消息的机制。 它们比邮箱稍微灵活和复杂。 消息的大小取决于实现方式,但通常是固定的,并针对单词/指针。

任何任务都可以将消息发送到队列,并且可以重复执行此操作,直到队列已满,然后再尝试发送将导致错误。 队列的长度通常由用户在创建或配置系统时确定。 在许多RTOS中,可以将阻塞机制应用于队列。 也就是说,如果队列已满,则可以暂停任务,直到队列中的消息被另一个任务读取为止。 任何任务都可以从队列中读取消息。 消息的读取顺序与发送顺序相同(先进先出,先进先出)。 如果任务尝试从空队列中读取,它将收到错误响应。 在许多RTOS中,可以使用阻塞机制从空队列中读取数据。 也就是说,如果队列为空,则任务可以挂起,直到另一个任务将消息发送到队列。

最有可能的是,RTOS中将存在一种将消息发送到队列前端的机制,这称为干扰。 一些RTOS还支持广播功能。 这使您可以向读取队列时暂停的所有任务发送消息。

另外,RTOS可以支持发送和读取长度可变的消息。 这样可以提供更大的灵活性,但会带来额外的开销。

许多RTOS支持另一种内核对象,即“管道”。 本质上,通道类似于队列,但处理面向字节的数据。

队列的功能无关紧要,但是应该理解,它们比邮箱具有更多的内存和运行时开销,主要是因为有必要保存两个指针:队列的开始和结束。

互斥体


互斥体(互斥信号量)是独立的内核对象,其行为与常规二进制信号量非常相似。 它们比信号量要复杂一些,并包含临时所有权(控制访问的资源)的概念。 如果任务分配了互斥锁,则只有同一任务才能再次释放它:互斥锁(以及资源​​)因此暂时属于该任务。

不是所有的RTOS都提供互斥对象,但是常规的二进制信号量很容易适应。 您必须编写一个互斥锁获取函数,该函数分配一个信号量并分配一个任务标识符。 然后,附加功能“互斥体释放”将检查调用任务的标识符,并仅在与存储值匹配时释放信号量,否则将返回错误。

当我们开发自己的实时OSRV MAX操作系统时(此前已发表过有关此文章的文章),我们的团队“遇到了” Mentor Graphics的微电子学和固件专家Colin Walls的博客。 文章似乎很有趣,可以自己翻译,但是为了不“写到桌子上”,他们决定发表文章。 希望它们对您也有用。 如果是这样,那么我们计划发布该系列中所有翻译的文章。

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

阅读之前发表的第一,第二, 第三, 第四篇文章。

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


All Articles