目录
引言:怎么独享厕所?自己开门上锁,完事了自己开锁。
你当然可以进去后,让别人帮你把门:但是,命运就掌握在别人手上了。
1.互斥锁的使用场景
使用队列、信号量,都可以实现互斥访问,以信号量为例:
- 信号量初始值为1
- 任务A想上厕所,"take"信号量成功,它进入厕所
- 任务B也想上厕所,"take"信号量不成功,等待
- 任务A用完厕所,"give"信号量;轮到任务B使用
前提是:任务B很老实,不撬门(一开始不"give"信号量) 没有坏人:别的任务不会"give"信号量
可以看到,使用信号量确实也可以实现互斥访问,但是不完美。
使用互斥量可以解决这个问题,互斥量的名字取得很好:
- 量:值为0、1
- 互斥:用来实现互斥访问
它的核心在于:谁上锁,就只能由谁开锁。
很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:
即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。
谁上锁、谁释放:只是约定。
2.互斥锁函数
创建:
互斥量是一种特殊的二进制信号量。
使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。
创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:
/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 创建一个互斥量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer
);
要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:
##define configUSE_MUTEXES 1
其他函数:
要注意的是,互斥量不能在ISR中使用。
各类操作函数,比如删除、give/take,跟一般是信号量是一样的。
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
/* 释放(ISR版本) */
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
/* 获得 */
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
/* 获得(ISR版本) */
xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
3.互斥锁的基本使用
使用互斥量时有如下特点:
- 刚创建的互斥量可以被成功"take"
- "take"互斥量成功的任务,被称为"holder",只能由它"give"互斥量;别的任务"give"不成功
- 在ISR中不能使用互斥量
本程序创建2个发送任务:故意发送大量的字符。可以做2个实验:
- 使用互斥量:可以看到任务1、任务2打印的字符串没有混杂在一起
- 不使用互斥量:任务1、任务2打印的字符串混杂在一起
main函数:
/* 互斥量句柄 */
SemaphoreHandle_t xMutex;
int main( void )
{
prvSetupHardware();
/* 创建互斥量 */
xMutex = xSemaphoreCreateMutex( );
if( xMutex != NULL )
{
/* 创建2个任务: 都是打印
* 优先级相同
*/
xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建互斥量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
发送任务的函数如下:
static void vSenderTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
int cnt = 0;
int task = (int)pvParameters;
int i;
char c;
/* 无限循环 */
for( ;; )
{
/* 获得互斥量: 上锁 */
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("Task %d use UART count: %d, ", task, cnt++);
c = (task == 1 ) ? 'a' : 'A';
for (i = 0; i < 26; i++)
printf("%c", c + i);
printf("\r\n");
/* 释放互斥量: 开锁 */
xSemaphoreGive(xMutex);
vTaskDelay(xTicksToWait);
}
}
其实本质都是队列,只是有着特殊意义方式的队列,并且同步通信与互斥的能力都是一定存在的。