一、简介
互斥锁又被称为互斥型信号量,是一种特殊的二值信号量,用于实现对共享资源的独占式处理。
任意时刻互斥锁的状态只有两种:开锁或闭锁。
当有任务占用公共资源时,互斥锁处于闭锁状态,这个任务获得该互斥锁的使用权。
当该任务释放公共资源时,互斥锁被开锁,任务失去该互斥锁的所有权。
当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。
多任务环境下,往往存在多个任务竞争同一共享资源的应用场景。互斥锁可被用于对共享资源的保护,从而实现独占式访问。另外,互斥锁可以解决信号量存在的优先级翻转问题。
更多关于互斥锁的概念以及优先级翻转问题的概念,可以参考如下链接:FreeRTOS学习五(信号量)_freertos信号量用法_t_guest的博客-CSDN博客
Mutex Management
二、 运行机制
多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源时非共享的,需要任务进行独占式处理。此时,就需要互斥锁出面了。
用互斥锁处理非共享资源的同步访问时,如果有任务访问该资源,则互斥锁切换为闭锁状态。此时,其他任务如果想访问这个公共资源,则会被阻塞。直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,并且互斥锁会再次转换为闭锁状态。如此确保在同一时刻,只有一个任务正在访问这个公共资源,保证了公共资源操作的完整性。
优先级翻转,之前的文章中已经介绍过了,这里就不再过多介绍。有兴趣可以看上述的链接。
三、API介绍
osMutexNew
函数功能:
创建互斥锁。不可在中断中使用。
函数原型:
osMutexId_t osMutexNew(const osMutexAttr_t *attr)
参数:
attr:属性,自定义内存时使用。默认NULL
返回值:
锁标识符
实例:
osMutexId_t mutex_id;
mutex_id = osMutexNew(NULL);
osMutexAcquire
函数功能:
获取互斥锁。如果为闭锁状态,则阻塞任务等待,直到超时为止。如果为开锁状态,则切换为闭锁状态。不可在中断中使用。
函数原型:
osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout)
参数:
mutex_id:锁标识符。创建锁osMutexNew时获取。
timeout:等待超时时间。osWaitForever死等
返回值:
osOK:成功
其他值:失败
实例:
osMutexId_t mutex_id;
osMutexAcquire(mutex_id, osWaitForever);
osMutexRelease
函数功能:
释放锁。如果锁为闭锁状态,释放后才能被其他任务获取到。不可在中断中使用。
函数原型:
osStatus_t osMutexRelease(osMutexId_t mutex_id)
参数:
mutex_id:锁标识符。创建锁osMutexNew时获取。
返回值:
osOK:成功
其他值:失败
实例:
osMutexId_t mutex_id;
osMutexRelease(mutex_id);
osMutexDelete
函数功能:
删除锁。不可在中断中使用。
函数原型:
osStatus_t osMutexDelete(osMutexId_t mutex_id)
参数:
mutex_id:锁标识符。创建锁osMutexNew时获取。
返回值:
osOK:成功
其他值:失败
实例:
osMutexId_t mutex_id;
osMutexDelete(mutex_id);
四、实例
创建三个任务,三个任务的优先级递增。同时操作互斥锁,看看结果如何。
#define LOG_I(fmt, args...) printf("<%8ld> - [TIMER]:"fmt"\r\n",osKernelGetTickCount(),##args);
#define LOG_E(fmt, args...) printf("<%8ld>-[TIMER_ERR]>>>>>>>>>>>>:"fmt"\r\n",osKernelGetTickCount(), ##args);
osMutexId_t mutex_id;
void HighPrioThread(void)
{
osDelay(50U);
while (1)
{
LOG_I("HighPrioThread get before");
osMutexAcquire(mutex_id, osWaitForever);
LOG_I("HighPrioThread get after");
osDelay(50U);
LOG_I("HighPrioThread mutex release before");
osMutexRelease(mutex_id);
LOG_I("HighPrioThread mutex release after");
}
}
void MidPrioThread(void)
{
osDelay(10U);
while (1)
{
LOG_I("MidPrioThread get before");
osMutexAcquire(mutex_id, osWaitForever);
LOG_I("MidPrioThread get after");
osDelay(100);
LOG_I("MidPrioThread mutex release before");
osMutexRelease(mutex_id);
LOG_I("MidPrioThread mutex release after");
}
}
void LowPrioThread(void)
{
while (1)
{
LOG_I("LowPrioThread get before");
osMutexAcquire(mutex_id, osWaitForever);
LOG_I("LowPrioThread get after");
osDelay(200U);
LOG_I("LowPrioThread mutex release before");
osMutexRelease(mutex_id);
LOG_I("LowPrioThread mutex release after");
}
}
void Hello_World(void)
{
LOG_I("Test mutex");
mutex_id = osMutexNew(NULL);
if (mutex_id == NULL)
{
LOG_E("Falied to create Mutex!\n");
}
osThreadAttr_t attr;
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.name = "HighPrioThread";
attr.priority = osPriorityNormal2;
if (osThreadNew((osThreadFunc_t)HighPrioThread, NULL, &attr) == NULL)
{
LOG_E("Falied to create HighPrioThread!\n");
}
attr.name = "MidPrioThread";
attr.priority = osPriorityNormal1;
if (osThreadNew((osThreadFunc_t)MidPrioThread, NULL, &attr) == NULL)
{
LOG_E("Falied to create MidPrioThread!\n");
}
attr.name = "LowPrioThread";
attr.priority = osPriorityNormal;
if (osThreadNew((osThreadFunc_t)LowPrioThread, NULL, &attr) == NULL)
{
LOG_E("Falied to create LowPrioThread!\n");
}
}
看结果:
任务1优先级高,任务2优先级中,任务3优先级低。
启动后,任务1延迟500ms执行,任务2延迟100ms执行,任务3不延迟。
注:以下编号与图片上编号对应。
1.任务3率先执行,先获取到互斥锁,使锁为闭锁状态。模拟操作共享数据。
2.任务2延迟完成,开始执行,获取互斥锁,发现为闭锁状态,则挂起任务等待。
3.任务1延迟完成,开始执行,获取互斥锁,发现为闭锁状态,则挂起任务等待。
4.任务3数据操作完成,释放互斥锁。
5.任务1因为优先级最高,即使任务2先等待,但是还是任务1抢占到控制权然后获取互斥锁。然后开始操作数据。
6.任务3继续执行。
7.任务3获取互斥锁,发现为闭锁状态,则挂起任务等待。
8.9。任务1继续执行
10.任务1获取互斥锁。虽然任务1刚才释放了互斥锁,但是任务2一直在等待互斥锁,在任务1释放的瞬间就获取到互斥锁了。所以这里即使任务1优先级高,也无法获取到互斥锁。
11.任务2模拟操作数据
12.任务2操作数据完成,释放互斥锁。
13.任务1因为优先级高,这里获取到互斥锁,模拟操作数据。
这里可能会有疑问,为什么在10阶段,任务2明明优先级低,却能获取到互斥锁。而13这里任务3缺获取不到。这是因为在10阶段,任务2是在任务1释放互斥锁时,等待任务队列中优先级最高的任务。而13阶段,任务2在释放信号量时,任务1早已经在等待了。任务1是等待队列中优先级最高的。所以互斥锁才会被任务1截胡。
从运行结果可以看出来,在同时等待互斥锁时,高优先级的任务总会有限获取到信号量,跟等待顺序是无关的。