FreeRTOS信号量(10)

信号量的简介

信号量是进程间用于通信的一种手段,其是基于队列实现的,信号量更适用于进程间同步,信号量包括二值信号量(Binary Semaphores)和计数信号量(Counting Semaphores)

任务的同步和互斥

《RTOS 中的同步与互斥》 在实时操作系统(RTOS)中,同步是不同任务之间或者任务与外部事件之间的协同工作方式,确保多个并发执行的任务按照预期的顺序或时机执行。同步涉及到线程或任务间的通信和协调机制,其目的在于避免数据竞争、解决竞态条件,并确保系统的正确行为。 而互斥则是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。

当计数值大于0,代表有信号量资源

当释放信号量,信号量计数值(资源数)加一

当获取信号量,信号量计数值(资源数)减一

信号量的计数值都有限制:限定最大值。

如果最大值被限定为1,那么它就是二值信号量

如果最大值不是1,它就是计数型信号量

信号量:用于传递状态

队列与信号量的对比

队列信号量
可以容纳多个数据; 创建队列有两部分内存:队列结构体+队列项存储空间仅存放计数值,无法存放其他数据; 创建信号量,只需分配信号量结构体
写入队列:当队列满时,可阻塞;释放信号量:不可阻塞,计数值++, 当计数值为最大值时,返回失败
读取队列:当队列为空时,可阻塞;获取信号量:计数值–, 当没有资源时,可阻塞

二值信号量和计数型信号量

二值信号量就是只有一个项的队列,该队列不为空则为满(所谓二值),二值信号量就像一个标志,适和用于进程间同步的通信

二值信号量通常用于互斥访问或任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,所以二值信号量更适合用于同步!

在这里插入图片描述
计数信号量就是有固定长度的队列,队列中每个单元都是一个标志,其通常用于对多个共享资源的访问进行控制

创建信号量

信号量在使用之前也必须先创建,信号量被创建完之后是无效的,也即为 0 ,而由于信号量分为二值信号量和计数信号量两种,因此FreeRTOS也提供了不同的API函数,具体如下所述

/**
  * @brief  动态分配内存创建二值信号量函数
  * @param  xSemaphore:创建的二值信号量句柄
  * @retval None
  */
void vSemaphoreCreateBinary(SemaphoreHandle_t xSemaphore);
 
/**
  * @brief  静态分配内存创建二值信号量函数
  * @param  pxSemaphoreBuffer:指向一个StaticSemaphore_t类型的变量,该变量将用于保存信号量的状态
  * @retval 返回创建成功的信号量句柄,如果返回NULL则表示因为pxSemaphoreBuffer为空无法创建
  */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
									StaticSemaphore_t *pxSemaphoreBuffer);
 
/**
  * @brief  动态分配内存创建计数信号量函数
  * @param  uxMaxCount:可以达到的最大计数值
  * @param  uxInitialCount:创建信号量时分配给信号量的计数值
  * @retval 返回创建成功的信号量句柄,如果返回NULL则表示内存不足无法创建
  */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, 
										   UBaseType_t uxInitialCount);
 
/**
  * @brief  静态分配内存创建计数信号量函数
  * @param  uxMaxCount:可以达到的最大计数值
  * @param  uxInitialCount:创建信号量时分配给信号量的计数值
  * @param  pxSempahoreBuffer:指向StaticSemaphore_t类型的变量,该变量然后用于保存信号量的数据结构体
  * @retval 返回创建成功的信号量句柄,如果返回NULL则表示因为pxSemaphoreBuffer为空无法创建
  */
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
									UBaseType_t uxMaxCount,
									UBaseType_t uxInitialCount,
									StaticSemaphore_t pxSempahoreBuffer);

释放信号量

以下两个函数不仅仅可以用于释放二值信号量,还可以用于释放计数信号量和互斥量,具体如下所示

/**
  * @brief  释放信号量函数
  * @param  xSemaphore:要释放的信号量的句柄
  * @retval 如果信号量释放成功,则返回pdTRUE;如果发生错误,则返回pdFALSE
  */
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
 
/**
  * @brief  释放信号量的中断安全版本函数
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  * @retval 如果成功给出信号量,则返回pdTRUE,否则errQUEUE_FULL
  */
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, 
								 BaseType_t *pxHigherPriorityTaskWoken);

获取信号量

/**
  * @brief  获取信号量函数
  * @param  xSemaphore:正在获取的信号量的句柄
  * @param  xTicksToWait:等待信号量变为可用的时间
  * @retval 成功获得信号量则返回pdTRUE;如果xTicksToWait过期,信号量不可用,则返回pdFALSE
  */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
/**
  * @brief  获取信号量的中断安全版本函数
  * @param  xSemaphore:正在获取的信号量的句柄
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  * @retval 成功获取则返回pdTRUE,未成功获取则返回pdFALSE
  */
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, 
								 signed BaseType_t *pxHigherPriorityTaskWoken);
 

删除信号量

/**
  * @brief  删除信号量,包括互斥锁型信号量和递归信号量
  * @param  xSemaphore:被删除的信号量的句柄
  * @retval None
  */
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);

获取信号量计数

/**
  * @brief  获取信号量计数
  * @param  xSemaphore:正在查询的信号量的句柄
  * @retval 如果信号量是计数信号量,则返回信号量的当前计数值。如果信号量是二进制信号量,则当信号量可用时,返回1,当信号量不可用时,返回 0
  */
UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore);

互斥量

优先级翻转简介

使用二值信号量用于进程间同步时可能会出现优先级翻转的问题,什么是“优先级翻转”问题呢?

  • 在 t1 时刻,低优先级的任务 TaskLP 切入运行状态,并且获取到了一个二值信号量 Binary Semaphores
  • 在 t2 时刻,高优先级的任务 TaskHP 请求获取二值信号量 Binary Semaphores ,但是由于 TaskLP 还未释放该二值信号量,所以在 t3 时刻,任务 TaskHP 进入阻塞状态等待二值信号量被释放
  • 在 t4 时刻,中等优先级的任务 TaskMP 进入就绪状态,由于不需要获取二值信号量,因此抢占低优先级任务任务 TaskLP 切入运行状态
  • 在 t5 时刻,任务 TaskMP 运行结束,任务 TaskLP 再次切入运行状态
  • 在 t6 时刻,任务 TaskLP 运行结束,释放二值信号量 Binary Semaphores,此时任务 TaskHP 从等待二值信号量的阻塞状态切入运行状态
  • 在t7时刻,任务 TaskHP 运行结束

根据上述流程读者可以发现一个问题,即在 t4 时刻中等优先级的任务 TaskMP 先于高优先级的任务 TaskHP 抢占了处理器,这破坏了 FreeRTOS 基于优先级抢占式执行的原则,我们将这种情况称为优先级翻转问题,上述描述的任务运行过程具体时刻流程图如下图所示:

在这里插入图片描述

优先级继承和互斥信号量

为了解决使用二值信号量可能会出现的优先级翻转问题,对二值信号量做了改进,增加了一种名为 “优先级继承” 的机制,改进后的实例称为了互斥量,注意虽然互斥量可以减缓优先级翻转问题的出现,但是并不能完全杜绝

接下来我们来通过例子介绍什么是优先级继承?

仍然考虑由 “优先级翻转问题” 小节中提出的任务运行过程的例子,具体流程如下所述,读者可以细心理解其中的不同之处

  • 在 t1 时刻,低优先级的任务 TaskLP 切入运行状态,并且获取到了一个互斥量 Mutexes
  • 在 t2 时刻,高优先级的任务 TaskHP 请求获取互斥量 Mutexes ,但是由于 TaskLP 还未释放该互斥量,所以在 t3 时刻,任务 TaskHP 进入阻塞状态等待互斥量被释放,但是与二值信号量不同的是,此时 FreeRTOS 将任务 TaskLP 的优先级临时提高到与任务 TaskHP 一致的优先级,也即高优先级
  • 在 t4 时刻,中等优先级的任务 TaskMP 进入就绪状态发生任务调度,但是由于任务 TaskLP 此时优先级被提高到了高优先级,因此任务 TaskMP 仍然保持就绪状态等待优先级较高的任务执行完毕
  • 在 t5 时刻,任务 TaskLP 执行完毕释放互斥量 Mutexes,此时任务 TaskHP 抢占处理器切入运行状态,并恢复任务 TaskLP 原来的优先级
  • 在 t6 时刻,任务 TaskHP 执行完毕,此时轮到任务 TaskMP 执行
  • 在 t7 时刻,任务 TaskMP 运行结束

根据互斥量的上述任务流程读者可以发现与二值信号量的不同之处,上述描述的任务运行过程具体时刻流程图如下图所示
在这里插入图片描述

互斥信号量

互斥量/互斥锁是一种特殊类型的二进制信号量,用于控制对在两个或多个任务之间共享资源的访问

互斥锁可以被视为一个与正在共享的资源相关联的令牌,对于合法访问资源的任务,它必须首先成功 “获取” 令牌,成为资源的持有者,当持有者完成对资源的访问之后,其需要 ”归还” 令牌,只有 “归还” 令牌之后,该令牌才可以再次被其他任务所 “获取” ,这样保证了互斥的对共享资源的访问,上述机制如下图所示
在这里插入图片描述

死锁现象

“死锁” 是使用互斥锁进行互斥的另一个潜在陷阱,当两个任务因为都在等待对方占用的资源而无法继续进行时,就会发生死锁,考虑如下所述的情况

  1. 任务 A 执行并成功获取互斥量 X
  2. 任务 A 被任务 B 抢占
  3. 任务 B 在尝试获取互斥量 X 之前成功获取互斥量 Y,但互斥量 X 由任务 A 持有,因此对任务 B 不可用,任务 B 选择进入阻塞状态等待互斥量 X 被释放
  4. 任务 A 继续执行,它尝试获取互斥量 Y,但互斥量 Y 由任务 B 持有,所以对于任务 A 来说是不可用的,任务 A 选择进入阻塞状态等待待释放的互斥量 Y

经过上述的这样一个过程,读者可以发现任务 A 在等待任务 B 释放互斥量 Y ,而任务 B 在等待任务 A 释放互斥量 X ,两个任务都在阻塞状态无法执行,从而导致 ”死锁“ 现象的发生,与优先级翻转一样,避免 “死锁” 的最佳方法是在设计时考虑其潜在影响,并设计系统以确保不会发生死锁

递归互斥量

任务也有可能与自身发生死锁,如果任务尝试多次获取相同的互斥体而不首先返回互斥体,就会发生这种情况,考虑以下设想:

  1. 任务成功获取互斥锁
  2. 在持有互斥体的同时,任务调用库函数
  3. 库函数的实现尝试获取相同的互斥锁,并进入阻塞状态等待互斥锁变得可用

在此场景结束时,任务处于阻塞状态以等待互斥体返回,但任务已经是互斥体持有者。 由于任务处于阻塞状态等待自身,因此发生了死锁

通过使用递归互斥体代替标准互斥体可以避免这种类型的死锁,同一任务可以多次 “获取” 递归互斥锁,并且只有在每次 “获取” 递归互斥锁之后都调用一次 “释放” 递归互斥锁,才会返回该互斥锁

因此递归互斥量可以视为特殊的互斥量,一个互斥量被一个任务获取之后就不能再次获取,其他任务想要获取该互斥量必须等待这个任务释放该互斥连,但是递归互斥量可以被一个任务重复获取多次,当然每次获取必须与一次释放配对使用

注意不管是互斥量,还是递归互斥量均存在优先级继承机制,但是由于 ISR 并不是任务,因此互斥量和递归互斥量不能在中断中使用

互斥信号量相关API函数

使用互斥信号量:首先将宏configUSE_MUTEXES置1

注意:创建互斥信号量时,会主动释放一次信号量

创建互斥量

互斥量在使用之前必须先创建,因为互斥量分为互斥量和递归互斥量两种,所以 FreeRTOS 也提供了不同的 API 函数,具体如下所述

/**
  * @brief  动态分配内存创建互斥信号量函数
  * @retval 创建互斥信号量的句柄
  */
SemaphoreHandle_t xSemaphoreCreateMutex(void);
 
/**
  * @brief  静态分配内存创建互斥信号量函数
  * @param  pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态
  * @retval 返回成功创建后的互斥锁的句柄,如果返回NULL则表示内存不足创建失败
  */
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);
 
/**
  * @brief  动态分配内存创建递归互斥信号量函数
  * @retval 创建递归互斥信号量的句柄,如果返回NULL则表示内存不足创建失败
  */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
 
/**
  * @brief  动态分配内存创建二值信号量函数
  * @param  pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态
  */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(
								StaticSemaphore_t pxMutexBuffer);

获取互斥量

获取互斥量直接使用获取信号量的函数即可,但对于递归互斥量需要专门的获取函数,具体如下所述

/**
  * @brief  获取信号量函数
  * @param  xSemaphore:正在获取的信号量的句柄
  * @param  xTicksToWait:等待信号量变为可用的时间
  * @retval 成功获取信号量则返回pdTRUE, xTicksToWait过期,信号量不可用,则返回pdFALSE
  */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
 
/**
  * @brief  获取递归互斥量
  * @param  xMutex:正在获得的互斥锁的句柄
  * @param  xTicksToWait:等待信号量变为可用的时间
  * @retval 成功获取信号量则返回pdTRUE, xTicksToWait过期,信号量不可用,则返回pdFALSE
  */
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex,
								   TickType_t xTicksToWait);

释放互斥量

释放互斥量直接使用释放信号量的函数即可,但对于递归互斥量需要专门的释放函数,具体如下所述

/**
  * @brief  释放信号量函数
  * @param  xSemaphore:要释放的信号量的句柄
  * @retval 成功释放信号量则返回pdTRUE, 若发生错误,则返回pdFALSE
  */
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
 
/**
  * @brief  释放递归互斥量
  * @param  xMutex:正在释放或“给出”的互斥锁的句柄
  * @retval 成功释放递归互斥量后返回pdTRUE
  */
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);

删除互斥量

直接使用信号量的删除函数即可,具体如下所述

/**
  * @brief  获取信号量函数
  * @param  xSemaphore:要删除的信号量的句柄
  * @retval None
  */
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
FreeRTOS 信号量是一种同步机制,用于在任务之间共享资源。信号量是一个计数器,用于记录可用资源的数量。当一个任务需要使用共享资源时,它会尝试获取信号量。如果信号量计数器的值为正,则任务可以获取该信号量,并将计数器减一。如果信号量的计数器值为零,则任务将进入等待状态,直到有其他任务释放信号量FreeRTOS 中的信号量有两种类型:二进制信号量和计数信号量。 1. 二进制信号量:二进制信号量只有两种状态:可用和不可用。当计数器值为 0 时,信号量不可用;当计数器值为 1 时,信号量可用。 2. 计数信号量:计数信号量允许计数器的值大于 1。当计数器值为 0 时,信号量不可用;当计数器值大于 0 时,信号量可用。 使用 FreeRTOS 信号量需要调用以下函数: 1. `xSemaphoreCreateBinary()`: 创建二进制信号量。 2. `xSemaphoreCreateCounting()`: 创建计数信号量。 3. `xSemaphoreGive()`: 释放信号量。 4. `xSemaphoreTake()`: 获取信号量。 在使用信号量时,需要注意以下几点: 1. 释放信号量的任务必须先获取该信号量。 2. 在获取信号量时,可以设置超时时间,避免任务一直等待。 3. 在使用计数信号量时,需要注意计数器的上限,避免产生资源浪费或死锁的情况。 4. 在使用二进制信号量时,需要注意任务的优先级,避免优先级反转导致的问题。 总之,FreeRTOS 信号量是一种非常实用的同步机制,可以在多任务系统中解决资源共享的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值