Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
树莓派Pico
SMP
Raspberry-Pico SMP调度移植~
10.00
发布于 2022-05-01 22:52:32 浏览:3393
订阅该版
[tocm] Raspberry pico 是一款双核cortex-m0的处理器,在RT-Thread提供的bsp中目前是默认采用libcpu/arm/cortex-m0,其并没有对多核进行支持。在Coremark的测试中pico的性能很一般,只用一个核心实在是太浪费了,所以下面用一种不太优雅的方式基本实现Pico的SMP,简单测试没有问题,当然由于萌新对于内核的理解程度有限,总是可能存在一些问题,不过总算跑起来了不是😍 # 移植SMP 由于官方的文件完全没有支持Cortex-M的多核,可能设计之初也没有考虑,移植过程感觉蛮别扭的。 首先是几个基本的函数对接: * ```rt_hw_cpu_id```:最首先需要实现的一个也是最容易的实现的一个,直接访问pico的```sio```就可以 ```c int rt_hw_cpu_id(void) { return sio_hw->cpuid; } ``` * ```rt_hw_interrupt_disable/enable```: 在SMP框架当中,关闭中断不只是屏蔽中断,其还要通过spinlock来保证对资源访问的互斥,对于此rtt在```rthw```通过宏定义将其替换,并且重新命名原来的中断控制函数 ```c #define rt_hw_interrupt_disable rt_cpus_lock #define rt_hw_interrupt_enable rt_cpus_unlock #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 ``` * ```rt_hw_spin_lock_xxx```:自旋锁,用于多核之间的资源保护,在rp2040中芯片提供硬件spinlock使用,这一部分同样使用pico-sdk的api即可,选择```unsafe```版本 ```c void rt_hw_spin_lock(rt_hw_spinlock_t *lock) { spin_lock_unsafe_blocking((spin_lock_t *)lock->slock); } void rt_hw_spin_unlock(rt_hw_spinlock_t *lock) { spin_unlock_unsafe((spin_lock_t *)lock->slock); } ``` * ```rt_hw_secondary_cpu_up```:在主CPU启动后,运行调度器,调度器会调用main线程运行,main线程运行前会首先调用该api来启动第二个核心。Rp2040两个核心其实是上电以后同时启动的,CPU-1会在bootrom中被拦截下来进入等待状态,我们可以通过sio的fifo来唤醒第二个核心,pico-sdk中提供了api,可以直接指定CPU-1唤醒后执行的函数。在唤醒过程中同时使能两个CPU的SIO中断,用来进行IPI_Handler. ```c void secondary_cpu_c_start(void) // 其中CPU-1,该函数为入口 { irq_set_enabled(SIO_IRQ_PROC1,RT_TRUE); // 启动该核心的SIO中断,用于IPI systick_config(frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC)*10000/RT_TICK_PER_SECOND); // 配置该核心的systick rt_hw_spin_lock(&_cpus_lock); rt_system_scheduler_start(); } void rt_hw_secondary_cpu_up(void) { multicore_launch_core1(secondary_cpu_c_start); // 启动CPU-1 irq_set_enabled(SIO_IRQ_PROC0,RT_TRUE); // 打开CPU-0的SIO中断 } ``` 在需要调度的时候,CPU之间可能会互相通知让其进行调度,该部分通过```rt_hw_ipi_send```和```rt_hw_ipi_handler```对接, ```c #define IPI_MAGIC 0x5a5a void rt_hw_ipi_send(int ipi_vector, unsigned int cpu_mask) { sio_hw->fifo_wr = IPI_MAGIC; // 通知其他CPU调度 } // 两个CPU SIO实际执行的部分,用来进行调度和一些其他需要沟通的事情 void rt_hw_ipi_handler(void) { uint32_t status = sio_hw->fifo_st; // 清楚中断标志 if ( status & (SIO_FIFO_ST_ROE_BITS | SIO_FIFO_ST_WOF_BITS) ) { sio_hw->fifo_st = 0; } if ( status & SIO_FIFO_ST_VLD_BITS ) { if ( sio_hw->fifo_rd == IPI_MAGIC ) { rt_schedule(); // 如果正确接受指令,进行调度 } } } ``` 上面对接的函数都比较基础,其次是对接上下文的汇编代码部分,这一部分就不是特别顺利了。简单梳理一下Cortex-M的调度流程,```rt_schedule```获取最高优先级的任务然后使能```PendSV```中断并在全局变量中保存调度信息,最后在完成高优先级中断(或者直接进行```PendSV```)后进行实际的上下文切换,在SMP中基本同理,但是由于RT-Thread的SMP是针对Cortex-A提供的,这里出现了一些问题。 首先在调度中必须关注一个函数,```rt_cpus_lock_status_restore(thread)```,其将要调度的线程绑定到当前的cpu上,调用该函数的位置是一个关键问题 ```c void rt_cpus_lock_status_restore(struct rt_thread *thread) { struct rt_cpu* pcpu = rt_cpu_self(); pcpu->current_thread = thread; // 绑定CPU到当前核心 if (!thread->cpus_lock_nest) // 用于第一次调度是解锁spinlock { rt_hw_spin_unlock(&_cpus_lock); } } ``` 在Cortex-A中其在```rt_hw_context_switch```中被调用,这对于Cortex-A是可行的,因为在非中断情况下A核会直接进行线程切换而不需要PendSV,但是对于Cortex-M核心放在这个位置会存在下面一个问题:PendSV是中断,所以需要使能中断才能运行,因此在```rt_hw_context_switch```后立马就有一个```rt_hw_interrupt_enable```,如果M核工作在非SMP框架下这是没有问题的,但是在SMP框架下当前的线程已经变了,而```rt_hw_interrupt_enable```是同当前线程绑定的,所以这里会导致CPU的```scheduler_lock_nest,cpus_lock_nest```错乱,从而导致调度器不能正常工作 ```c rt_base_t rt_cpus_lock(void) { rt_base_t level; struct rt_cpu* pcpu; level = rt_hw_local_irq_disable(); pcpu = rt_cpu_self(); if (pcpu->current_thread != RT_NULL) { register rt_ubase_t lock_nest = pcpu->current_thread->cpus_lock_nest; pcpu->current_thread->cpus_lock_nest++; // 会锁的nest加在变量上 if (lock_nest == 0) { pcpu->current_thread->scheduler_lock_nest++; rt_hw_spin_lock(&_cpus_lock); } } return level; } ``` 基于上面的描述,我考虑把```rt_cpus_lock_status_restore```放在```PendSV```中进行调用,这样就可以保证```scheduler_lock_nest```工作的正确性,但是导致一个更大的问题!!!在```rt_schedule```函数中,如果中断还没有使能的情况下重复调用```rt_schedule```(systick中多层中断)会导致已经被标记为```RUNNING```的线程无法正常被加入到就绪列表中。因为在上一次的```rt_schedule```中线程已经被移除了,其等待在```PendSV```中绑定到当前CPU的时候```rt_schedule```再次到来,其应该被重新加入到就绪列表(如果优先级低的话),但是```schudler```是基于当前CPU上的线程来管理的,由于之前被调度的线程当前还没有绑定,所以线程变成游离状态而无法被调度,就会出现下面的情况: ```shell thread cpu bind pri status sp stack size max used left tick error -------- --- ---- --- ------- ---------- ---------- ------ ---------- --- i-7 0 2 17 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-6 0 2 16 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-5 0 2 15 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-4 0 2 14 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-3 0 2 13 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-2 0 2 12 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-1 0 2 11 running 0x000000b4 0x00000200 35% 0x00000014 -02 i-0 0 2 10 running 0x000000b4 0x00000200 35% 0x00000014 -02 tshell 1 2 20 running 0x000000e4 0x00001000 17% 0x0000000a 000 tsystem N/A 2 30 suspend 0x000000b4 0x00000100 73% 0x00000020 000 tidle1 N/A 1 31 ready 0x00000060 0x00000100 37% 0x00000020 000 tidle0 0 0 31 running 0x00000058 0x00000100 34% 0x00000005 000 main N/A 2 10 suspend 0x000000e8 0x00000800 17% 0x00000014 000 ``` 所以```rt_cpus_lock_status_restore(thread)```只能在```rt_hw_context_switch```中被调用,但这种情况下我们需要处理```scheduler_nest```和```cpus_lock_nest```错乱的问题,由于SMP框架将```nest```绑定到线程上,但实际上锁针对的还是CPU,我也认为将太绑定到CPU上更合适,为了不修改内核源码的情况下实现,我在```rt_hw_context_switch```中将当前cpu线程的```nest```绑定到需要调度的线程上,这样就等价于把```nest```绑定到CPU上,此时就可以正常工作了。 ```c struct __thread_switch_status { uint32_t from; uint32_t to; uint32_t flag; }_thread_switch_array[2]; extern void rt_cpus_lock_status_restore(struct rt_thread *thread); void thread_switch_status_store(uint32_t from, uint32_t to, rt_thread_t thread) { int cpu_id = sio_hw->cpuid; if ( _thread_switch_array[cpu_id].flag == 0) { _thread_switch_array[cpu_id].from = from; _thread_switch_array[cpu_id].flag = 1; } _thread_switch_array[cpu_id].to = to; if ( from != 0 ) { rt_thread_t currrent_cpu_thread = rt_thread_self(); thread->cpus_lock_nest = currrent_cpu_thread->cpus_lock_nest; thread->scheduler_lock_nest = currrent_cpu_thread->scheduler_lock_nest; thread->critical_lock_nest = currrent_cpu_thread->critical_lock_nest; } rt_cpus_lock_status_restore(thread); } ``` ```asm // rt_hw_context_switch PUSH {LR} BL thread_switch_status_store POP {R0} MOV LR,R0 ``` 解决上述问题后知剩下最后一个问题,我们前文的讨论都是基于非中断情况下的,对于Cortex-M而言中断中的调度和非中断中的调度是一致的,都是基于```PendSV```实现的,所以我们```rt_hw_context_switch,rt_hw_context_interrupt_switch```用一套一样的代码就可以,但是在SMP框架中这两个部分具有两个调度函数,在中断中调用```rt_schedule```,SMP框架会直接跳过当前调度并且给当前CPU打上中断调度标记,最后在离开中断的时候调用```rt_scheduler_do_irq_switch(void *context)```来实现,对于Cortex-A的中断结构来说这是没有问题的,只要保证switch能够在本次调度过程中直接切换就行,但是对于Cortex-M这样就不太合适,我们可以把NVIC弄成统一IRQ的样子,但是我觉得直接废弃```rt_scheduler_do_irq_switch```更加合适。 ```c void rt_schedule() { .... /* whether do switch in interrupt */ if (pcpu->irq_nest) { pcpu->irq_switch_flag = 1; rt_hw_interrupt_enable(level); goto __exit; } ... } void rt_scheduler_do_irq_switch(void *context); ``` 为了使得调度器不知道我们在中断状态,我把```rt_interrupt_enter/leave```注释掉了(应该在涉及内核调度的中断中全部采用这种办法),这样```irq_nest```就一直是```0```,调度器也不会去调用do_irq了,其实我们不用这个处理方法也能够工作的,但是中断中就没法调度了,实时性也没法保障。按照我的理解在Cortex-M中这样的处理并不会有太大的问题,但是总不太好是吧hh ```c void isr_systick(void) { /* enter interrupt */ //rt_interrupt_enter(); rt_tick_increase(); /* leave interrupt */ //rt_interrupt_leave(); } ``` 最后基于上面全部的修改,RP2040的SMP能够正常工作,小灯能够按照正常闪烁。 ```shell \ | / - RT - Thread Operating System / | \ 4.1.1 build May 1 2022 20:00:57 2006 - 2022 Copyright by RT-Thread team Hello, RT-Thread! msh >ps thread cpu bind pri status sp stack size max used left tick error -------- --- ---- --- ------- ---------- ---------- ------ ---------- --- i-7 N/A 2 17 suspend 0x000000b4 0x00000200 35% 0x00000012 000 i-6 N/A 2 16 suspend 0x000000b4 0x00000200 35% 0x00000014 000 i-5 N/A 2 15 suspend 0x000000b4 0x00000200 35% 0x00000014 000 i-4 N/A 2 14 suspend 0x000000b4 0x00000200 35% 0x00000014 000 i-3 N/A 2 13 suspend 0x000000b4 0x00000200 35% 0x00000013 000 i-2 N/A 2 12 suspend 0x000000b4 0x00000200 35% 0x00000014 000 i-1 N/A 2 11 suspend 0x000000b4 0x00000200 35% 0x00000014 000 i-0 N/A 2 10 suspend 0x00000094 0x00000200 35% 0x00000014 000 tshell 1 2 20 running 0x000002dc 0x00001000 17% 0x00000009 000 tsystem N/A 2 30 suspend 0x000000b4 0x00000100 73% 0x00000020 000 tidle1 N/A 1 31 ready 0x00000060 0x00000100 37% 0x00000020 000 tidle0 0 0 31 running 0x00000058 0x00000100 34% 0x0000000f 000 main N/A 2 10 suspend 0x000000e8 0x00000800 17% 0x00000014 000 msh > ``` # 最后 我对于RT-Thread的理解还很有限,萌新,有很多问题我可能预料不到,这样的实现方式我也觉得不太优雅,不过总算是跑起来了(肝了两天还是有点累emmm)。后续会优化整理并且再经过一段时间的测试,或许能够喜提自己的第一个RT-Thread PR ~ 最后是关于SMP,我不明白为什么把```nest```绑定到thread而不是cpu上,因为总还是在锁cpu,其次rt-thread的smp似乎是专门给A核设计,目前的多核MCU也有蛮多,希望可以提供一些相关支持。 **注意**: 虽然跑起来了,但是还有一些问题目前没有解决,在评论区有说明
19
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
lliunix
这家伙很懒,什么也没写!
文章
2
回答
6
被采纳
1
关注TA
发私信
相关文章
1
aarch64有计划支持SMP吗
2
SMP重新定义中断处理函数的问题
3
rt_tick_increase()在SMP时只增加当前核的TICK?
4
RISCV smp系统调度异常问题请教
5
qemu-vexpress-a9 在SMP情况下GDB无法调试
6
为什么在k210上使用多核smp总是会卡死
7
RT-Thread SMP核弹碰撞树莓派
8
请教多核SMP功能验证
9
明年开始玩SMP多核处理器
10
建议RT-Thread支持SMP
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
keil_MDK
SFUD
msh
ulog
C++_cpp
MicroPython
本月问答贡献
三世执戟
5
个答案
1
次被采纳
KunYi
4
个答案
1
次被采纳
RTT_逍遥
3
个答案
1
次被采纳
xiaorui
1
个答案
1
次被采纳
JonasWen
1
个答案
1
次被采纳
本月文章贡献
出出啊
1
篇文章
3
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
3
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部