freeRTOS中互斥锁与信号量使用?
目录
为什么互斥锁不适合用于交替闪烁任务?
使用互斥锁的问题
为什么信号量适合交替任务
问题 1:任务抢占问题
问题 2:死循环阻止任务切换
问题 3:调度器无法均衡运行时间
信号量 vs 互斥锁的选择
本文是因为在学习到freeRTOS的时候,使用互斥锁来实现灯的交替闪烁,但是实现不了,原因是我单纯的以为使用互斥锁来进行阻塞等待别的任务当中来释放信号,就是将互斥锁当成了信号量来使用了,如果换成信号量,就可以实现灯的交替闪烁,但是使用互斥锁就不能够实现。
为什么互斥锁不适合用于交替闪烁任务?
互斥锁本质上是用于保护共享资源的,它允许一个任务独占对某个资源的访问,其他任务如果尝试获取该资源的访问权,会被阻塞,直到当前任务释放互斥锁。这在你目前的场景下并不合适,因为:
- 任务之间的交替闪烁本质上是同步行为,即一个任务在完成后需要“通知”另一个任务执行。这不是共享资源竞争问题,而是典型的任务同步问题。
- 互斥锁的目的是为了保护临界区,即防止多个任务同时访问同一资源。而你的任务交替执行红灯和绿灯操作,它们并没有共享资源,只是需要等待和交替运行,这就是信号量的作用。
使用互斥锁的问题
如果你使用互斥锁,会遇到以下问题:
-
互斥锁不适用于通知机制:当一个任务执行完并释放互斥锁,另一个任务并不会因此自动开始执行,互斥锁的目的仅是防止任务同时操作同一资源,而不是通知任务何时该执行。这样,互斥锁可能会导致两个任务都尝试获取同一个锁,进而都等待对方释放锁,无法实现交替闪烁。
-
没有明确的同步控制:互斥锁的设计是为了短时间内锁住资源,而不是用来协调任务之间的顺序。两个任务可能在竞争锁,而不是通过信号量明确地交替执行任务。
为什么信号量适合交替任务
信号量更适合你这种场景的原因是,它本质上用于任务间的同步和控制执行顺序:
- 当一个任务完成时,它可以释放信号量,让另一个任务知道它已经完成并可以执行。
- 二值信号量可以确保两个任务依次执行,一旦一个任务执行完毕并释放信号量,另一个任务才能继续运行。
- 信号量是用于任务间通信的,它不是为了保护资源,而是为了协调任务之间的执行顺序。
void led_r_function()
{while(1){//加锁xSemaphoreTake(led_mutex,1000);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_7,GPIO_PIN_RESET);vTaskDelay(1000);//解锁xSemaphoreGive(led_mutex);}
}
void led_g_function()
{while(1){//加锁xSemaphoreTake(led_mutex,1000);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_7,GPIO_PIN_SET);vTaskDelay(1000);//解锁xSemaphoreGive(led_mutex);}}
使用互斥锁的错误代码,
从这方面可以转入到freeRTOS的任务调度机制当中,在我两个任务是同一优先级的情况下
问题 1:任务抢占问题
- 互斥锁的优先级反转:互斥锁本身支持优先级继承(Priority Inheritance),这意味着当一个低优先级任务持有锁时,等待这个锁的高优先级任务会让低优先级任务“临时继承”高优先级。然而,如果两个任务具有相同优先级,并且一个任务长时间持有互斥锁,另一个任务就会陷入阻塞状态,无法执行。这相当于一方“垄断了资源”。
问题 2:死循环阻止任务切换
- 互斥锁不释放时的死锁风险:如果某个任务在死循环中长时间持有互斥锁,并且没有释放锁的机制(比如它卡在某个逻辑中),另一个任务将一直无法获取锁。即便有互斥锁的优先级继承机制存在,但在死循环中,如果任务没有适当的阻塞或者让出 CPU 的行为,会导致另一个任务无法运行。
问题 3:调度器无法均衡运行时间
- 在 FreeRTOS 中,任务之间的切换是基于任务状态和调度器策略的。如果你依靠互斥锁,而不引入明确的阻塞或同步机制(比如通过信号量或延时),那么调度器很难平衡这些相同优先级的任务的执行。一个任务可能会长时间占用互斥锁,阻止其他任务运行。
信号量 vs 互斥锁的选择
目前的场景中,两个任务交替控制 LED(红绿灯交替闪烁),这并不是共享资源保护的问题,而是任务同步的问题。因此,信号量比互斥锁更合适:
- 互斥锁适用于保护临界区、共享资源,而你并没有资源竞争的情况。
- 信号量可以很好地控制两个任务的交替执行,每个任务完成操作后,通过释放信号量唤醒另一个任务,确保任务的交替运行,而不会发生任务饿死或长时间等待的情况。
使用信号量:
void led_r_function()
{while(1){// 释放信号量,交给绿灯任务xSemaphoreGive(led_binary);// 点亮红灯,熄灭绿灯HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET); // 红灯亮HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_RESET); // 绿灯灭vTaskDelay(500); // 延迟500ms// 申请信号量,等待绿灯任务释放信号量xSemaphoreTake(led_binary, portMAX_DELAY);vTaskDelay(500); // 适当延迟以确保任务调度顺序}
}
void led_g_function()
{while(1){// 释放信号量,交给红灯任务xSemaphoreGive(led_binary);// 熄灭红灯,点亮绿灯HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET); // 红灯灭HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_SET); // 绿灯亮vTaskDelay(500); // 延迟500ms// 申请信号量,等待红灯任务释放信号量xSemaphoreTake(led_binary, portMAX_DELAY);vTaskDelay(500); // 适当延迟以确保任务调度顺序}}