1-队列
队列(我对队列的理解就是上体育课,排队这种)是任务之间通信的一种方式。队列可以用于任务和任务之间或者中断和任务之间消息的接收与发送。在多数情况下,他们消息缓冲是按照FIFO(先进先出)原则。也就是最新的数据在上一个数据的后面,换句话说就是,类似于吃羊肉串,比如第一个串起来的,就第一个被吃掉。
下图是队列的数据读写:
FreeRTOS队列用户通常情况下会把灵活性与简单性相结合,但是这种类型通常是属于互斥的。
消息是通过复制的方式经过队列来进行发送,这说明数据的本身被复制到队列中,而不是队列对存储数据的一直引用。这种方式相对来说是较为友好的。主要原因以下几点。
- 如果已经包含在C变量之中(整型、结构体短小的)是可以直接发送到队列中的。不需要为消息进行缓冲区的分配。
也可以直接从队列中把消息读取到C变量中。
除此之外,这种队列发送方式,即使发送的消息仍保留在队列之中,也是允许发送任务立即覆盖发送到队列的缓冲区或者变量。由于变量中包含的数据被复制到队列中,所以变量本身可以重用。无论任务是接收消息还是发送消息,不需要商定是哪个任务接收消息,以及哪个任务负责发送消息。 - 使用通过复制传递数据的队列并不会阻止使用队列通过引用传递数据。当消息的大小达到某个点时,将整个消息字节复制到队列中是不现实的,则定义队列以保存指针,只将指向消息的指针复制到队列中。
- 内核完全负责分配用作队列存储区域的内存
- 可以通过定义结构体成员来保存大小不一的信息,然后再通过队列进行消息的发送和接收。
- 单个队列可用于接收不同的消息类型和来自多个位置的消息,方法是将队列定义为包含一个结构,该结构具有一个成员保存消息类型,另一个成员保存消息数据(或指向 消息数据)。 如何发送数据取决于消息类型。 这正是管理 FreeRTOS-Plus-UDP IP 堆栈的任务能够使用单个队列接收 ARP 计时器事件通知、从以太网硬件接收的数据包、从应用程序接收的数据包、网络传输事件等。
- 这种方式的实现适合受到存储器保护的内存环境中使用。在受保护内存区域的任务可以将数据传递给限制在不同受保护内存区域的任务,因为通过调用队列发送函数来调用 RTOS 会提高微控制器的特权级别。 队列存储区只能由 RTOS 访问(具有完全权限)。
提供了一个单独的API中断供内部使用。
提供了一个单独的 API 供在中断内部使用。 将 RTOS 任务使用的 API 与中断服务例程使用的 API 分开意味着 RTOS API 函数的实现不会带来每次执行时检查其调用内存的管理。 使用单独的中断 API 还意味着,在大多数情况下,与替代 RTOS 产品相比,创建 RTOS 感知中断服务例程对最终用户来说更简单。
1.1 队列阻塞
允许队列API函数指向阻塞
当任务从一个空的队列读取的时候,这个任务将被设置为阻塞状态,因为这样不会消耗任何的CPU以及时间,而且其他的任务是可以运行的,直到队列中有数据为止,或者阻塞时间结束。
当任务尝试写入完整队列时,该任务将处于 阻塞的状态(因此它不消耗任何 CPU 时间,并且可以运行其他的任务) 直到队列中的空间可用,或者阻塞结束。
如果在同一队列上有多个阻塞任务,那么优先级高的将最先解除。
2-FreeRTOS 二进制信号量
二进制信号量的主要用于互斥和同步。
二进制信号量和互斥锁非常相似,但有一些细微的区别:互斥锁有优先级继承机制,而二进制信号量没有。这使得二进制信号量能够实现同步(任务之间或任务与中断之间)有更好选择,互斥体成为实现简单互斥较好的选择。互斥锁如何被用作互斥机制的描述同样适用于二进制信号量。
允许信号量的API函数指向阻塞。
阻塞表示当一个任务尝试“获取”一个信号量时,如果该信号量不是立即可用的,该任务应该进入阻塞状态的最大“tick”数。如果有多个任务阻塞在同一个信号量上,那么优先级最高的任务将是该信号量下次可用时被解除阻塞的任务。
将二进制信号量视为只能容纳一个项目的队列。因此,队列只能为空或已满(因此为二进制)。任务和中断 使用队列并不关心队列包含什么 - 他们只想知道队列是空的还是满的。这 可以利用机制来同步(例如)任务与中断。
考虑任务被用于服务外围设备。轮询外围设备将浪费 CPU 资源,并阻止其他任务执行。因此,最好任务大部分时间都处于Blocked状态(允许其他任务执行),并且只在实际有事情需要它做的时候才执行它自己。这是通过二进制信号量在尝试“获取”信号量时让任务块来实现的。然后为外设编写中断例程,当外设需要服务时,该任务仅“提供”信号量。任务 总是“获取”信号量(从队列中读取以使队列为空),但从不“给予”它。中断总是“给予”信号量(写入 到队列以使其满员),但从不接受它。
任务优先级可以用来确保外设及时获得服务——有效地生成一个“延迟中断”方案。(注意,FreeRTOS也有一个内置的延迟中断机制)。另一种方法是使用队列来代替信号量。当这完成时,中断程序可以捕获与外围事件相关的数据,并将其发送到一个队列中给任务。当队列上的数据变为可用时,该任务解除阻塞,从队列中检索数据,然后执行所需的任何数据处理。第二种方案允许中断保持尽可能短的时间,而所有的后处理都发生在一个任务中。
下图是使用信号量使任务与中断同步:中断只会“给出”信号量,而任务只会“获取”信号量。
3-FreeRTOS计数信号量
就像二进制信号量可以被认为是长度为1的队列一样,计数信号量可以被认为是长度大于1的队列。同样,信号量并不关心存储在队列中的数据,但只关心队列是否为空。
计数信号通常用于两种情况:
-
事件计数
在这个使用场景中,事件处理程序将在每次事件发生时“给出”一个信号量(信号量计数值递增),而处理程序任务将在每次处理事件时“获取”一个信号量(信号量计数值递减)。因此,计数值是已经发生的事件数和已经处理的事件数之间的差值。在这种情况下,当创建信号量时,计数值应该为零。 -
资源管理
在此使用方案中,计数值指示可用的资源数。要获得对资源的控制,任务必须首先获得信号量 - 递减信号量计数值。当计数值达到零时,没有可用资源。当任务完成时 它“提供”信号量的资源 - 递增信号量计数值。在这种情况下,计数值最好为 等于创建信号量时的最大计数值。
4-FreeRTOS互斥锁
互斥体是包含优先级继承机制的二进制信号量。而二进制信号量 是实现同步(任务之间或任务与中断之间)的更好选择,互斥体是实现同步的更好选择。
当互斥用于互斥时,互斥就像保护资源的令牌一样。当一个任务希望访问该资源时,它必须首先获得(“获取”)令牌。当它完成了对资源的处理后,它必须“归还”令牌——允许其他任务有机会访问相同的资源。
互斥体使用相同的信号量访问 API 函数,因此也允许指定块时间。区块时间表示“即时报价”的最大数量 如果互斥锁不能立即可用,则任务在尝试“获取”互斥锁时应进入“已阻止”状态。与二进制信号量不同 但是 - 互斥锁采用优先级继承。这意味着,如果高优先级任务在尝试获取互斥锁(令牌)时阻塞 当前由较低优先级的任务持有,则持有令牌的任务的优先级暂时提高到阻止任务的优先级。此机制 旨在确保优先级较高的任务在尽可能短的时间内保持阻塞状态,从而最大限度地减少“优先级倒置” 这种情况已经发生。
优先级继承不能解决优先级倒置!在某些情况下,它只是将其影响降至最低。硬实时应用程序应设计为此类 优先级倒置首先不会发生。
最好不要从中断中使用互斥:
因为:
- 它们包括一个优先级继承机制,只有在以下情况下才有意义 互斥锁是从任务中给出和获取的,而不是中断。
- 中断不能阻塞以等待由 互斥锁变为可用。
- 下图是:互斥锁对共享资源的访问
5-FreeRTOS 递归互斥
递归使用的互斥锁可以被所有者反复“获取”。在所有者调用之前,互斥锁不再可用 xSemaphoreGiveRecursive() 表示每个成功的 xSemaphoreTakeRecursive() 请求。例如,如果一项任务成功地“采取”了相同的任务 互斥锁 5 次,则互斥锁将不可用于任何其他任务,直到它也“返回”互斥锁正好五次。
这种类型的信号灯使用优先级继承机制,因此“获取”信号量的任务必须始终“返回”信号量一次 的 信号灯 它不再需要。
不能从中断服务例程中使用互斥类型信号量。
不应从中断中使用互斥体,因为:
- 它们包括一个优先级继承机制,只有在以下情况下才有意义 互斥锁是从任务中给出和获取的,而不是中断。
- 中断不能阻塞以等待由 互斥锁变为可用