FreeRTOS 任务的通信、同步

FreeRTOS 任务的通信、同步


待到采菊六月天,满城尽带黄金甲。 @firestaradmin

今天浅浅记录一下关于FreeRTOS 的任务间的 通信、同步等知识,主要是一些概念和应用场景的浅析记录,不包含具体代码用法。

总是会忘记,所以在此记录。

FreeRTOS 里主要有 消息队列、信号量、互斥量、事件、软件定时器、人物通知等功能模块用于任务间(或者任务和中断间)的通信和同步等功能。

1、队列

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息。消息队列是一种异步的通信方式。

当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效,当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。

通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO),但是也支持后进先出原则(LIFO)。

FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:

  • 消息支持先进先出方式排队,支持异步读写工作方式。
  • 读写队列均支持超时机制。
  • 消息支持后进先出方式排队,往队首发送消息(LIFO)。
  • 可以允许不同长度(不超过队列节点最大值)的任意类型消息。
  • 一个任务能够从任意一个消息队列接收和发送消息。
  • 多个任务能够从同一个消息队列接收和发送消息。
  • 当队列使用结束后,可以通过删除队列函数进行删除

2、信号量

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任 务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。

抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。

通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况:

  • 0:表示没有积累下来的释放信号量操作,且有可能有在此信号量上阻塞的任务。
  • 正值,表示有一个或多个释放信号量操作。

2.1 二值信号量

一般的,二值信号量更多用于同步功能,当然也可以用于临界资源访问,但一般临界资源访问我们使用互斥量(具体原因看下面互斥信号量讲解)。

二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别:互斥量有优先级继承机制,二值信号量则没有这个机制。这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于临界资源的访问。

用作同步时,信号量在创建后应被置为空,任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪态,如果任务 1 的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。同样的,在中断服务函数中释放信号量,任务 1 也会得到信号量,从而达到任务与中断间的同步。

还记得我们经常说的中断要快进快出吗,在裸机开发中我们经常是在中断中做一个标记,然后在退出的时候进行轮询处理,这个就是类似我们使用信号量进行同步的,当标记发生了,我们再做其他事情。在 FreeRTOS 中我们用信号量用于同步,任务与任务的同步,中断与任务的同步,可以大大提高效率。

可以将二值信号量看作只有一个消息的队列,因此这个队列只能为空或满(因此称为二值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。


2.2 计数信号量

二进制信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。

顾名思义,计数信号量肯定是用于计数的,在实际的使用中,我们常将计数信号量用于事件计数与资源管理。每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加 1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号量计数值减 1),信号量的计数值则表示还有多少个事件没被处理。此外,系统还有很多资源,我们也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0的时候任务就无法访问该资源了。

3、互斥信号量

互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理

任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。

当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。

如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于任务与任务、任务与中断的同步,但是互斥量更多的是用于保护资源的互锁。

用于互锁的互斥量可以充当保护资源的令牌,当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问该资源。是不是很熟悉,在我们的二值信号量里面也是一样的,用于保护临界资源,保证多任务的访问井然有序。当任务获取到信号量的时候才能开始使用被保护的资源,使用完就释放信号量,下一个任务才能获取到信号量从而可用使用被保护的资源。

但是信号量会导致的另一个潜在问题,那就是任务优先级翻转(具体会在下文讲解)。而 FreeRTOS 提供的互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的响,所以,用于临界资源的保护一般建议使用互斥量。

3.1 优先级翻转和继承

这里引用(242条消息) RTOS信号量与互斥量的理解_奔跑的小赛兔的博客-CSDN博客_freertos互斥信号量 博客。

优先级反转:低优先级先执行,高优先级后执行。
优先级继承:将低优先级暂时拉到和高优先级相同的优先级(继承高优先级的优先级)。

优先级反转的举例:

任务1优先级 > 任务2优先级 > 任务3优先级

任务1首先执行(首先不断获取二值信号量,由于之前没有释放二值信号量的操作,所以任务1首先释放一次二值信号量,然后进入获取二值信号量的等待)

任务2接着执行。

任务3执行(首先获取信号量,由于之前任务1释放了信号量,所以任务3就得到了这个信号量,在释放这个二值信号量之前发起很多次任务调度,在任务调度的过程中发现了优先级更高的任务1,由于任务3没有释放掉这个信号量,所以任务1暂时被阻塞,等待任务3释放信号量。在此同时,任务调度还在发起,检测到次优先级任务2,所以此时任务2开始运行直到结束再切换到任务3,任务3接着运行直到结束。而任务1一直处于阻塞状态,直到任务2、任务3运行完才能运行任务1).这个过程中就发生了优先级反转,任务1本来优先级最高却没有机会及时执行。

由于优先级反转危害太大,说以这种情况就用互斥量代替二值信号量,互斥量具有优先级继承机制,有效避免了优先级反转的危害。

优先级继承的举例:

任务1优先级 > 任务2优先级 > 任务3优先级

和上述例子的区别在于,任务调度发起后,任务1由于没得到任务3释放的信号量而阻塞,此时系统将任务3的优先级提升到和任务1相同(优先级继承),目的就是任务3释放信号量之前任务2不能运行,任务3一释放信号量任务1马上运行。那任务1等待的时间最多也就是任务3运行的时间,这样就节省了等待任务2的时间,有效的保证了实时性。

4、事件

事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。


TODO

5、软件定时器

定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。类似生活中的闹钟,我们可以设置闹钟每天什么时候响,还能设置响的次数,是响一次还是每天都响。


TODO

6、任务通知

FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有一个 32 位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)。


TODO


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!