信号量本质 信号量实验(控制车辆运行,优先级反转)互斥量

发布于:2024-11-03 ⋅ 阅读:(64) ⋅ 点赞:(0)

信号量本质

 前面介绍的队列(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 D
153
一开始信号量的计数值为 0 ,如果任务 C D 想获得信号量,会有两种结
果:
阻塞:买不到东西咱就等等吧,可以定个闹钟 ( 超时时间 )
即刻返回失败:不等
任务 A B 可以生产资源,就是让信号量的计数值增加 1 ,并且把等待这个
资源的顾客唤醒
唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的

信号量跟队列的对比

两种信号量的对比 

信号量的计数值都有限制:限定了最大值。如果最大值被限定为 1,那么它就是二进制
信号量;如果最大值不是 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,通常由于内存不足或系统限制。
使用场景
  • 资源池管理:当你有一个固定数量的资源(如线程池、连接池等)时,可以使用计数信号量来管理它们的分配和释放。
  • 任务同步:在多个任务间同步执行,允许一定数量的任务并行访问某些资源。

删除

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete 可以用来删除二进制信号量、计数型信号量,函数原型如下
函数原型
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
参数
  • xSemaphore:要删除的信号量的句柄,类型为 SemaphoreHandle_t
注意事项
  1. 信号量状态:在调用 vSemaphoreDelete() 之前,确保没有任务正在使用该信号量。否则,可能会导致未定义的行为。

  2. 内存管理:使用 vSemaphoreDelete() 后,信号量的句柄将变为无效。后续尝试使用这个句柄(如调用 xSemaphoreTake()xSemaphoreGive())将会引发错误。

  3. 任务优先级:如果信号量被用在多个任务中,确保在所有任务完成其对信号量的使用后再进行删除。

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 中,互斥量是一种特殊类型的信号量,它提供了更强的排他性。

互斥量的特点

  1. 独占性:一次只有一个任务可以拥有互斥量。如果一个任务已经获得了互斥量,其他任务必须等待,直到该互斥量被释放。
  2. 优先级继承:如果一个低优先级任务持有互斥量,而一个高优先级任务试图获取该互斥量,低优先级任务的优先级会暂时提升到高优先级任务的级别,从而减少优先级反转的风险。
  3. 适用于保护共享资源:互斥量常用于保护共享数据或资源,以确保数据的完整性。

互斥量的基本工作原理:

  1. 获取和释放

    • 当一个任务需要访问共享资源时,它会尝试获取互斥量。
    • 如果互斥量当前未被其他任务占用,任务成功获取互斥量并可以安全地访问资源。
    • 如果互斥量已被其他任务占用,当前任务会被阻塞,直到互斥量被释放。
  2. 释放互斥量

    • 当任务完成对资源的操作后,它会释放互斥量,使其他被阻塞的任务能够获取该互斥量并访问资源。

使用互斥量的优势:

  • 数据一致性:通过确保只有一个任务在任何时刻访问共享资源,互斥量可以避免数据冲突和不一致性。
  • 避免死锁:如果设计得当,互斥量可以减少或避免死锁情况的发生。

注意事项:

  • 性能开销:频繁地获取和释放互斥量可能会导致性能下降,因此在设计时要考虑优化。
  • 死锁风险:如果多个任务相互等待对方释放互斥量,可能会导致系统死锁。