前言
最近报名参加了恩智浦社区的 LPC55S69 开发板测评活动,由于其搭载的是一颗 Cortex-M33 Dual Core 的 CPU,而且有大佬已经支持了 RT-Thread 的 BSP,就考虑使其支持 RT-Thread 框架下的 SMP,最近就一直在研究 SMP,并在 Raspberry-Pico 上做了一些实验。以下是一些我在学习过程中的心得体会,不对的地方欢迎大家指正交流~
SMP 简介
SMP: 对称多处理(Symmetrical Multi-Processing)简称 SMP,是指在一个计算机上汇集了一组处理器 (多 CPU), 各 CPU 之间共享内存子系统以及总线结构。虽然同时使用多个CPU,但是从管理的角度来看,它们的表现就像一台单机一样。系统将任务队列对称地分布于多个CPU之上,从而极大地提高了整个系统的数据处理能力。RT-Thread 自 v4.0.0 版本开始支持 SMP,在对称多核上可以通过使能 RT_USING_SMP 来开启。
系统上电后,各 CPU 的启动流程如下图所示:
每个次级 CPU 自身硬件部分的初始化不能由 CPU0 完成,因为其自身硬件不能由其它 CPU 访问。
关于 CPU 已经支持了 SMP 的平台
RT-Thread 的 libcpu 中有一些芯片类型已经支持了 SMP 功能,例如 Cortex-A 系列。对于这样的平台,SMP 的移植工作就会简单很多,我们只需要实现 rt_hw_secondary_cpu_up() ,secondary_cpu_c_start() ,rt_hw_secondary_cpu_idle_exec() 这三个函数即可,具体的移植介绍可以参考 RT-Thread 文档中心SMP 介绍与移植
关于 CPU 还未支持 SMP 的平台
RT-Thread 中还有一些 CPU 是没有支持 SMP 的,例如 Cortex-M 系列的大部分 CPU,练手的 PICO 是 M0 ,准备开发的 LPC55S69 是 M33,都是还没有支持 SMP 的。对于这样的平台移植 SMP 就会相对麻烦。除了 rt_hw_secondary_cpu_up() ,secondary_cpu_c_start() ,rt_hw_secondary_cpu_idle_exec() 这三个函数,我们还需要补充 SMP 所需要的底层支持,主要是中断和调度部分,从而实现更加复杂的共享资源保护,以及线程间的通讯和调度。
推荐大家先去看看 RT-Thread 文档中心的相关资料,以及这篇文章:[RT-Thread学习笔记] 中断锁、调度锁与死锁,最好再去看看 rt-threadsrc 目录下的 scheduler.c 源码。其中有许多 SMP 的相关实现。
CPU ID
scheduler.c 中已经有 SMP 的相关支持,但是会发现,还需要 CPU 的个数及其对应的 ID 等重要参数。所以我们首先要是实现的是 rt_hw_cpu_id() 这个函数。这个需要对应自己开发平台的实际情况。
OS Tick
在 SMP 系统中,每个 CPU 维护自己独立的 tick 值,用作任务运行计时以及时间片统计。除此之外,CPU0 还通过 tick 计数来更新系统时间,并提供系统定时器的功能,次级 CPU 不需要提供这些功能。这部分也是要针对使用的开发开发平台进行配置。
处理器间中断 IPI
处理器间中断(Inter-Processor Interrupt)负责 CPU 之间的相互通讯及处理,针对不同开发的平台,我们还需要实现以下函数:
/* 该函数用来向 CPU 位图中表示的 CPU 集合发送指定编号的 IPI 信号 /
void rt_hw_ipi_send(int ipi_vector, unsigned int cpu_mask)
/ 函数为当前 CPU 设置指定编号 IPI 信号的处理函数 */
void rt_hw_ipi_handler_install(int ipi_vector, rt_isr_handler_t ipi_isr_handler)
调度与同步
临界区保护是需要特别注意的。对于 SMP 通过关中断的方式并不能阻止多个 CPU 对共享资源的并发访问,需要通过自旋锁机制进行保护(在次级 CPU 启动中就需要使用)。我们就还需要实现以下几个函数:
rt_hw_spin_lock_init() /* 初始化已分配的 spinlock 变量 /
rt_hw_spin_lock() / 获取 spinlock,忙等待直到获取成功 /
rt_hw_spin_unlock() / 释放 spinlock */
需要定义自旋锁:
typedef union {
unsigned long slock;
struct __arch_tickets {
unsigned short owner;
unsigned short next;
} tickets;
} rt_hw_spinlock_t;
不使用 SMP 的时候,在进行调度等需要临界资源保护的情况下,是通过 rt_hw_interrupt_disable/enable 来屏蔽中断进行保护,但是在 SMP 中,对 rt_hw_interrupt_disable/enable 进行了替换,从而保证对共享资源访问的互斥:
/* 在 rthw.h 中 */
#ifdef RT_USING_SMP
#define rt_hw_interrupt_disable rt_hw_local_irq_disable
#define rt_hw_interrupt_enable rt_hw_local_irq_enable
#endif
因为 SMP 使用了多核,调度和同步的情况相较于单个处理器更加复杂和重要,这部分是移植的重点。需要针对开发平台的不同,对以下函数进行重写(对于 Cortex-M 内核,需要注意辅助上下文切换的 PendSV 也是一种中断):
/* 实现从当前线程切换到目标线程,在 Cortex-M 内核里 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致 /
rt_hw_context_switch_interrupt:
rt_hw_context_switch:
/ 实现没有来源线程切换到目标线程 /
rt_hw_context_switch_to:
/ PendSV 中断处理函数,在 Cortex-M 内核里 PendSV 辅助完成上下文切换 */
PendSV_Handler:
这部分需要在 context_gcc.S 文件中实现。
移植 SMP 的重点是调度与同步,大家在移植之前,最好对 RT-Thread 的调度流程和中断机制有一定的学习和理解,这部分可以参考 RT-Thread 文档中心,最好能配合着理解源码的实现。我对 RT-Thread 框架下的 SMP 目前的理解就是以上这些,欢迎大家交流讨论。
全部0条评论
快来发表一下你的评论吧 !