FreeRTOS学习:资源管理:互斥操作的本质
屏蔽中断
屏蔽中断有两套宏:任务中使用、ISR中使用:
- 任务中使用:taskENTER_CRITICA()/taskEXIT_CRITICAL()
- ISR中使用:taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()
在 taskENTER_CRITICA()/taskEXIT_CRITICAL() 之间:
- 低优先级的中断被屏蔽了:优先级低于、等于 configMAX_SYSCALL_INTERRUPT_PRIORITY
- 高优先级的中断可以产生:优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY
- 但是,这些中断ISR里,不允许使用FreeRTOS的API函数
- 任务调度依赖于中断、依赖于API函数,所以:这两段代码之间,不会有任务调度产生
这套 taskENTER_CRITICA()/taskEXIT_CRITICAL() 宏,是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,调用 taskEXIT_CRITICAL() 才会重新使能中断。
使用 taskENTER_CRITICA()/taskEXIT_CRITICAL() 来访问临界资源是很粗鲁的方法:
- 中断无法正常运行
- 任务调度无法进行
- 所以,之间的代码要尽可能快速地执行
在 taskENTER_CRITICA_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR() 之间:
- 低优先级的中断被屏蔽了:优先级低于、等于 configMAX_SYSCALL_INTERRUPT_PRIORITY
- 高优先级的中断可以产生:优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY
- 但是,这些中断ISR里,不允许使用FreeRTOS的API函数
- 任务调度依赖于中断、依赖于API函数,所以:这两段代码之间,不会有任务调度产生
---------------------------------------------------------------------------------------------------------------------------------
暂停调度器
如果有别的任务来跟你竞争临界资源,你可以把中断关掉:这当然可以禁止别的任务运行,但是这代价太大了。它会影响到中断的处理。
如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,中断还是可以发生、处理。
使用这2个函数来暂停、恢复调度器:
这套 vTaskSuspendScheduler()/xTaskResumeScheduler() 宏,是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,调用 taskEXIT_CRITICAL() 才会重新使能中断。
---------------------------------------------------------------------------------------------------------------------------------
“极简版互斥锁”
任务A和B调用
任务时间轴
A
- 先关闭调度器
- Bcanuse值-- 1-1=0
- 开启任务调度器
此时A无论如何都是可以使用LCD
由于任务A开启了调度器导致被优先级高的任务B抢占资源
- B也是先关闭调度器
- Bcanuse值— 0-1=-1
- 不开启调度器
- Bcanuse值++ -1+1=0
B此时不能够使用LCD
任务 A 执行阶段
- A:关闭调度器 → 冻结任务切换(B 无法抢占,哪怕优先级高)。
- A:bCanUse-- → 1→0 → 标记 “开始占用 LCD”。
- A:使用 LCD(临界资源) → 因调度器关闭,B 无法干扰。
- A:开启调度器 → 调度器恢复,触发 “优先级检查”:
- 若 B 优先级更高 → B 立即抢占 A,A 被暂停;
- 若 B 优先级≤A → A 继续执行,B 不抢占。
任务 B 抢占后执行阶段(假设 B 优先级更高)
- B:关闭调度器 → (但此时 A 可能还没完全释放?不,A “开启调度器” 后已释放调度器锁,B 可独立关调度器 )。
- B:bCanUse-- → 0→-1 → 检查bCanUse == -1 → 进入else。
- B:bCanUse++ → -1→0 → 标记 “未抢到资源”,返回-1,无法使用 LCD。
- B 执行结束 → 调度器再次检查,若 A 仍有未完成逻辑,A 继续执行。
---------------------------------------------------------------------------------------------------------------------------------
A和B两个函数调用,中断也调用
中断(ISR)是 “比任务优先级更高的抢占源” :就算任务关了调度器,中断仍可能打断任务,抢占临界资源(比如 LCD )。
所以,想彻底独占资源,既要关调度器(防任务抢),又要关中断(防中断抢) 。
RTOS 里 “调度器” 和 “中断” 的底层关联:“任务调度的触发,依赖中断(如时钟中断);关中断后,调度器无法主动调度任务”
无论是任务A和B,哪个调用
- 先关闭中断
- bCanUse-- 1-1=0
由于关闭了中断,在这里进行操作时,就不会被其他任务打断,其他中断也不能被打断。关中断后,调度器无法主动调度任务”
- 再开启中断
在任务里面,中断肯定都是开启的
---------------------------------------------------------------------------------------------------------------------------------
但是在中断里,中断可能是关闭的,可能是开启的,所以
- 保存,关闭中断
- 恢复中断
- 恢复
---------------------------------------------------------------------------------------------------------------------------------
写事件组的中断
实际上就是写事件组,写Timer队列,唤醒TimerTask任务
实际上通过Timer任务进行写事件值
写事件值时,在任务里进行修改的,即使是通过中断触发,但是也是在任务里写的,所以对事件值的保护,没有必要进行关闭中断,当需要修改事件值时,只需要关闭调度器就可以了。
---------------------------------------------------------------------------------------------------------------------------------
解决DHT11经常出错的问题
想要在挡球板任务的顶上中间位置,每隔二秒显示温度,湿度。
想要每隔两秒显示,就使用定时器
可以正常读取,但是为什么使用MPU6050时,有时会导致显示err?
因为MPU6050的优先级的比较高,当DHT11在读取时,被切换。那么DHT11的时序被打断。读取 DHT11 数据的任务被延迟执行,错过了 DHT11 响应的时间窗口,从而无法正确获取温湿度数据。从而显示错误。
所以想要保证DHT11的任务不会出错,就要使用暂停调度器,和恢复调度器
那么当DHT11读取数据时,暂停调度器能阻止 “任务级抢占”
---------------------------------------------------------------------------------------------------------------------------------
三个红框都运用了I2C,I2C总线竞争
多任务 + I2C 的核心风险是 “总线竞争”,需用互斥锁隔离访问,否则会导致数据混乱、任务阻塞、系统实时性下降。