目录
- 1、ESP32的双核对称多处理SMP概念
- 2、涉及任务task的特殊性
- 2.1 创建任务的特殊函数
- 2.2 xTaskCreatePinnedToCore()函数的解释
- 3、任务的删除
- 4、总结
1、ESP32的双核对称多处理SMP概念
最初的FreeRTOS(以下简称Vanilla FreeRTOS)是一种小型高效的实时操作系统,支持许多单核MCU和SoC。然而,ESP32和ESP32-S3等,能够进行双核对称多处理(SMP)。因此,ESP-IDF中使用的FreeRTOS版本(以下简称为ESP-IDF FreeRTOS)是Vanilla FreeRTOS v10.4.3的修改版本。这些修改使ESP-IDF FreeRTOS能够利用ESP SoC的双核SMP功能。
==SMP(对称多处理器)==是一种计算体系结构,其中两个或多个相同的CPU(核)连接到单个共享主存储器,并由单个操作系统控制。一般来说,SMP系统的特点如下:
>具有多个独立运行的内核。每个核心都有自己的寄存器文件、中断和中断处理。
>为每个核心提供相同的内存视图。因此,一段访问特定内存地址的代码将具有相同的效果,无论它在哪个内核上运行。
ESP目标上的SMP
ESP目标(例如ESP32、ESP32-S3)是双核SMP SoC。这些目标具有以下硬件功能,使其具有SMP功能:
1、两个相同的核心,称为CPU0(即协议CPU或PRO_CPU)和CPU1(即应用CPU或APP_CPU)。这意味着一段代码的执行是相同的,无论它在哪个核心上运行。
2、对称内存(除了一些小的例外)。2.1 如果多个内核访问相同的内存地址,那么它们的访问将在内存总线级别串行化。
2.2 对同一存储器地址的真正原子访问是通过ISA提供的原子比较和交换指令实现的。3、允许一个CPU在另一个CPU上触发和跨核中断。这允许内核相互发送信号。
CPU0和CPU1的“PRO_CPU”和“APP_CPU”别名存在于ESP-IDF中,因为它们反映了典型的IDF应用程序将如何利用这两个CPU。通常,负责处理无线网络(例如,WiFi或蓝牙)的任务将被固定到CPU0(因此名称为PRO_CPU),而处理应用程序的其余部分的任务将固定到CPU1(因此名称APP_CPU)。
因为ESP32的具有对称多处理的双核,因此在esp-idf中对FreeRTOS的适配需要相应的做出改变。
2、涉及任务task的特殊性
2.1 创建任务的特殊函数
Vanilla FreeRTOS提供以下功能来创建任务:
xTaskCreate()创建一个任务。任务的内存是动态分配的
xTaskCreateStatic()创建一个任务。任务的内存是静态分配的(即由用户提供)
然而,在SMP系统中,需要为任务的创建做适配。
因此,ESP-IDF提供了Vanilla FreeRTOS任务创建功能的PinnedToCore版本:
xTaskCreatePinnedToCore()创建一个具有特定核心关联的任务。任务的内存是动态分配的。
xTaskCreateStaticPinnedToCore()创建一个具有特定核心关联的任务。任务的内存是静态分配的(即由用户提供)
任务创建函数API的PinnedToCore版本不同于它们的普通计数器部分,因为它有一个额外的xCoreID参数,用于指定创建的任务的核心相关性。核心的有效值为:
0,将创建的任务固定到CPU0
1,将创建的任务固定到CPU1
tskNO_AFFINITY,它允许任务在两个CPU上运行
请注意,ESP-IDF FreeRTOS仍然支持普通版本的任务创建功能。然而,它们已经被修改为简单地用tskNO_AFINITY来调用PinnedToCore对应对象。
2.2 xTaskCreatePinnedToCore()函数的解释
创建具有指定相关性的新任务。此函数类似于xTaskCreate,但允许在SMP系统中设置任务相关性。
参数
参数 | 说明 |
---|---|
pvTaskCode | 指向任务入口函数的指针。任务必须实现为永不返回(即连续循环),或者应使用vTaskDelete函数终止。 |
pcName | 任务的描述性名称。这主要是为了方便调试。configMAX_TASK_NAME_LEN定义的最大长度-默认值为16。 |
usStackDepth | 指定为字节数的任务堆栈的大小。请注意,这与普通的FreeRTOS不同。 |
pvParameters | 将用作正在创建的任务的参数的指针。 |
uxPriority | 任务应该运行的优先级。包括MPU支持的系统可以通过设置优先级参数的位portPRIVILEGE_bit,选择性地以特权(系统)模式创建任务。例如,要创建优先级为2的特权任务,uxPriority参数应设置为(2 |
pvCreatedTask | 用于传递回一个句柄,通过该句柄可以引用创建的任务。 |
xCoreID | 如果值为tskNO_AFFINITY,则创建的任务不会固定到任何CPU,并且调度程序可以在任何可用的内核上运行它。值0或1表示任务应固定到的CPU的索引号。指定大于(portNUM_PROCESSORS-1)的值将导致函数失败。 |
返回
如果任务已成功创建并添加到就绪列表,则为pdPASS,否则为文件projdefs.h中定义的错误代码
备注:
ESP-IDF FreeRTOS还在任务创建功能中更改ulStackDepth的单位。Vanilla FreeRTOS中的任务堆栈大小是以字数指定的,而在ESP-IDF FreeRTO中,任务堆栈大小以字节指定。
3、任务的删除
Vanilla FreeRTOS中的任务删除是通过vTaskDelete()调用的。该函数允许删除另一个任务或当前正在运行的任务(如果提供的任务句柄为NULL)。任务内存的实际释放有时会委托给空闲任务(如果要删除的任务是当前正在运行的任务)。
ESP-IDF FreeRTOS提供了相同的vTaskDelete()函数。然而,由于双核的性质,在ESP-IDF FreeRTOS中调用vTaskDelete()时存在一些行为差异。
> 当删除固定到另一个核心的任务时,该任务的内存总是被另一个内核的空闲任务释放(由于需要清除FPU寄存器)。
> 当删除当前在另一个内核上运行的任务时,会在另一内核上触发yield,并且该任务的内存会被其中一个空闲任务释放(取决于该任务的内核相关性)
> 当以下情况,已删除任务的内存将立即释放…
>> 任务当前正在该核心上运行,并且也固定到此核心
>> 该任务当前未运行,也未固定到任何核心
用户应避免对当前正在另一个核心上运行的任务调用vTaskDelete()。这是因为很难知道当前在另一个核心上运行的任务正在执行什么,从而可能导致不可预测的行为,例如:
> 删除持有互斥的任务
> 删除尚未释放先前分配的内存的任务
在可能的情况下,用户应该设计他们的应用程序,使vTaskDelete()只在已知状态的任务上调用。例如:
> 任务在执行完成时自行删除(通过vTaskDelete(NULL)),并且还清理了任务中使用的所有资源。
> 在被另一个任务删除之前,将自己置于挂起状态的任务(通过vTaskSuspend())。
4、总结
在多核环境下,Vanilla FreeRTOS 在任务的创建与删除的行为会在为双核的选择原因而有行为上的改变。这个改变不是很大,但需要考虑到双核环境的特殊性。