信号量本质
前面介绍的队列(queue)可以用于传输数据:在任务之间、任务和中断之间。
消息队列用于传输多个数据,但是有时候我们只需要传递状态,这个状态值需要用一个数值表示,比如:⚫ 卖家:做好了 1 个包子!做好了 2 个包子!做好了 3 个包子!⚫ 买家:买了 1 个包子,包子数量减 1⚫ 这个停车位我占了,停车位减 1⚫ 我开车走了,停车位加 1在这种情况下我们只需要维护一个数值,使用信号量效率更高、更节省内存
信号量的特性
信号量的常规操作
信号量这个名字很恰当:⚫ 信号:起通知作用⚫ 量:还可以用来表示资源的数量◼ 当 " 量 " 没有限制时,它就是 " 计数型信号量 "(Counting Semaphores)◼ 当 " 量 " 只有 0 、 1 两个取值时,它就是 " 二进制信号量 "(Binary Semaphores)⚫ 支持的动作: "give" 给出资源,计数值加 1 ; "take" 获得资源,计数值减 1计数型信号量的典型场景是:⚫ 计数:事件产生时 "give" 信号量,让计数值加 1 ;处理事件时要先 "take" 信号量,就是获得信号量,让计数值减 1 。⚫ 资源管理:要想访问资源需要先 "take" 信号量,让计数值减 1 ;用完资源后"give" 信号量,让计数值加 1 。信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:⚫ 生产者为任务 A 、 B ,消费者为任务 C 、 D153⚫ 一开始信号量的计数值为 0 ,如果任务 C 、 D 想获得信号量,会有两种结果:◼ 阻塞:买不到东西咱就等等吧,可以定个闹钟 ( 超时时间 )◼ 即刻返回失败:不等⚫ 任务 A 、 B 可以生产资源,就是让信号量的计数值增加 1 ,并且把等待这个资源的顾客唤醒⚫ 唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人
信号量跟队列的对比
两种信号量的对比
信号量函数
使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。
创建
二进制信号量
xSemaphoreCreateBinary()
是 FreeRTOS 中用于创建二进制信号量的函数。二进制信号量是一个非常有用的同步机制,适用于任务之间的协调与互斥访问。
函数原型
SemaphoreHandle_t xSemaphoreCreateBinary(void);
返回值
- 成功:返回一个有效的信号量句柄 (
SemaphoreHandle_t
)。 - 失败:返回
NULL
,这通常表示系统内存不足或信号量创建失败。
使用场景
- 任务同步:一个任务可以在完成某个操作后,释放信号量,通知其他任务可以继续执行。
- 互斥访问:确保同一时刻只有一个任务可以访问共享资源,以防数据竞争和不一致性。
计数型信号量
xSemaphoreCreateCounting()
是 FreeRTOS 中用于创建计数信号量的函数。计数信号量可以用于限制对共享资源的访问,支持多个任务同时访问,并且能够管理多个资源的可用数量。
函数原型
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
参数
uxMaxCount
:信号量的最大计数值,表示可以同时获得的最大资源数量。uxInitialCount
:信号量的初始计数值,表示在创建时可用的资源数量。
返回值
- 成功:返回一个有效的信号量句柄 (
SemaphoreHandle_t
)。 - 失败:返回
NULL
,通常由于内存不足或系统限制。
使用场景
- 资源池管理:当你有一个固定数量的资源(如线程池、连接池等)时,可以使用计数信号量来管理它们的分配和释放。
- 任务同步:在多个任务间同步执行,允许一定数量的任务并行访问某些资源。
删除
函数原型
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
参数
xSemaphore
:要删除的信号量的句柄,类型为SemaphoreHandle_t
。
注意事项
信号量状态:在调用
vSemaphoreDelete()
之前,确保没有任务正在使用该信号量。否则,可能会导致未定义的行为。内存管理:使用
vSemaphoreDelete()
后,信号量的句柄将变为无效。后续尝试使用这个句柄(如调用xSemaphoreTake()
或xSemaphoreGive()
)将会引发错误。任务优先级:如果信号量被用在多个任务中,确保在所有任务完成其对信号量的使用后再进行删除。
give/take
give
xSemaphoreGive()
是 FreeRTOS 中用于释放信号量的函数。它的主要功能是将信号量的计数值增加,允许其他任务获取信号量。
函数原型
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
参数
xSemaphore
:要释放的信号量的句柄,类型为SemaphoreHandle_t
。
返回值
pdTRUE
:成功释放信号量。pdFALSE
:释放信号量失败(通常在信号量未被占用时调用时会失败)。
使用场景
- 任务同步:在多任务环境中,当一个任务完成了对某个共享资源的使用后,可以调用
xSemaphoreGive()
释放信号量,允许其他任务访问该资源。 - 计数信号量:在计数信号量中,调用
xSemaphoreGive()
将信号量的计数增加,可以表示资源的可用数量。
take
xSemaphoreTake()
是 FreeRTOS 中用于获取信号量的函数。它的主要作用是尝试获得一个信号量,如果信号量可用,函数将成功返回,并将信号量的计数减一;如果信号量不可用,函数将根据设置的等待时间进行阻塞或立即返回失败。
函数原型
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
参数
xSemaphore
:要获取的信号量的句柄,类型为SemaphoreHandle_t
。xTicksToWait
:等待信号量可用的时间,以滴答数为单位。如果设置为portMAX_DELAY
,则任务将无限期阻塞,直到获得信号量。
返回值
pdTRUE
:成功获取信号量。pdFALSE
:获取信号量失败(在设定的时间内没有获得信号量)。
使用场景
- 任务同步:在多任务系统中,一个任务可能需要在访问共享资源之前先获取信号量,确保其他任务不会同时访问该资源。
- 保护共享资源:通过信号量的机制,可以防止数据竞争和资源冲突。
信号量实验
控制车辆运行
static void CarTask(void *params)
{
struct car *pcar = params;
struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
/* 读取按键值:读队列 */
//xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
/* 控制汽车往右移动 */
//if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=10 ;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
vTaskDelay(50);
if(pcar->x==g_xres-CAR_LENGTH)
{
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
void car_game(void)
{
int x;
int i, j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
g_xSemTicks =xSemaphoreCreateBinary();
xSemaphoreGive(g_xSemTicks);
xSemaphoreGive(g_xSemTicks);
xSemaphoreGive(g_xSemTicks);
/* 画出路标 */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 8; j++)
{
draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16*j, 16+17*i, 8, 1);
}
}
/* 创建3个汽车任务 */
#if 0
for (i = 0; i < 3; i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);
}
#endif
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);
}
优先级反转
static void CarTask1(void *params)
{
struct car *pcar = params;
struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
/* 读取按键值:读队列 */
//xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
/* 控制汽车往右移动 */
//if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=5 ;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
vTaskDelay(50);
if(pcar->x==g_xres-CAR_LENGTH)
{
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
static void CarTask2(void *params)
{
struct car *pcar = params;
struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
vTaskDelay(1000);
//xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
/* 读取按键值:读队列 */
//xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
/* 控制汽车往右移动 */
//if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=4 ;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
mdelay(50);
if(pcar->x==g_xres-CAR_LENGTH)
{
//xSemaphoreGive(g_xSemTicks);
//vTaskDelete(NULL);
}
}
}
}
}
static void CarTask3(void *params)
{
struct car *pcar = params;
struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
vTaskDelay(2000);
xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
/* 读取按键值:读队列 */
//xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
/* 控制汽车往右移动 */
//if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=4 ;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
vTaskDelay(50);
if(pcar->x==g_xres-CAR_LENGTH)
{
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
void car_game(void)
{
int x;
int i, j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
g_xSemTicks =xSemaphoreCreateBinary();
xSemaphoreGive(g_xSemTicks);
xSemaphoreGive(g_xSemTicks);
xSemaphoreGive(g_xSemTicks);
/* 画出路标 */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 8; j++)
{
draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16*j, 16+17*i, 8, 1);
}
}
/* 创建3个汽车任务 */
#if 0
for (i = 0; i < 3; i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);
}
#endif
xTaskCreate(CarTask1, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(CarTask2, "car2", 128, &g_cars[1], osPriorityNormal+2, NULL);
xTaskCreate(CarTask3 , "car3", 128, &g_cars[2], osPriorityNormal+3 , NULL);
}
互斥量
互斥量(Mutex,Mutual Exclusion)是一种用于同步的机制,主要用于控制对共享资源的访问,确保同一时间只有一个线程或任务能够访问该资源。这在多线程或多任务环境中非常重要,以避免数据竞争和不一致的问题。
互斥量(Mutex)是用于保护共享资源的一种同步机制,通常用于防止多个任务同时访问同一个资源,从而避免数据竞争和不一致性。在 FreeRTOS 中,互斥量是一种特殊类型的信号量,它提供了更强的排他性。
互斥量的特点
- 独占性:一次只有一个任务可以拥有互斥量。如果一个任务已经获得了互斥量,其他任务必须等待,直到该互斥量被释放。
- 优先级继承:如果一个低优先级任务持有互斥量,而一个高优先级任务试图获取该互斥量,低优先级任务的优先级会暂时提升到高优先级任务的级别,从而减少优先级反转的风险。
- 适用于保护共享资源:互斥量常用于保护共享数据或资源,以确保数据的完整性。
互斥量的基本工作原理:
获取和释放:
- 当一个任务需要访问共享资源时,它会尝试获取互斥量。
- 如果互斥量当前未被其他任务占用,任务成功获取互斥量并可以安全地访问资源。
- 如果互斥量已被其他任务占用,当前任务会被阻塞,直到互斥量被释放。
释放互斥量:
- 当任务完成对资源的操作后,它会释放互斥量,使其他被阻塞的任务能够获取该互斥量并访问资源。
使用互斥量的优势:
- 数据一致性:通过确保只有一个任务在任何时刻访问共享资源,互斥量可以避免数据冲突和不一致性。
- 避免死锁:如果设计得当,互斥量可以减少或避免死锁情况的发生。
注意事项:
- 性能开销:频繁地获取和释放互斥量可能会导致性能下降,因此在设计时要考虑优化。
- 死锁风险:如果多个任务相互等待对方释放互斥量,可能会导致系统死锁。