RT-Thread移植(二):CPU架构

概述

摘自官方文档:

在嵌入式领域有多种不同 CPU 架构,例如 Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。
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_t to); 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用
void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to); 从 from 线程切换到 to 线程,用于线程和线程之间的切换
void rt_hw_context_switch_interrupt(rt_uint32_t from, rt_uint32_t to); 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用
rt_uint32_t rt_thread_switch_interrupt_flag; 表示需要在中断里进行切换的标志
rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; 在线程进行上下文切换时候,用来保存 from 和 to 线程

一共存在6个函数和3个变量,也就是说只要针对开发板的芯片,最多实现这6个函数和3个变量,就可以在RT-Thread架构下屏蔽掉芯片这一层的差异,对上层的代码提供统一的接口来实现与芯片相关的操作。

开关全局中断

摘自官方文档:

无论内核代码还是用户的代码,都可能存在一些变量,需要在多个线程或者中断里面使用,如果没有相应的保护机制,那就可能导致临界区问题。

临界区问题就是在多个线程或中断中共享了同一个或一些数据,当线程切换或中断来临时,当前线程下的这个变量可能会被这个切换过去的线程或中断子程序修改,而在当前线程的上下文无法找到问题所在。
那么就需要对这段数据相关的代码进行一定的保护,以防出现临界区问题。在RT-Thread中,开关全局中断函数的实现就是解决方法。
libcpu/arm/cortex-m3 下的 context_rvds.S 文件中,就实现了开关全局中断的函数。

关闭全局中断

;/*
; * rt_base_t rt_hw_interrupt_disable(void);
; */
rt_hw_interrupt_disable    PROC      ;PROC 伪指令定义函数
    EXPORT  rt_hw_interrupt_disable  ;EXPORT 输出定义的函数,类似于 C 语言 extern
    MRS     r0, PRIMASK              ; 读取 PRIMASK 寄存器的值到 r0 寄存器
    CPSID   I                        ; 关闭全局中断
    BX      LR                       ; 函数返回
    ENDP                             ;ENDP 函数结束

该汇编代码实现了关闭全局中断的功能。首先将 rt_hw_interrupt_disable 作为一个函数提供给其他的文件域访问,然后将当前的 PRIMASK 值保存到 R0 寄存器中。

PRIMASK 是中断屏蔽寄存器,可读写,该寄存器只有一位。
读取可获取到当前值(0或1)。
写1时会屏蔽所有可屏蔽中断,写0取消屏蔽所有可屏蔽中断。

如果该函数在定义时存在返回值,R0会作为函数的返回值传递出去。也就是将进入这个函数之前系统的中断屏蔽状态存下来,后续等处理完成再将这个值写回去,保证不影响系统原本的值。

CPSID I 其实就是 PRIMASK = 1 ,往上述的中断屏蔽寄存器中写1,来屏蔽所有可屏蔽中断。

开启全局中断

;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC      ; PROC 伪指令定义函数
    EXPORT  rt_hw_interrupt_enable  ; EXPORT 输出定义的函数,类似于 C 语言 extern
    MSR     PRIMASK, r0             ; 将 r0 寄存器的值写入到 PRIMASK 寄存器
    BX      LR                      ; 函数返回
    ENDP                            ; ENDP 函数结束

大体上和上面的关闭是一样的,同样,如果函数定义时存在形参,默认会放进R0里,也就是上面说的进入关闭全局中断之前系统的可屏蔽寄存器的状态。

关闭全局中断和开启全局中断必须是成对出现的,在调用时先使用一个变量来存储 rt_hw_interrupt_disable() 函数的返回值,最后使用 rt_hw_interrupt_enable() 时将这个变量传进去。

这是ARM架构下的开关全局中断,别的芯片架构肯定会有不同的操作,但是流程都是差不多的。其实就是对芯片的全局中断开关做了一个封装。ARM架构下存在 PRIMASK 寄存器来开关全局中断,如果开发板不存在这类寄存器,甚至可以直接用C文件来实现,并不一定使用汇编,当然可能会降低性能。
所有与芯片架构相关的函数都定义在了rthw.h中,以确保能够被其他函数调用。如下图:
在这里插入图片描述

线程栈初始化

以Cortex-M3为例,其芯片的内部寄存器一共是37个,除了R0-R7是每个模式都一样的通用寄存器外,在不同的模式下可能会存在独有的寄存器。
那么在多线程的RTOS中,每个线程可能都对应着不同的上下文环境(即内部寄存器值),而在同一个上下文环境下寄存器只存在一组,那怎么来区分不同的线程呢?
这就是在内存中构建线程栈的目的。我们可以在线程创建时,将当前的上下文,即当前的内部寄存器的值保存到一个在内存中构建出来的栈中,也就是手动的保存当前代码的上下文。然后当需要进行线程切换时,再把这个保存下来的上下文内容写回到内部寄存器中,让程序回到这个保存下来的线程环境中继续执行。这样就能够实现不同线程的切换。
下图是上下文内容在内存中构建的栈中的排布:
在这里插入图片描述
代码具体实现在 arm/cortex-m3/ 下的 cpuport.c 中。结合栈实现的结构体来看:
在这里插入图片描述
exception_stack_frame 部分是ARM的硬件压栈部分, stack_frame 的R4-R11则是需要自行实现的压栈部分。组合起来就是一个完整的上下文环境。
实现初始化的具体代码如下:

rt_uint8_t *rt_hw_stack_init(void       *tentry,
                             void       *parameter,
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值