FreeRTOS 体验教程:4.如何用临界区解决FreeRTOS多线程访问共享资源的同步性问题?

FreeRTOS 临界区使用教程

临界区(Critical Section)是一种原始的互斥机制,通过其可以实现简单而有效的线程间互斥,确保线程间的数据同步性和稳定性。本文将详细介绍临界区的两种类型,以及如何在 FreeRTOS 中使用临界区和互斥量来管理任务调度和资源访问。

临界区类型

临界区主要有两种类型:

  1. 关闭中断及系统任务的临界区 使用以下 API 函数进入和退出临界区:

    taskENTER_CRITICAL();
    taskEXIT_CRITICAL();
    

    这种方式通过完全禁止中断和任务调度来保护临界区代码,保证代码段在执行时不会被其他任务或中断打断。

  2. 关闭系统任务调度的临界区 使用以下 API 函数挂起和恢复任务调度:

    vTaskSuspendScheduler(); // or vTaskSuspendAll();
    vTaskResumeScheduler();  // or vTaskResumeAll();
    

    这种方式仅禁止任务调度,而不禁止中断。因此,中断仍然可以在临界区内执行。

示例说明

1. 未使用临界区的示例

假设我们希望在任务 1 中输出 10 次信息,然后在任务 2 中输出 10 次信息,最后在任务 3 中输出 10 次信息,这样循环进行。

static unsigned int TestCount = 0;
void CriticalTask1_entry(void* parameter)
{
	while(1)
	{
		for(int i=0; i<10; i++)
		{
			printf("\r\n1.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
	}
}
void CriticalTask2_entry(void* parameter)
{
	while(1)
	{
		for(int i=0; i<10; i++)
		{
			printf("\r\n2.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
	}
}
void CriticalTask3_entry(void* parameter)
{
	while(1)
	{
		for(int i=0; i<10; i++)
		{
			printf("\r\n3.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
	}
}

image.png

2. 实际运行情况

您可能会发现,上述代码在实际运行时输出并不符合预期,而是任务之间会乱序输出。这是因为任务调度的随机性导致的。
image.png

3. 增加临界区

为了确保每个任务能够在输出时不被打断,我们可以使用临界区来保护输出操作。

使用关闭中断及系统任务的临界区
void CriticalTask1_entry(void* parameter)
{
	while(1)
	{
		taskENTER_CRITICAL();
		for(int i=0; i<10; i++)
		{
			printf("\r\n1.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
		taskEXIT_CRITICAL();
	}
}
void CriticalTask2_entry(void* parameter)
{
	while(1)
	{
		taskENTER_CRITICAL();
		for(int i=0; i<10; i++)
		{
			printf("\r\n2.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
		taskEXIT_CRITICAL();
	}
}
void CriticalTask3_entry(void* parameter)
{
	while(1)
	{
		taskENTER_CRITICAL();
		for(int i=0; i<10; i++)
		{
			printf("\r\n3.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
		taskEXIT_CRITICAL();
	}
}

image.png

4. 运行效果

使用上述代码后,您将看到每个任务连续输出 10 次信息,然后切换到下一个任务,达到了预期的效果。
image.png

5. 使用挂起调度器的方法

另一种管理临界区的方法是挂起任务调度器:

void CriticalTask1_entry(void* parameter)
{
	while(1)
	{
		vTaskSuspendAll();
		for(int i=0; i<10; i++)
		{
			printf("\r\n1.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
		vTaskResumeAll();
	}
}
void CriticalTask2_entry(void* parameter)
{
	while(1)
	{
		vTaskSuspendAll();
		for(int i=0; i<10; i++)
		{
			printf("\r\n2.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
		vTaskResumeAll();
	}
}
void CriticalTask3_entry(void* parameter)
{
	while(1)
	{
		vTaskSuspendAll();
		for(int i=0; i<10; i++)
		{
			printf("\r\n3.TestCount: %d", TestCount++);
			vTaskDelay(100);
		}
		vTaskResumeAll();
	}
}

image.png

6. 中断触发情况下的运行效果

在这种情况下,当系统中断发生时,它们依然会被触发并执行。例如:
image.png

7. 运行效果

使用挂起调度器的方法后,即便有其他中断触发,任务仍能顺利执行。中断服务程序(ISR)将被正常执行,并输出信息。
image.png

通过这篇教程,您应该能够理解如何使用临界区和互斥量来管理 FreeRTOS 中的任务调度和资源访问。选择合适的临界区类型和使用场景,可以保证系统的实时性与稳定性。

对应的 demo 源码, 请点击 RtosExPro at freertos_sync_critical

也可扫码关注博主同名公众号"不解之榬",回复 “freeRTOS” 获取
不解之榬


如果您有任何疑问或需要进一步的指导,欢迎在评论区留言,我们将尽快回复! 祝您开发顺利,代码运行稳定!

  • 49
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值