IOT-OS之RT-Thread(二)--- CPU架构与BSP移植过程

一、RT-Thread内核简介

前篇系统启动与初始化过程介绍了RT-Thread总体架构及系统启动和初始化过程,本篇在介绍该系统移植过程前,先介绍下系统内核架构,RT-Thread内核架构图如下:
RT-Thread内核架构图
从上图可以看出,将RT-Thread内核移植到某款芯片或板卡上,可以分为两大部分:CPU芯片移植和板级支持包BSP移植。RT-Thread为了能更方便的在不同CPU架构和不同板卡上移植,分别抽象出libcpu抽象层和BSP设备驱动层,两个抽象层向上对内核提供统一的调用接口,向下分别提供一套CPU架构和BSP移植接口,方便用户将RT-Thread移植到多数CPU架构和板卡上,降低了移植难度和工作量。

最上层是RT-Thread的内核部分,对比前面介绍的UCOS内核部分(UCOS任务调度器任务间通信),最大的区别主要有两个:一个是使用了统一的对象管理架构,另一个是增加了设备管理层。其余的部分如线程管理与调度器、线程间同步与通信、时钟管理与内存管理等RTOS内核基本要素的实现原理跟UCOS类似,只是API接口不同。下面先简单看下RT-Thread的内核对象管理架构:
RT-Thread内核对象模型
RT-Thread采用内核对象管理系统来访问、管理所有内核对象(包括线程、信号量、互斥量、事件、邮箱、消息队列、定时器、内存池、设备驱动等),各类内核对象间有类似面向对象的派生和继承关系,对于每一种具体的内核对象和对象控制块,除了基本结构(抽象对象rt_object)外,还有自己的扩展私有属性。从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。

这种设计方法的优点主要有两个:

  • 提高了系统的可重用性和扩展性,增加新的对象类别很容易,只需要继承通用对象的属性再加少量扩展即可;
  • 提供统一的对象操作方式,简化了各种具体对象的操作,提高了系统的可靠性。

RT-Thread的对象管理架构和设备驱动管理层在后面的博客中再详细介绍,下面看RT-Thread的CPU架构移植与BSP移植过程,以手边的STM32L475潘多拉开发板为例。

二、RT-Thread CPU架构移植

在嵌入式领域有很多种不同的CPU架构(例如Cortex-M / MIPS32 / RISC-V等),为了使RT-Thread能够在不同CPU架构的芯片上运行,RT-Thread提供了一个libcpu抽象层来适配不同的CPU架构,libcpu向上对内核提供统一的接口,包括全局中断开关、线程栈初始化、上下文切换等。下面以Cortex-M CPU 架构为例介绍其移植过程。

2.1 Cortex-M CPU 架构简介

之前介绍UCOS中断管理与定时器时介绍过Cortex-M3的一些基础,本文移植对象STM32L475是Cortex-M4架构的,M4相比M3主要增加了FPU浮点运算单元,相应的增加了不少跟FPU相关的寄存器,Cortex-M4的寄存器主要如下图所示:
Cortex-M寄存器简介
通用寄存器R0-R15每个寄存器的作用如下图所示(参考自:Procedure Call Standard for the ARM® Architecture):
Cortex-M通用寄存器作用
特殊功能寄存器作用如下:
Crotex-M特殊功能寄存器
具有浮点单元的 Cortex-M4 或者 Cortex-M7,控制寄存器也用来指示浮点单元当前是否在使用,浮点单元包含了 32 个浮点通用寄存器 S0~S31 和特殊 FPSCR 寄存器(Floating point status and control register)。

Cortex-M 引入了操作模式和特权级别的概念,分别为线程模式和处理者模式,如果进入异常或中断处理则进入处理模式,其他情况则为线程模式,特权状态或工作模式可由CONTROL特殊寄存器控制,工作模式状态切换情况如下图:
Crotex-M工作模式切换
Cortex-M的中断过程在之前的博客UCOS中断管理与定时器已经介绍过了,这里就不再赘述了,下面介绍RT-Thread对中断的管理机制。

2.2 RT-Thread 中断机制

RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如下图:
RT-Thread中断处理过程
三部分的主要作用如下:

  • 中断前导程序:保存 CPU 中断现场,处理器硬件将当前运行部分的上下文寄存器( PSR、PC、LR、R12、R3-R0 寄存器)自动压入中断栈中,并通知内核进入中断状态;
  • 用户中断服务程序:分为两种情况,第一种情况是不进行线程切换,用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程;另一种情况是,在中断处理过程中需要进行线程切换,这种情况会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异;
  • 中断后续程序:通知内核离开中断状态,恢复中断前的 CPU 上下文,如果在中断处理过程中未进行线程切换,那么恢复 from 线程的 CPU 上下文,如果在中断中进行了线程切换,那么恢复 to 线程的 CPU 上下文,这部分实现也跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。

在进行中断处理时(实质是调用用户的中断服务程序函数),中断处理函数中很可能会有自己的局部变量,这些都需要相应的栈空间来保存,所以中断响应依然需要一个栈空间来做为上下文,运行中断处理函数。中断栈可以保存在打断线程的栈中,当从中断中退出时,返回相应的线程继续执行;也可以与线程栈完全分离开来,即每次进入中断时,在保存完打断线程上下文后,切换到新的中断栈中独立运行,在中断退出时,再做相应的上下文恢复。RT-Thread 采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。

RT-Thread 不对中断服务程序所需要的处理时间做任何假设、限制,但用户需要保证所有的中断服务程序在尽可能短的时间内完成(中断服务程序在系统中相当于拥有最高的优先级,会抢占所有线程优先执行)。如果中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即上半部分(Top Half)和下半部分(Bottom Half)。在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为下半部处理(Linux也有丰富的下半部处理和推后执行机制,比如软中断、Tasklet、工作队列等)。

为了把操作系统和系统底层的异常、中断硬件隔离开来,RT-Thread 把中断和异常封装为一组抽象接口,如下图所示:
RT-Thread中断管理接口
上面的接口函数中有一部分需要在CPU架构移植时实现,但有些API不会出现在每个移植分支中,例如rt_hw_interrupt_install()、rt_hw_interrupt_mask()/unmask()就不会出现在Cortex-M0/M3/M4 的移植分支中,其余中断接口函数的实现见下文CPU架构移植。

2.3 CPU 架构移植

RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容,下表是 CPU 架构移植需要实现的接口和变量:

函数和变量 描述
rt_base_t rt_hw_interrupt_disable(void); 关闭全局中断;
void rt_hw_interrupt_enable(rt_base_t level); 打开全局中断;
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter, rt_uint8_t *stack_addr, void *texit);
线程栈的初始化,内核在线程创建和
线程初始化里面会调用这个函数;
void rt_hw_context_switch_to(rt_uint32 to); 没有来源线程的上下文切换,在调度器
启动第一个线程的时候调用,以及在
signal 里面会调用;
void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); 从 from 线程切换到 to 线程,用于线程
和线程之间的切换;
void rt_hw_context_switch_interrupt(rt_uint32 from,
rt_uint32 to);
从 from 线程切换到 to 线程,用于中断
里面进行切换的时候使用;
rt_uint32_t rt_thread_switch_interrupt_flag; 表示需要在中断里进行切换的标志;
rt_uint32_t rt_interrupt_from_thread,
rt_interrupt_to_thread;
在线程进行上下文切换时候,用来保存
from 和 to 线程;

全局中断开关与堆栈初始化跟UCOS系统移植基本一致,Cortex-M 使用 CPS 指令(CPSID I / CPSIE I)实现全局快速开关中断。剩下最主要的就是在线程与中断中实现上下文切换的函数实现,首先是调度器启动第一个线程时,只有目标线程而没有来源线程,在rt_hw_context_switch_to实现切换到指定线程的功能,流程图如下(Crotex-M4架构MDK版该函数代码见:rt-thread-4.0.1\libcpu\arm\cortex-m4\context_rvds.S):
RT-Thread无来源线程切换到目标线程
第一个线程启动了,接下来的线程切换主要分两种:一种是线程到线程的上下文切换,另一种是中断到线程的上下文切换,两种上下文切换的过程对比如下:
线程到线程上下文切换
中断到线程上下文切换
对比两个过程可以发现,在 Cortex-M 内核里 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致,都是在 PendSV 里完成剩余上下文的保存和回复。所以我们仅仅需要实现一份代码,简化移植的工作。这两个函数的流程图如下:
RT-Thread上下文切换流程
前面的线程上下文切换都是通过触发PendSV异常实现的,最后的切换还是要在PendSV中断处理函数 PendSV_Handler()中完成线程切换的实际工作,具体的流程图如下:
RT-Thread实现PendSV中断处理流程
RT-Thread 针对大多数常见的CPU架构,已经帮我们实现了CPU架构移植的接口函数(在目录rt-thread-4.0.1\libcpu中),只有少数不常见的CPU架构需要我们自己实现这些接口函数。我们开发板STM32L475的CPU架构是Cortex-M4,MDK版的移植接口函数实现在rt-thread-4.0.1\libcpu\arm\cortex-m4\context_rvds.S中,我们只需要在rtconfig.h中配置几个宏定义即可,比如针对我们开发板配置的宏定义如下:

//  rt-thread-4.0.1\bsp\stm32\libraries\templates\stm32l4xx\rtconfig.h

#define ARCH_ARM
#define ARCH_ARM_CORTEX_M
#define ARCH_ARM_CORTEX_M4

宏定义起作用的过程简单如下:

// rt-thread-4.0.1\libcpu\Kconfig
config ARCH_ARM
    bool

config ARCH_ARM_CORTEX_M
    bool
    select ARCH_ARM

config ARCH_ARM_CORTEX_M4
    bool
    select ARCH_ARM_CORTEX_M

// rt-thread-4.0.1\bsp\stm32\libraries\templates\stm32l4xx\rtconfig.py
# toolchains options
ARCH='arm'
CPU='cortex-m4'
CROSS_TOOL='gcc'

// rt-thread-4.0.1\libcpu\SConscript
if rtconfig.ARCH in list:
    group = group + SConscript(os.path.join(rtconfig.ARCH, 'SConscript'))

实现了libcpu接口函数,RT-Thread 就可以进行线程的创建、运行和切换了,但周期性的线程切换、对相同优先级线程的时间片轮转调度还需要时钟节拍的支持,下面看看时钟节拍的配置和处理函数实现代码:

// rt-thread-4.0.1\bsp\stm32\libraries\templates\stm32l4xx\r
  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

流云IoT

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

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

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

打赏作者

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

抵扣说明:

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

余额充值