首页 > 其他分享 >RT-Thread内核-RT-Thread SMP 介绍与移植

RT-Thread内核-RT-Thread SMP 介绍与移植

时间:2024-02-06 14:25:53浏览次数:35  
标签:RT rt Thread SMP 任务 CPU

转载

原文 https://blog.csdn.net/kingpower2018/article/details/134365819

 

RT-Thread内核-RT-Thread SMP 介绍与移植①

 

RT-Thread内核-RT-Thread SMP 介绍与移植

在这里插入图片描述

RT-Thread SMP 介绍与移植

SMP: 对称多处理(Symmetrical Multi-Processing)简称 SMP,是指在一个计算机上汇集了一组处理器 (多 CPU), 各 CPU 之间共享内存子系统以及总线结构。

RT-Thread 自 v4.0.0 版本开始支持 SMP,在对称多核上可以通过使能 RT_USING_SMP 来开启。该文档主要对 SMP 进行介绍,以及讲解如何移植 RT-Thread SMP 。

多核启动

概述
系统上电后,每个 CPU 都会在 ROM 中的代码控制下独自运行,但是只有主处理器(以下简称 CPU0 )跳转到 RT-Thread 的初始化入口处,而其他的处理器(以下简称次级 CPU )则会暂停在某个状态下,等待 CPU0 将它们唤醒。

CPU0 完成 RT-Thread 的全局初始化过程,包括外设初始化、中断控制器的中断分发部分初始化、全局变量的初始化、全局内核对象的创建等等。它还完成 CPU0 自身硬件初始化,包括 MMU 、中断控制器的 CPU 接口部分,以及中断向量表等。

最终,CPU0 在执行 main 线程之前,唤醒其它的次级 CPU,引导它们执行次级 CPU 的初始化代码,这段代码会让各个次级 CPU 去完成自身相关的硬件初始化,并开启任务调度。

此后,系统进入正常运行阶段。系统启动阶段各个 CPU 的动作如下图所示:

image-20210508143753591

值得注意的是,每个次级 CPU 自身硬件部分的初始化不能由 CPU0 完成,因为其自身硬件不能由其它 CPU 访问。

CPU0 启动流程

在 SMP 平台上,启动核心 CPU0 的启动流程和单核 CPU 上的启动过程相同,主要流程如下图所示:

image-20210508143828745

由于硬件平台和编译器的不同,系统上电后执行的初始化代码和启动流程并不相同,但是系统最终统一调用入口函数 rtthread_startup() 来启动 RT-Thread 。该函数设置硬件平台、初始化操作系统各组件、创建用户 main 线程,并最终开启当前 CPU 的任务调度机制,开始正常工作。

当开启了 CPU0 的任务调度器之后,CPU0 上通常存在两个线程:main 线程和 idle 线程,用户可以通过修改配置选项或者通过 RT-Thread 提供的接口创建新的线程,调度器依据优先级和线程状态从中选择就绪线程执行。

次级 CPU 启动流程

如果定义了配置选项 RT_USING_SMP ,CPU0 的 main 线程在运行过程中会执行函数 rt_hw_secondary_cpu_up() 以启动其它 CPU 核心。该函数由移植内核的开发人员提供,完成以下两个操作:

设置次级 CPU 的启动入口地址;
加电启动次级 CPU 。
在 ARMv7-A 中,次级 CPU 的启动入口地址固定设置为 secondary_cpu_start ,该标号定义在文件 libcpu/arm/cortex-a/start_gcc.S 中,主要步骤包括设置当前 CPU 的内核栈,建立 MMU 内存映射表,然后跳转到函数 secondary_cpu_c_start() 执行。 函数 secondary_cpu_c_start() 是所有次级 CPU 的初始化函数,它同样与硬件平台密切相关,由移植系统的开发者提供,需要完成以下步骤:

初始化当前 CPU 的中断控制器,设置中断向量表;
设置定时器为当前 CPU 产生 tick 中断;
获取内核自旋锁 _cpus_lock 以保护全局任务表,调用函数 rt_system_scheduler_start() 开启当前 CPU 的任务调度器。
全志 T3 芯片采用 GIC 中断控制器,系统通过 Generic Timer 定时器提供 tick 中断计数,函数 secondary_cpu_c_start() 代码如下:

image-20210508143912633

每个次级 CPU 启动之后,从全局任务表和当前 CPU 的局部任务表中选取优先级最高的任务执行,在优先级相同的情况下,优先选择当前 CPU 的局部任务表中的任务执行。

在不存在其它任务的情况下,每个 CPU 调度自己的 idle 任务执行。其中,CPU0 的 idle 任务循环执行函数 rt_thread_idle_execute() ,而次级 CPU 的 idle 任务循环执行函数 rt_hw_secondary_cpu_idle_exec() 。后者同样需要移植系统的开发者提供,实例代码如下:

image-20210508143934778

多核调度

任务特性

RT-Thread 中的任务分为以下状态:

  • 运行态:任务正在某个 CPU 上执行;
  • 就绪态:任务随时可以被执行,但尚未分配到 CPU ,因此等待在某个就绪态任务表中;
  • 挂起态:任务因为条件不满足(等待超时或者数据到来等),而不能够被执行;
  • 关闭态:任务已经被删除,正在等待被回收。
    在进入正常运行阶段后,每个 CPU 都独自地运行中断处理、调度器以及任务的代码。RT-Thread 在多核系统上运行时存在以下特性:

同一时刻,一个任务(线程)只会运行在一个 CPU 上;
每个 CPU 互斥地访问全局调度器数据,以确定将要在当前 CPU 上运行的任务;
支持将任务绑定在某一个 CPU 上运行。

调度策略

为了实现上述目标,RT-Thread 调度器实现了两种就绪任务队列:

  • 全局就绪任务表 rt_thread_ready_table[] ,包含没有绑定 CPU 的就绪任务;
  • CPU 局部就绪任务表 ready_table[] ,每个 CPU 对应一个,包含绑定到对应 CPU 的就绪任务。典型的 CPU 绑定任务是每个 CPU 自己的 idle 任务。
    当 CPU 需要切换任务执行时,任务调度器查找系统中优先级最高的就绪任务执行,即全局就绪任务表和当前 CPU 的局部就绪任务表中优先级最高的任务。在优先级相同的情况下,优先选取局部任务表中的任务。

相对应的是,如果一个任务由其它状态变为就绪态,则进行如下处理:

  • 如果它不是 CPU 绑定任务,则把它挂入全局就绪表,并向其它的所有 CPU 发送 IPI 中断,通知它们检查是否需要切换任务,因为其它 CPU 的当前任务的优先级可能低于此就绪态任务,因而会发生优先级抢占;
  • 如果它是一个 CPU 绑定任务,检查它是否比对应 CPU 的当前任务优先级高,如果是则发生优先级抢占,否则把它挂入对应 CPU 的局部就绪任务表。整个过程不通知其它 CPU 。

SMP 内核接口

为支持 SMP 平台,RT-Thread 提供以下内核接口,以方便内核开发人员使用多核的功能。

处理器间中断 IPI
当单个 CPU 上运行的任务改变了系统状态,或者触发了某个事件,需要通过处理器间中断(Inter-Processor Interrupt)通知其它 CPU ,其它 CPU 在收到该信号后,调用注册的相应例程进行处理。

RT-Thread 提供如下 IPI 接口:

void rt_hw_ipi_send(int ipi_vector, unsigned int cpu_mask)
 
  • 1

该函数用来向 CPU 位图中表示的 CPU 集合发送指定编号的 IPI 信号。

void rt_hw_ipi_handler_install(int ipi_vector, rt_isr_handler_t ipi_isr_handler)
 
  • 1

该函数为当前 CPU 设置指定编号 IPI 信号的处理函数。

OS Tick

在 SMP 系统中,每个 CPU 维护自己独立的 tick 值,用作任务运行计时以及时间片统计。除此之外,CPU0 还通过 tick 计数来更新系统时间,并提供系统定时器的功能,次级 CPU 不需要提供这些功能。

在初始化次级 CPU 的过程中,每个 CPU 需要使能各自的 tick 定时器,并注册相应的 tick 中断处理函数。使能 tick 定时器的操作与具体的硬件平台相关,需要移植内核的开发者提供;而 tick 中断处理函数主要完成两个动作:

  • 设置 tick 定时器的状态。
  • 增加当前 CPU 的 tick 计数。
自旋锁 spinlock

在 SMP 系统中,通过关中断的方式不能阻止多个 CPU 对共享资源的并发访问,需要通过自旋锁机制进行保护。和互斥锁类似,在任何时刻,自旋锁最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。不同的是,对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。而自旋锁不会引起调用者睡眠,而是循环查询直到该自旋锁的保持者已经释放了锁。

RT-Thread 提供自旋锁接口:

1、如下函数初始化已分配的 spinlock 变量:

void rt_spin_lock_init(struct rt_spinlock *lock)
 
  • 1

2、如下函数获取 spinlock,忙等待直到获取成功:

void rt_spin_lock(struct rt_spinlock *lock)
 
  • 1

3、如下函数释放 spinlock :

void rt_spin_unlock(struct rt_spinlock *lock)
 
  • 1

4、如下函数禁止当前 CPU 中断后获取 spinlock,忙等待直到获取成功,返回之前的中断状态:

rt_base_t rt_spin_lock_irqsave(struct rt_spinlock *lock)
 
  • 1

5、如下函数释放 spinlock ,并恢复之前的中断状态:

void rt_spin_unlock_irqrestore(struct rt_spinlock *lock, rt_base_t level)
 
  • 1
任务绑定

通常系统中的就绪任务位于全局就绪任务表中,每个任务在哪个 CPU 上调度运行是随机的。通过将就绪任务放入到某个 CPU 局部就绪任务列表中,RT-Thread 允许将任务与 CPU 绑定,即该任务只能够在指定的 CPU 上。

RT-Thread 提供的任务绑定接口如下:

rt_err_t rt_thread_control (rt_thread_t thread, int cmd, void *arg)
 
  • 1

当参数 cmd 的值为 RT_THREAD_CTRL_BIND_CPU 时,函数将线程 thread 绑定到参数 arg 指定的 CPU 上。

移植说明

为了将 RT-Thread 系统移植到其它 SMP 平台上,需要创建相应的 BSP ,编写底层代码。这里我们以全志 T3 芯片(四核 Cortex-A7 )为模版,说明 RT-Thread 在多核 Cortex-A 平台上的移植步骤。内核开发者可参考这些步骤将 RT-Thread 移植到其它多核 Cortex-A 芯片上运行。

编译环境准备

RT-Thread 使用 scons 工具进行编译和配置,以操作系统 Ubuntu 18.04 为例说明编译环境,需要安装以下软件包:

  • lib32ncurses5
  • lib32z1
  • python
  • scons
  • python-pip
  • make
  • zlib1g-dev
  • binutils-arm-none-eabi
创建 BSP 目录及文件模版

当向其它 SMP 平台移植时,可使用全志 T3 BSP 做为模版,复制文件夹 bsp/allwinner_t3/ ,名字更改为目标平台,例如 bsp/some_cortexa_smp ,保留以下文件:

  • Kconfig:BSP 配置说明文件,用来组织 BSP 的配置选项
  • link.lds:生成 BSP 可执行文件所使用的链接文件
  • .config:BSP 配置结果文件,提供默认的配置选项
  • SConscript:scons 配置文件,和子目录的 SConscript 文件一起记录待编译的源文件
  • SConstruct:scons 配置文件,用来设置 BSP 编译环境
  • applications/main.c:提供用户代码入口 main 函数
  • drivers/:
  • board.c 和 board.h:包含 PCB 板上的初始化代码
  • drv_clock.c 和 drv_clock.h:包含时钟树设置代码
  • platsmp.c 和 platsmp.h:包含 SMP 启动代码
配置 SMP 核心数量

进入 BSP 目录 bsp/allwinner_t3 运行命令 scons --menuconfig 配置 BSP 的核心数量,修改配置选项 RT_CPUS_NR ,将该值修改为实际的 SMP 核心数量,如下图所示:

image-20210508144131262

配置 MMU 映射的地址范围

RT-Thead 采用直接映射的方式,内核和所有任务共享同一个地址空间,物理内存地址和设备寄存器地址空间采用不同的映射属性,但是映射的虚拟地址和物理地址相一致。

系统在初始化过程中,参照数组 platform_mem_desc[] 的设置建立系统唯一的 MMU 映射表。数组中每一项对应一个地址映射范围的描述,其中的元素依次为起始物理地址、结束物理地址、起始虚拟地址和映射属性。

该 MMU 映射配置数组定义在文件 drivers/board.c 中。以全志 T3 为例,其 DDR 内存起始地址为 0x40000000 ,结束地址为 0xC0000000;而设备地址范围从 0x01000000 到 0x40000000 ,因此数组 platform_mem_desc[] 的内容如下:

image-20210508144207430

设置内核加载地址

编译生成的内核镜像通常加载到内存的起始地址执行,内核链接时所使用的起始地址要和该值保持一致,该地址由链接脚本 link.lds 决定。以全志 T3 为例,DDR 内存起始地址为 0x40000000 ,因此内核镜像的加载地址也设置为 0x40000000 ,内容如下:

image-20210508144218109

移植时,将该值修改为内核镜像的实际加载地址,通常为物理内存的起始地址。

实现次级 CPU 的启动代码

按照上面次级 CPU 启动过程的描述,在将 RT-Thread 移植到其它 ARMv7-A SMP 芯片的过程中,内核开发者需要提供以下三个函数:

  1. rt_hw_secondary_cpu_up(), 该函数设置次级 CPU 的启动入口地址为 secondary_cpu_start ,加电启动其它 CPU 核心;

  2. secondary_cpu_c_start(), 该函数用来初始化单个次级 CPU ,主要包括初始化中断控制器接口,设置中断向量表,以及当前 CPU 的 tick 中断。最后获取内核自旋锁 _cpus_lock ,并调用函数 rt_system_scheduler_start() 开启当前 CPU 的任务调度器;

  3. rt_hw_secondary_cpu_idle_exec(), 该函数被次级 CPU 的 idle 线程循环调用,可用来做功耗相关的处理。

上述三个函数定义在文件 drivers/platsmp.c 中。其中,只有函数 rt_hw_secondary_cpu_up() 的功能实现与芯片密切相关,需要移植者根据芯片特性提供。如果芯片使用的不是 GIC 中断控制器和 Generic Timer 定时器,那么同样需要重新实现函数 secondary_cpu_c_start() 。

需要注意的是,在全志 T3 平台上,Generic Timer 的工作频率为 24 MHZ ,tick 的频率为 1000 ,所以中断间隔寄存器 CNTP_TVAL 设置为 24M / 1000 = 24000 。在 tick 的中断处理代码中需要重新设置间隔寄存器,代码如下:

image-20210508144258754

该值在每次 tick 中断被触发时重新设置,中断处理代码见上面 OS tick 的说明。如果目标平台的工作频率与上述值不一致,修改函数 gt_set_interval() 的参数进行设置。

我有疑问: [email protected]

  文章知识点与官方知识档案匹配,可进一步学习相关知识 Java技能树首页概览140088 人正在系统学习中  

标签:RT,rt,Thread,SMP,任务,CPU
From: https://www.cnblogs.com/zxdplay/p/18009623

相关文章

  • 无涯教程-Math.cbrt(x)函数
    此方法返回数字的立方根。Math.cbrt(x)-语法Math.cbrt(x);x  - 代表数字Math.cbrt(x)-返回值返回数字的立方根。Math.cbrt(x)-示例console.log("---Math.cbrt()---")console.log("Math.cbrt(27):"+Math.cbrt(27))console.log("Math.cbrt(22):"......
  • 华为云软件开发生产线CodeArts开发者实践8件套——开发者的进阶宝典!
    华为云软件开发生产线CodeArts是一站式DevSecOps平台,集华为多年研发实践,前沿研发理念,领先研发工程能力于一体,覆盖软件开发全生命周期,开箱即用,为您提供软件开发的一切。为帮助开发者快速上手CodeArts,我们汇聚了精品视频课程、在线动手实验、职业认证及丰富示例代码,助您扫平产品使用......
  • Docker Arthas 实战指南
    Arthas是一款强大的Java诊断和调试工具,它能够在生产环境中实时诊断Java应用,提供强大的调试功能,帮助开发者和运维人员解决各种Java应用的性能问题和调试挑战。本指南将介绍如何在Docker环境中使用Arthas进行实战。官方文档GitHub地址gitee地址应用场景性能分析与优化:Art......
  • strtol() 函数
    功能概述:字符串转换为数值。其声明 位于头文件<stdlib.h>C库函数 longintstrtol(constchar*str,char**endptr,intbase) 把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为longint型),base必须介于2和36(包含)之间,或者是特殊值0。参数str--......
  • FreeRTOS_消息缓冲区
    问题:消息缓冲区发送失败现象:1、创建消息缓冲区成功,xBufferSizeBytes设置为8; MessageBufferHandle_txMessageBufferCreate(size_txBufferSizeBytes);2、发送失败xBytesSent=xMessageBufferSend(UartToLed_MBHandle_t,Led_Control_Buf,8,100);原因:消息缓冲区创建......
  • 十八张图带你入门实时监控系统HertzBeat
    我们经常讲:研发人员有两只眼睛,一只是监控平台,另一只是日志平台。在对性能和高可用讲究的场景里,监控平台的重要性再怎么强调也不过分。这篇文章,我们聊聊开源实时监控告警系统HertzBeat赫兹跳动。1产品特色HertzBeat有两个非常鲜明的特色:强大的监控模版和无需Agent。1.1......
  • RTSS 降帧 减少游戏卡顿
    原理:降低帧数,减少CPU/GPU的负担,让GPU可以比较平均地产出视频帧,均匀的帧数可以减少卡顿感。使用方法:下载安装MSIAfterburnerhttps://www.msi.com/Landing/afterburner/graphics-cards(会附带RTSS)打开MSIAfterburner,设置监控功能,对应想要监控的勾选OSD选项,方便查看效果......
  • prometheus之node_exporter安装
    一、简介node_exporter用来安装到被监控的主机上,暴露被监控主机的指标数据,服务器端基于http协议调用的端口9100(默认)来获取被监控服务器信息。二、安装部署下载地址https://github.com/prometheus/node_exporter/releases1、解压安装#tar-xvfnode_exporter-*.linux-amd6......
  • openWrt使用rclone挂载webDav
    前言觉得路由器(linux)硬盘太小,又不好扩展(x86机器可以插硬盘、但arm机器的硬盘是焊死的无法扩展)。这个时候,我们可以通过davfs或者rclone将外部资源如webDav挂载到本机上用来作为自己的硬盘。安装rclone#新版的rclone依赖fuse3,所系需要安装(尽管rclone的子依赖包含fuse但那是旧版......
  • C#中Thread和Task的区别
    https://blog.csdn.net/happyjava2/article/details/131411791Thread和Task是.NET框架中用于实现多线程编程的两个重要概念。它们的主要区别如下:1、基于不同的.NET框架:Thread是基于Windows操作系统提供的API实现,而Task则是基于.NET框架提供的TPL(TaskParallelL......