关于信号量和队列的联系
请注意本文章是关于FreeRTOS嵌入式实时操作系统的学习,内容设计互斥量,和软件开发中多线程的互斥量虽然名字相同,但并非完全同一概念,可以理解成另一角度来互斥量,特声明.此外本文章仅用作个人记录,手册查找,待修改.错漏百出,不堪入目.如有错误,请尽情码我!
互斥量就是解决有 不同优先级 任务矛盾的问题的特殊 信号量
矛盾描述:
假设两个任务, 优先级 taskA 优先级为3, taskC 优先级为1(FreeRTOS数越大,优先级越高),
task A 优先级最高,最先执行, 当执行到一步,需要从taskC 中传入参数,此时taskA 阻塞,执行taskB, 执行完,taskB返回给taskA参数,taskA继续执行.
可以看出 当高优先级任务需要从低优先级任务活动的参数时,得阻塞到地低优先级任务执行完出传回参数.
两个级别优先级看不出大问题,但又三个不同优先级级别的任务就有矛盾了
设 以中间层 优先级 任务 TaskB ,优先级2
仍然taskA最先执行,当需要传入参数,taskA阻塞,跳转到taskC, 此时就出现问题了, 还有一个taskB处于"就绪状态"呢,比他优先级高 的taskA"阻塞"了,虽然跳转到taskC,但C优先级只有1, taskB 一看,抢占执行了.taskC"就绪",taskA"阻塞"后台"等"这taskC传入参数,
taskB执行完,taskC执行,最后taskA"恢复",执行完毕
问题就来了.
中间层任务会抢占要给最高优先级任务传入参数的最低优先级执行权限.
(缩写:中优先级抢占低优先级,但没有这个低优先级执行完后的参数,高优先级就不能继续执行,只能等着(阻塞),这就是优先级反转)
即 优先级最高的任务 最先执行,但不一定 最先执行完.
互斥量就是来解决这一问题的
Mutex!
对于互斥量这一概念块的描述:
基本概念
将互斥量理解为令牌,用于访问一个特殊的内存区域, 设置互斥量理解为上锁,可以将特殊的内存区域保护起来,锁起来不让阻塞在外面的高优先级任务打扰,因此将内存划分为两种
临界区: 临界区是指一段在执行过程中不能被中断的代码区域,即同一时间只有一个任务可以执行的代码段.
非临界区:任务(线程)可以并发运行的区域
首先互斥量在FreeRTOS中实际上是一个数据结构,
(是什么数据结构?是一个句柄,什么是句柄? 指向一个变量的叫做指针,指向含有多个类型成员的结构体的叫句柄. 这些成员用于管理和控制互斥量)
更具体的而说,互斥量这个句柄包含以下成员
类型标识 标识这个数据结构是互斥量。
互斥量状 标记 是否被占用。
占用互斥量的任务列表 记录当前正在占用该互斥量的任务列表。
等待互斥量的任务列表 记录正在等待获取该互斥量的任务列表。
拓展属性 例如是否是递归互斥量等。()
当我们在FreeRTOS中创建一个互斥量时,实际上是创建了这个互斥量数据结构,并返回一个指向该数据结构的指针(句柄)。我们使用这个句柄来操作互斥量,比如获取(xSemaphoreTake())、释放(xSemaphoreGive())等操作。
xSemaphoreTake())需要互斥量句柄,超时时间,最大是portMAXdelay(直到获取到互斥量这个令牌,就是要访问临界区域)
使用互斥量的步骤:
创建,获取,释放
创建xSemaphoreCreateMutex() 函数创建一个互斥量,获取返回的句柄(handle)。
获取互斥量:在任务中使用 xSemaphoreTake() 函数来获取互斥量。如果互斥量当前可用(没有被其他任务持有),任务将会获取互斥量并继续执行。如果互斥量已经被其他任务持有,则当前任务会被阻塞,直到互斥量可用。 使用共享资源:一旦任务成功获取了互斥量,它就可以安全地访问共享资源了,因为在这个时刻,其他任务无法同时访问这个资源。
释放互斥量:在任务完成对共享资源的操作后,使用 xSemaphoreGive() 函数释放互斥量。这样其他任务就可以继续获取这个互斥量了。
有过的疑惑:
Q:对于互斥量,直观理解是什么? A:{是一种数据结构,即结构体,
包含多个成员:
- 状态标志:表示互斥量的当前状态,比如是否被占用。
- 持有者信息:记录当前持有互斥量的任务或任务列表。
- 等待队列:记录正在等待获取互斥量的任务列表。
- 优先级继承信息:记录因为优先级继承机制而提升的优先级等}
Q:为什么要用互斥保某一代码段? 什么代码段需要被保护
A: 为了避免数据竞争,保证多任务的访问井然有序,
共享资源(如全局变量)需要被保护
数据竞争演示 任务1读取 sharedVariable 的值为5。 任务2也同时读取 sharedVariable 的值为5。 任务1对 sharedVariable 执行加1操作,结果为6。 任务2对 sharedVariable 也执行加1操作,结果为6。
使用互斥量 的目的
防止竞态条件
:竞态条件指的是当多个任务同时访问一个共享资源时可能产生的不确定性行为。例如,两个任务同时对一个全局变量进行读写操作,由于执行顺序不确定,可能导致变量值异常或不一致。
确保数据一致性
:如果多个任务同时修改共享数据,可能会导致数据不一致的问题。例如,两个任务同时修改一个计数器,如果没有互斥保护,计数器的值可能不正确。
避免死锁
:在多任务环境下,如果多个任务互相等待对方释放的资源,可能导致死锁。使用互斥量可以避免这种情况。
示例
// 创建一个互斥量
SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
// 全局变量,需要互斥保护
volatile int sharedVariable = 0;
void Task1(void *pvParameters) {
while (1) {
// 尝试获取互斥量,进入临界区
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdPASS) {
// 进入临界区代码
sharedVariable++; // 对共享变量进行操作
xSemaphoreGive(mutex); // 离开临界区前释放互斥量
}
// 非临界区代码
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void Task2(void *pvParameters) {
while (1) {
// 尝试获取互斥量,进入临界区
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdPASS) {
// 进入临界区代码
sharedVariable--; // 对共享变量进行操作
xSemaphoreGive(mutex); // 离开临界区前释放互斥量
}
// 非临界区代码
vTaskDelay(pdMS_TO_TICKS(100));
}
}
在这个示例中,Task1 和 Task2 的临界区是对 sharedVariable 进行加减操作的部分。在这些代码段内,必须使用互斥量来保护,因为多个任务会同时访问 sharedVariable。而在 vTaskDelay() 的调用处是非临界区,这些操作是安全的,并且可以并发执行。
因此,没有加互斥量的函数或代码段可以看作是非临界区,而加了互斥量的部分则是临界区。
Q:互斥量的创建:
#include "FreeRTOS.h"
#include "semphr.h"
int main() {
SemaphoreHandle_t mutex; // 定义一个互斥量的句柄变量
// 创建互斥量,并将句柄存储在变量 mutex 中
mutex = xSemaphoreCreateMutex();
// 检查互斥量是否成功创建
if (mutex != NULL) {
// 互斥量创建成功,可以在这里进行其他操作
// 例如使用 xSemaphoreTake() 和 xSemaphoreGive() 等函数操作互斥量
// 互斥量不用的化记得删除互斥量
vSemaphoreDelete(mutex);
}
// 其他代码...
return 0;
}
Q: 当低优先级任务持有互斥量, 即使是高优先级任务也无法抢断,
互斥量机制下的 优先级继承机制
暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。
一句话,就是暂时 将 低优先级任务 与他访问同一内存区域 的高优先级 任务 的优先级保持一致
引用野火文档
Q:什么是优先级反转
A上面已经解释了,这里粘贴
中间层任务会抢占要给最高优先级任务传入参数的最低优先级执行权限.
(缩写:中优先级抢占低优先级,但没有这个低优先级执行完后的参数,高优先级就不能继续执行,只能等着(阻塞),这就是优先级反转)
即 优先级最高的任务 最先执行,但不一定 最先执行完.
Q:互斥量如何实现上诉能力的,或者说如何实现优先级继承的?
优先级继承机制
A: 即解决 优先级反转的机制
(暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系 统资源被任何中间优先级的任务抢占。)
Q:关于占用互斥量的任务列表 和 等待互斥量的任务列表
A : 首先,这是两个列表, 里面分别 记录正在持有互斥量这个令牌,访问临界区 的任务(占用互斥量的任务),和外面等待使用互斥量访问临界区 的任务(包含比当前执行任务优先级高的任务,这也是互斥量这令牌的意义, 只有令牌 (xSemaphoreTake(mutex, portMAX_DELAY) == pdPASS),接收到pdPASS了,才能进入临界区)
Q 临界区的代码 是什么,干什么的
A:临界区内的代码段涉及对共享资源的读写或操作,需要确保同一时间只有一个任务能够进入临界区执行操作,以避免数据竞争和不一致的情况。
数据竞争: 对一个全局变量,两个任务同时访问内存和修改,会引起冲突.
Q 关于获取(xSemaphoreTake())、释放(xSemaphoreGive())为什么give是释放?give不是给予吗,这是什么意思?
A xSemaphoreTake():这个函数实际上是用来获取(take)互斥量的所有权。
当任务调用 xSemaphoreTake() 时,它试图获取(take)互斥量,如果互斥量当前可用(没有被其他任务持有),则任务会获取互斥量并继续执行。如果互斥量已经被其他任务持有,则当前任务会被阻塞,直到互斥量可用。因此,"Take" 在这里表示获取所有权的意思。
xSemaphoreGive():这个函数实际上是用来释放(give)互斥量的所有权。只有开锁了,其他任务才能有机会获取互斥量并访问临界区
不要忘记用xSemaphoreGive释放,否则如果互斥量没有释放掉,则外面的任务(线程)将会一直等待,
同时如果确定一个互斥量不再需要 用vSemaphoreDelete(互斥量句柄名字); 删除,避免内存泄漏,
Q: xSemaphoreTake和xSemaphoreTakeFromISR有什区别?
xSemaphoreTakeFromISR是xSemaphoreTake功能相同,都是获取信号量,就是获取互斥量(信号量特殊形式)
任务中需要获取信号量,可以使用 xSemaphoreTake。 如果在中断中需要获取信号量,应该使用 xSemaphoreTakeFromISR,这个函数会带有中断保护。
让任务尝试获取一个互斥量,信号量
Q如何获取?
上文Q:互斥量的创建: .这里细化
SemaphoreHandle_t semaphore; // 定义信号量
void Task(void *pvParameters) {
while (1) {
// 尝试获取信号量,等待时间为 portMAX_DELAY(永久等待)
if (xSemaphoreTake(semaphore, portMAX_DELAY) == pdPASS) {
// 成功获取信号量,进入临界区
// 执行临界区操作
xSemaphoreGive(semaphore); // 临界区操作完成后释放信号量
}
// 可以并发执行的代码段
}
}
Task 任务尝试获取信号量
semaphore,并且设置等待时间为
portMAX_DELAY,表示永久等待。如果信号量当前可用,任务会获取信号量进入临界区执行操作,然后在临界区操作完成后释放信号量。如果信号量当前不可用(已被其他任务持有),任务会被阻塞在
xSemaphoreTake() 这一行,直到信号量可用或超时。
xSemaphoreTake第二个参数 也可以指定一个超时时间,可以将等待时间设置为一个数值(以 FreeRTOS 的时钟节拍为单位)。例如,等待 100 个时钟节拍:
if (xSemaphoreTake(semaphore, 100) == pdPASS) {
// 获取信号量成功,在临界区内执行操作
// 如果 100 个时钟节拍后仍未获取到信号量,任务继续执行其他代码
} else {
// 未能在指定时间内获取到信号量
}
s
别忘记释放信号量xSemaphoreGicve(信号量句柄)这里是semaphore;
Q中断保护:当一个中断被触发时,系统会跳转到对应的 中断服务函数 中执行相应的处理
中断中也需要获取信号量或其他共享资源(互斥量保护的临界区的资源),这时就需要保证在中断期间不被任务抢占或任务影响。----即
中断处理期间禁止任务调度,以确保 ISR 中的代码可以正常执行
FreeRTOS中所有中断的优先级至少比所有任务线程优先级高 5, 如果说将main划分成多个线程执行, 中断仍是和main函同级别的存在,可以打断main中所有线程,即使是最低优先级,也能打断最高优先级任务,这需要特殊 的函数 xxxxxFromISR 来实现中断保护,禁止任务调度,确保在 ISR 中执行时不会被其他任务抢,,直到退出临界区为止。
Q 中断服务函数和终端回调函数又什么 区别?
互斥量缺点
对于嵌入式开发,一般都是单核CPU,而对于软件开发,互斥量的解释更加通用,
现在CPU都是多核心,任务常常本就可以多任务执行,如果使用互斥量,便相当于加了一块锁,
加锁是会损耗线程的性能,一次只能有一个线程进行运行,
加锁的内存区域破坏了多线程并发的过程,一般非必要的情况下最后不要加锁。
常用函数
使用前要开启的宏
原生FreeRTOS移植 在freeRTOSConfig.h中
cmsis 封装库
- xSemaphoreCreateMutex():用于创建一个互斥量。
- xSemaphoreTake():用于获取(占用)互斥量。如果互斥量已被占用,则任务会被阻塞直到互斥量可用。
- xSemaphoreGive():用于释放(归还)互斥量。如果有任务正在等待该互斥量,它将被释放并可以继续执行。
- xSemaphoreTakeRecursive():用于在递归情况下获取互斥量,允许同一个任务多次获取同一个互斥量而不会引起死锁。
- xSemaphoreGiveRecursive():用于在递归情况下释放互斥量。
通过一系列函数来实现任务之间对共享资源的安全访问
避免并发访问导致的不一致,或者数据损坏
验证 互斥量避免了优先级反转问题:
创建三个优先级不同任务