单核与多核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 的使用机制都有所不同,驱动开发中这些问题尤其重要,应用开发中关注这些将有益于实现更高效的代码。
(感谢点赞或收藏,您的支持是我持续更新的动力)