RTOS 中 Task 之间资源共享示例

本文探讨RTOS环境下任务间共享资源的概念及其重要性。通过实例分析,揭示了共享资源保护的必要性和具体方法,旨在帮助开发者理解如何有效管理和保护任务间的共享资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RTOS 中 Task 之间资源共享示例

什么是共享资源

大型项目往往需要创建多个任务,任务之间协同合作完成一个大型的功能。在前述的章节中,我们讲述了任务间的同步与通信,但合作与竞争总是相辅相成的。任务、中断之间除同步与通信外,还存在资源竞争。

共享资源:能够同时被两个以上的**并发程序(包括任务和中断)**使用的全局变量、外设、内存块等,称为共享资源。

对共享资源资源进行保护,以避免不同任务、中断之间能够正常访问共享资源的措施称为**”共享资源的同步“或者”共享资源保护“**。其是”同步“和”保护” 的意思大致相同,同步的目的是为了保护共享资源的正常使用。

以串口为例,若设备只有一个串口,则任务 A 正在使用串口时,任务B就不能使用串口。若两者不加限制的使用串口,则串口输出的数据就会出现乱序,系统功能就会出现混乱。

为了解决这个问题,通常需要规范任务共享资源(如示例中的串口)时的使用方法,最简单的方法是规范任务A、任务B使用串口的顺序,也可以使用先申请再使用的机制避免同时操作串口时的资源冲突问题。然而,在实际使用过程中,由于任务调度的复杂性,我们很难提前预测一些共享资源在什么时候会发生竞争冲突,为了保证在使用这些共享资源时的安全性,写出健壮的代码,我们将在接下来的课程中总结在 Task 之间出现共享资源保护的策略,解决共享资源访问时的冲突问题。

为什么要对共享资源添加保护

最简单的共享资源示例是任务之间共享变量,这可能很简单,但很具备代表性,这个变量在实际中可能是多个软件实体,如上诉示例的串口等。

共享资源需要添加保护的底层原因是:

1)对共享资源的操作实际是多条底层语句完成的。

2)如前述在 RTOS 中的任务调度与三种任务模型讲述的,RTOS 支持中断、高优先级的任务抢占内核后优先运行。

以最简单的算术运算 a = a + 1为例子,其在底层的代码中运行的逻辑如下:

1)拷贝 a 的值到临时寄存器1中
2)使用计算指令计算寄存器1中的值 + 1 的结果,并把结果存储到寄存器2中
3)将寄存器2中的最终结果拷贝到 a 对应的存储区域。

若任务1、任务2同时对变量 a 分别执行减一、加一操作,则对于使用该变量 a 的任务可能出现最终结果为 0 或者 2 的错误,在第三步拷贝的时候,两者会出现竞争导致数据的正确性出现问题:
在这里插入图片描述

同样,共享资源的完整性在并发程序中一样面临威胁。以一个代表时钟数据的全局变量为例,若一个任务负责刷新该变量的值,一个变量使用该值进行显示的屏幕上的操作,则可能出现下面的问题:

typedef struct {
    uint32_t minute; // 分钟
    uint32_t hour; // 时
} capture_counter_t;

在这里插入图片描述
如图所示若原先的时间为 1:59,在更新时间实现进位操作时,若被其他任务、中段打断,则使用该时间的任务会显示1:00,即 minute 已经完成刷新,但hour 还没刷新,这比应该显示的正常时间 2:00,整整少了一个小时。

因此,一些共享的数据必须添加保护措施,其正确性、完整性才能得到保证。

示例解析

一个全局变量,一个任务对其进行加一,同时另一个任务对其进行减1,则该全局变量最终的值是不变吗?

与上述刷新时钟的值类似,提取一个数据的最高位时,若结构体内的部分数据未一起更新,会引起该结构体内数据完整性遭到破坏的问题。

示例输出:

share_counter = 0
cap_counter.counter = 4, ten_place = 0
share_counter = 0
cap_counter.counter = 8, ten_place = 0
share_counter = 4294967295
cap_counter.counter = 16, ten_place = 1
share_counter = 4294967295
cap_counter.counter = 24, ten_place = 2
share_counter = 4294967294
cap_counter.counter = 32, ten_place = 2
share_counter = 4294967294
cap_counter.counter = 40, ten_place = 3
share_counter = 4294967293
cap_counter.counter = 44, ten_place = 4
share_counter = 4294967293
cap_counter.counter = 52, ten_place = 4

如上述 log 显示,数据的准确性被破坏了,尽管两个任务一个给共享数据 share_counter 加1,一个给其减去1,但它的值并不总是0。结构体 cap_counter 的数值在已经为 32 时,提取出的十位数字竟然是 2,可见,该数据的完整性被破坏了。

讨论

1)全局变量一定是共享资源吗?

非也,共享与否主要取决于是否被多个并发程序访问,那些仅被一个任务或中断使用的全局变量不是共享资源。

2)所有共享资源都必须进行资源保护措施吗?

非也,一些共享资源是只读的,即不存在一个任务正在读的时候,该资源的内容发生改变的情况。则这类只读的共享资源是不必添加保护措施的。其是需要保护的主要是那些动态的共享资源。

3)读取数据时一定不需要进行资源保护吗?

很多初学者,认为仅当一个对象存在多个写入的并发程序时才需要保护,在读取对象(使用对象)的情况下完全不需要保护。这显然也是错误的。仍以上述显示时钟的示例为例,在显示时间的任务获取系统时间时,仍旧存在仅获取了分钟,就被打断的情况。即原先是 1:59,已经获取了minute = 59,此时被更新时间的中断打断获取hour = 1,在中断响应结束后,hour 进位,此时时间变成了2:00。显示任务此时取获取 hour = 2。最终显示的时间会是:2:59,数据的完整性还是被破坏了。这种情况下,不仅仅是更新系统时间的任务、中断需要添加保护,就连获取系统时间的任务也需要添加保护,才能保证数据的完整性。

4)什么情况下容易出现对共享数据的竞争?

  • 任务的时间片用完。比如同优先级任务共享一个时间片的情况。
  • 发生抢占。高优先级任务、中断(中断的优先级比任何任务都高,无条件抢占任务的运行权)就绪。
  • 任务发生延时、阻塞。任务在处理一部分数据时因延时或申请资源失败而被迫让处 CPU 使用权的情况。

5)对共享资源添加保护的核心思想是什么?

尽可能影响小(对系统实时性、其他任务、中断)的情况下,实现资源在一定时间的独占。即关键部分在一定的时间范围内只有一个对象(任务或中断)可以访问。我们将在后续的章节中详细介绍这点。

总结

1)能够同时被两个以上的**并发程序(包括任务和中断)**使用的全局变量、外设、内存块等,称为共享资源。识别哪些部分是共享资源是很重要的,这是下述章节的起点。

2)共享资源需要添加必要的措施保证其准确性、完整性。

3)共享资源需要添加保护的底层原因是对共享资源的操作实际是分步骤完成的,并且 RTOS 支持中断、高优先级的任务抢占内核后优先运行。

4)共享资源添加保护的核心思想是尽可能影响小的情况下,实现资源在一定时间的独占。

小伙伴们务必了解上述知识,了解实现资源保护的必要性,我们将在下述章节讲述任务与任务、任务与中断、中断与中断之间如何实现共享资源的保护。

资源链接

1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)

3)下一篇:通过临界区实现 RTOS 中任务之间共享资源的保护

### RTOS任务节点的定义和作用 在实时操作系统(RTOS)中,任务节点是一个重要的概念,它通常用来表示任务之间的逻辑关系以及调度器如何管理这些任务。以下是关于任务节点的具体定义及其作用: #### 定义 任务节点可以被理解为一个数据结构或对象,用于存储与特定任务相关的信息。这种信息可能包括但不限于任务状态、优先级、堆栈指针以及其他控制参数。通过任务句柄[^1],开发者能够访问并操作该任务的相关属性。 #### 作用 任务节点的主要功能在于支持系统的多任务管理和资源分配。具体来说,其作用体现在以下几个方面: 1. **任务管理** - 使用任务句柄,可以通过 FreeRTOS 或其他 RTOS 的 API 对任务进行挂起、恢复、删除等操作。 - 这些操作依赖于任务节点中的状态字段来判断当前任务是否处于就绪态 (`RT_TASK_READY`)[^3]、运行态 (`RT_TASK_RUNNING`) 或者延迟等待态 (`RT_TASK_PEND_DLY`) 等。 2. **任务间通信与同步** - 任务节点不仅限于保存单一任务的状态信息,还参与更复杂的交互过程。例如,在某些情况下,当多个任务需要共享有限资源时,可通过任务节点实现信号量传递或消息队列通讯。 3. **定时行为调整** - 如果希望某个周期性执行的任务具备固定的总耗时特性,则单纯依靠 `vTaskDelay()` 可能无法满足需求[^2]。此时,借助更为精细的时间计算方法结合任务节点内的计数变量即可达成目标。 4. **错误检测与调试辅助** - 当系统发生异常状况时,检查各个活跃任务对应的任务节点有助于定位问题根源。比如查看是否有任务长期停留在睡眠模式(`RT_TASK_SLEEP`)而未唤醒[^3]。 下面给出一段简单的代码示例展示如何利用FreeRTOS创建新任务并通过获取到的手柄对其进行基本操控: ```c // 创建一个新的任务函数原型声明 void vATaskFunction(void *pvParameters); // 声明全局变量以便后续引用此任务手柄 TaskHandle_t xCreatedTaskHandle; // 调用xTaskCreate()生成指定名称的新线程并将返回值赋给上面定义好的handel xTaskCreate(vATaskFunction, "MyTask", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, &xCreatedTaskHandle ); if( xCreatedTaskHandle != NULL ){ // 此处可加入更多基于有效task handle的操作指令... } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网老王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值