单核与多核CPU的区别与联系-结合ESP32浅谈

单核与多核CPU的区别与联系-结合ESP32浅谈

最近一些小伙伴在使用 ESP32 的多核系统时问到了多核使用的一些问题。遂作此文,作为讨论。

什么是多核CPU?

CPU(central processing unit)即中央处理器,包括运算器和控制器。cpu的主要功能就是从存储器中加载一条一条的指令代码,然后放入对应的寄存器中,完成运算和控制,然后将结果写回指定的存储器单元。简言之,CPU 是处理数据的核心单元,是一个办事机构。

大家可以将 CPU 理解为厕所,在商场中,必须有厕所,人们通过上厕所,解决对应的问题。
单核 CPU,就是只有一个厕所的情况。指令代码经过排队,到达卫生间时发现只有一个厕所,大家只能一个一个地排队执行。

此时想要提升上厕所的效率,只有两个办法:
1)让每个人上厕所的时间缩短。很多年前,不断提升 CPU 主频的思路就是这样的。一个 CPU 从原先一秒执行 160 条指令,到一秒执行 240 条指令,当然后者更快。但主频不是可以无限制提高的,就如同将上厕所的时间限制为5 分钟是可行的,而限制成1s是不可行的一样。当主频高到一定程度,会出现CPU发热严重、对工艺要求更高的问题。
2)卫生间多建几个厕所。多个厕所多个坑,多个CPU就多个运算单元,这效率就起来了。多核还有两种情况:对称多核(SMP)、非对称多核(AMP)。其中对称多核是指多个 CPU 都是一样的;非对称多核是指,多个 CPU 中他们的型号和功能不一样,有主次之分。以新建厕所为例:
一个蹲坑+一个蹲坑就是SMP:
在这里插入图片描述
一个蹲坑+一个立式便池就是AMP:
在这里插入图片描述
ESP32 是 SMP 结构的双核 CPU,实际上,当前主要的多核技术,就是 SMP 多核,因此今天主要讨论下 SMP 多核的相关问题。

双核的效率是单核的双倍吗?

答案是 No,我们可以从下面两个问题来讨论具体的影响因素。

1)多核cpu之间的共享资源的数量问题。存储器、寄存器的数量是否一样成倍提供给 CPU了?就如同两个蹲坑的情况下,洗手池是否也同样配备了两个。如果没有配套设施的同步改善,则最终效果还是不能成倍的提升。
2)多核cpu之间的协调管理问题。两个蹲坑的情况下,为了避免一个坑被过度使用,另一个坑完全闲着不用的情况。需要考虑两个蹲坑均衡使用的问题,他们之间缺少不了一个协调者的角色,因此多了些协调者造成的损耗。
当然,只要是两个蹲坑,总还是要比一个蹲坑的情况好一些滴。

多核情况下的编程需要注意什么

最主要的问题仍然是上述提到的两个方面,即共享资源的使用问题,以及多个 CPU 的协调使用问题。比如两个 CPU 共享的内存、外设、中断的处理。以及两个 CPU 上运行的任务的分配。

ESP32 上的 SMP 双核

概述

ESP32 双核的芯片上具有两个相同的内核,称为 CPU0(即协议 CPU 或 PRO_CPU)和 CPU1(即应用程序 CPU 或APP_CPU)。通常,负责处理无线网络的任务(例如,WiFi或蓝牙)将被固定到CPU0(因此名称PRO_CPU),而处理应用程序其余部分的任务将被固定到CPU1(因此名称APP_CPU)。

关于任务创建

关于 CPU 的使用方面,ESP32 允许通过下述 API指定创建的任务运行在哪个 CPU 上

BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, 
																const char *const pcName, 
																const uint32_t usStackDepth, 
																void *const pvParameters, 
																UBaseType_t uxPriority, 
																TaskHandle_t *const pvCreatedTask, 
																const BaseType_t xCoreID)

其中 CoreID 的取值代表了任务运行在 CPU0、CPU1 的具体情况:
0 : CPU0
1 : CPU1
tskNO_AFFINITY: 可以运行在双核上
关于任务创建的更多介绍可以参考:FreeRTOS 创建第一个任务
注意,当使用 xTaskCreate() 创建任务时其相当于使用参数 tskNO_AFFINITY的效果。

关于cpu 核的协调问题

双核情况下,ESP-IDF 为每个 CPU 核心都安排了单独的 systick、idle task、调度器等。因此,对系统的优先级、抢占机制、资源回收,调度器启用与关闭等方面与传统的单核情况不太一样。

如果你没有熟悉过调度相关的概念,可以先熟悉 RTOS 中的任务调度与三种任务模型这篇博客。下面简单讨论优先级、抢占、调度器方面的影响。

双核情况下的优先级不是觉得任务执行的唯一因素

1)单核情况下,优先级高的先运行,但双核情况下的任务的优先级不是唯一决定因素,请看下述示例:

优先级为 10 的任务 A 固定到 CPU0
优先级为 9 的任务 B 固定到 CPU0
优先级为 8 的任务 C 固定到 CPU1
最终将使任务 A 在 CPU0 上运行,任务 C 在 CPU1 上运行。任务 B 不会运行,即使它是第二高优先级的任务。

2)双核情况下抢占机制
单核情况下,高优先级的任务可以立即抢占低优先级任务的 CPU而获得执行,双核情况下优先级不是唯一决定抢占的因素,请看下述示例:
优先级为 8 的任务 A 当前在 CPU0 上运行
优先级为 9 的任务 B 当前在 CPU1 上运行
优先级为 10 的任务 C (是通过 tskNO_AFFINITY 参数创建的任务,是个可以运行在任意核心的任务),若任务 C 由任务 B 唤醒。
则将使任务 A 在 CPU0 上运行,任务 C 抢占任务 B(而不是优先级更低的任务A),因为总是抢占最近的那个 CPU 核心。

双核情况下关闭调度器的影响是独立的

如前所述,每个 CPU 有自己单独的调度器,因此,关闭调度器不是确保在访问共享数据时任务之间相互排斥的有效方法,因为有可能另一个核心仍出现抢占。

两个 CPU 核心上的 SysTick 中断的功能不一样

关于 SysTick的概念和作用,可以参考RTOS 中的任务调度与三种任务模型浅析 FreeRTOS SysTick 和任务延时

ESP32 上的两个 CPU 上的 SysTick 作用不同:

1)CPU0 上的 SysTick 负责:

  • 递增调度程序的发生周期性滴答计数SysTick Count。(因此在 CPU0 上会导致整个调度器的计时时间滞后,虽然恢复调度程序时,xTaskResumeAll() 将追回所有丢失的时间来唤醒超时的任务。)
  • 唤醒超时的任务
  • 检查是否需要时间切片(即触发上下文切换)
  • 执行该 CPU 核心的应用程序 Tick hook 函数

2)CPU1 上的 SysTick 负责:

  • 检查时间切片(即触发上下文切换)
  • 执行该核心的应用程序 Tick hook 函数
同优先级的时间片轮转的均匀性

双核情况下,时间片轮转最终分配的时间可能不是平均的结果,因为不确定两个 CPU 的空闲情况是平均的。若要实现理想的轮循机制时间切片,用户应确保将特定优先级的所有任务固定到同一内核。

总结

1)多核 CPU 从运算数量上提升设备处理数据的速度,从功能上可以将多核 CPU 分为 SMP\AMP 两种。
2)多核情况下速率并没有成倍的提升,因为配套的设备、多核的协调都需要一些小的损耗。
3)使用多核后要考虑共享资源、多核间的协调同步的问题。
4)ESP32 是双核 CPU 结构,相比单核,它的任务创建机制,优先级调度策略、抢占机制、时间片轮转机制、SysTick 的使用机制都有所不同,驱动开发中这些问题尤其重要,应用开发中关注这些将有益于实现更高效的代码。

(感谢点赞或收藏,您的支持是我持续更新的动力)

### ESP32-S2 Arduino FreeRTOS 开发教程 #### 安装必要的软件包和支持库 为了能够在ESP32-S2上使用Arduino IDE进行开发,需先安装支持该硬件平台的相关扩展。打开Arduino IDE首选项页面,在附加板管理器网址处添加ESP32的支持链接[^1]。 接着访问工具->开发板->开发板管理器菜单选项并搜索`esp32`,按照提示完成安装过程。这一步骤确保了后续能够顺利编译运行针对ESP32架构编写的应用程序。 #### 配置FreeRTOS参数 当利用Arduino IDE开展基于FreeRTOS的任务调度时,部分关键配置可通过修改项目根目录下的`platformio.ini`文件实现(如果采用PlatformIO作为IDE的话)。对于标准版Arduino IDE而言,则是在素描(Sketch)内的特定位置定义宏来达到相同效果: ```cpp #define CONFIG_FREERTOS_UNICORE true // 单核模式下启用此行 #define ARDUINO_RUNNING_CORE 0 // 设置心编号为0或1取决于双分配策略 ``` 上述设置允许开发者指定应用执行的心以及是否开启单核还是多核工作模式。 #### 创建简单的FreeRTOS任务 下面给出一段创建两个独立任务并在其中循环打印消息到串口监视器的例子: ```cpp #include "freertos/FreeRTOS.h" #include "freertos/task.h" void TaskBlink(void *pvParameters){ while(1){ Serial.println("Task Blink is running"); vTaskDelay(pdMS_TO_TICKS(1000)); } } void setup() { Serial.begin(115200); xTaskCreate(TaskBlink, "BLINK", configMINIMAL_STACK_SIZE+1024, NULL, 1, NULL); } void loop(){} ``` 这段代码展示了如何初始化一个新线程(`TaskBlink`),它会每隔一秒向终端发送一条状态更新信息。值得注意的是这里调用了来自FreeRTOS库中的函数接口如`vTaskDelay()`用于处理延时操作而不是传统意义上的`delay()`方法。 #### 实现更复杂的功能交互 除了基本的消息输出外,还可以借助队列(queue),信号量(semaphore)等同步原语构建更加复杂的并发逻辑结构。例如可以设计一对生产者-消费者模型的任务间通信机制,其中一个负责采集传感器数据而另一个则专注于数据分析反馈控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

物联网老王

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

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

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

打赏作者

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

抵扣说明:

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

余额充值