【FreeRTOS】第八课(1):信号量(Semaphore)
目录
一、信号量的本质
信号量使用比喻
信号量使用
二、对比:普通队列和信号量队列
1.普通队列
2.信号量队列
小结
三、函数原型
1.创建
1)二值信号量
静态
动态
2)计数型信号量
静态
动态
2.删除
3.give
在普通任务中give
在ISR(中断)中give
4.take
在普通任务中take
在ISR中take
四、使用示例
五、优先级反转问题
优先级反转发生的逻辑
一、信号量的本质
信号:起提示作用
量:起计数作用
本质是一个不涉及数据传输,只涉及数据统计的队列,通过对信号量的使用可以实现任务的互斥操作
信号量使用比喻
将任务比喻成游客,信号比喻为票,票的数量就是信号量
take事件就是获得票,give事件就是增加票
如果有剩余的票,游客就能拿到票,同时票的数量减一
如果没有剩余的票,游客可以选择景区门口排队等待,vip游客先排队
景区可以增加票(give操作),让票的数量加一,同时唤醒等待的游客,每放一张票就唤醒一个等待的游客
每有一个游客离开景区可以选择保留票或把票上交(give操作),让票的数量加一
当票发到景区可以承载的上限时无需等待直接停止发票,同时返回发票失败的信息
信号量使用
如果有信号量,任务就能够执行,同时信号量减一
如果没有信号量,任务可以选择阻塞或者暂停,在阻塞过程中优先级高的任务会先获得信号量
在任务执行过程中可以使用give函数增加信号量,每增加一个信号量就会唤醒一个任务
每一个任务执行完毕可以选择保留或释放信号量(give操作,让信号量加一)
当信号量增加到上限就不再增加
二、对比:普通队列和信号量队列
1.普通队列
- 有队列长度、读位置、写位置、计数值、发送者链表、接收者链表,用环形缓冲区保存数据,
- 用send往队列里写数据,将数据写入环形缓冲区,计数值加一,提醒接收者有数据了,
- 用receive读取队列里的数据,将数据从环形缓冲区读出并删除,计数值减一,提醒发送者有空位了
2.信号量队列
- 有信号量最大值、计数值、接收者链表,无需环形缓冲区保存数据,
- 增加信号量的操作叫做give,计数值加一,唤醒接收者有信号量可以使用了,
- 其他任务使用信号量叫take,计数值减一,无需唤醒
小结
因为信号量队列不涉及数据的操作,所以相比于普通队列,信号量队列没有用于记录读写位置的指针,也没有读写数据的操作,但有接收者链表用于找到需要唤醒的任务
三、函数原型
数据类型解释
SemaphoreHandle_t | 信号量队列句柄,void* |
StaticSemaphore_t | 一个操作系统已经给出的结构体 |
UBaseType_t | unsigned long |
BaseType_t | long |
TickType_t | uint32_t |
1.创建
1)二值信号量
静态
/* 创建一个二进制信号量,返回它的句柄。* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
动态
/* 创建一个二进制信号量,返回它的句柄。* 此函数内部会分配信号量结构体 * 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
2)计数型信号量
静态
/* 创建一个计数型信号量,返回它的句柄。* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针* uxMaxCount: 最大计数值* uxInitialCount: 初始计数值,每消耗一个就减一* pxSemaphoreBuffer: StaticSemaphore_t结构体指针* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
动态
/* 创建一个计数型信号量,返回它的句柄。* 此函数内部会分配信号量结构体 * uxMaxCount: 最大计数值* uxInitialCount: 初始计数值* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
2.删除
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:
/** xSemaphore: 信号量句柄,你要删除哪个信号量*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
3.give
二进制信号量、计数型信号量的give函数是一样的,take函数也是一样的
在普通任务中give
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
参数 | 说明 |
---|---|
xSemaphore | 信号量句柄,释放哪个信号量 |
返回值 | pdTRUE表示成功, 如果二进制信号量的计数值已经是1,再次调用此函数则返回失败; 如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败 |
在ISR(中断)中give
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
参数 | 说明 |
---|---|
xSemaphore | 信号量句柄,释放哪个信号量 |
pxHigherPriorityTaskWoken | 如果释放信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE |
返回值 | pdTRUE表示成功, 如果二进制信号量的计数值已经是1,再次调用此函数则返回失败; 如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败 |
4.take
在普通任务中take
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
参数 | 说明 |
---|---|
xSemaphore | 信号量句柄,获取哪个信号量 |
xTicksToWait | 如果无法马上获得信号量,阻塞一会: 0:不阻塞,马上返回 portMAX_DELAY: 一直阻塞直到成功 其他值: 阻塞的Tick个数,可以使用*pdMS_TO_TICKS()*来指定阻塞时间为若干ms |
返回值 | pdTRUE表示成功 |
在ISR中take
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
参数 | 说明 |
---|---|
xSemaphore | 信号量句柄,获取哪个信号量 |
pxHigherPriorityTaskWoken | 如果获取信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE |
返回值 | pdTRUE表示成功 |
四、使用示例
static SemaphoreHandle_t g_xSemTicks; //用于接收信号量队列创建函数的返回值static void CarTask(void *params)
{/* 获得信号量,take */xSemaphoreTake(g_xSemTicks, portMAX_DELAY);/* 释放信号量,give *//* 当信号量被释放后,其他任务可以获得信号量执行,从而实现了互斥操作 */xSemaphoreGive(g_xSemTicks);vTaskDelete(NULL);
}void car_game()
{/* 创建动态信号量队列,最大值为3,初始值为1 *//* 意味着同时只有一个任务可以得到执行 *//* 类似于直接创建二值信号量 g_xSemTicks = xSemaphoreCreateBinary(); */g_xSemTicks = xSemaphoreCreateCounting(3, 1); /* 创建三个汽车任务 */xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);
}
五、优先级反转问题
有低中高三个优先级的任务,如果任务创建顺序是高中低,信号量是二值信号量,其中低优先级和高优先级的任务需要信号量才能执行,中优先级的任务无需信号量便能执行
优先级反转发生的逻辑
- 低优先级的任务最后创建,所以最先得到执行,执行一小段时间后发生任务切换,去寻找可以执行的任务,
- 由于高优先级的任务需要得到信号量才能执行,所以跳过执行高优先级的任务去执行中优先级的任务,
- 中优先级的任务执行一小段时间后发生任务切换,但是只会从中优先级切换到高优先级,永远不会切换回低优先级,信号量始终保持在低优先级任务内得不到释放,高优先级任务虽然能被切换,但也永远得不到执行
发生了优先级低的任务阻塞了优先级高的任务,这就是优先级反转