RT-Thread学习笔记【内核移植】

RTT的内核与内核移植

内核移植:将操作系统的内核在不同CPU架构和不同外围硬件环境下运行,能够具有线程管理和调度、内存管理、线程间同步与通信、定时器管理等基本功能的过程

内核移植可以分为CPU架构移植BSP(Board Support Package板级支持包)移植两部分

CPU架构移植

RTT提供了libcpu抽象层来适配不同的CPU架构

可支持arm系、mips系、RISC-V、avr系、xilinx系等多种流行的嵌入式cpu架构

libcpu抽象层

libcpu的上层对内核提供运行接口,下层对CPU提供架构移植接口

总体上讲,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线程

全局中断开关的实现

所有临界区处理时用到的线程间同步和通信机制都依赖libcpu中提供的全局中断开关函数

rt_hw_interrupt_disable(void);//关闭全局中断
rt_hw_interrupt_enable(rt_base_t level);//打开全局中断

Cortex-M架构中使用CPS指令实现这两个函数

CPSID I ;PRIMASK=1,  ; 关中断
CPSID I ;PRIMASK=0,  ; 开中断

Cortex-M架构的移植示例

关闭全局中断

rt_hw_interrupt_disable()函数功能是

	1. 保存当前全局中断状态

 		2. 把状态作为函数返回值

基于MDK汇编实现代码如下

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

基于MDK汇编实现代码如下

;/*
; * 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	; 函数结束

开关全局中断的移植思路:通用寄存器内保存了希望中断锁处于的状态,用寄存器转移指令将通用寄存器内数据和中断控制寄存器内的数据交换,借助CPU自带的中断生效指令对全局中断状态进行生效

线程栈初始化

线程初始化函数 _rt_thread_init()进行操作系统内部的线程初始化

栈初始化函数rt_hw_stack_init()会手动构造上下文,这个上下文内容作为每个线程第一次执行的初始值

线程栈自顶向下生长

_rt_thread_init()和rt_hw_stack_init()函数如下所示

static rt_err_t _rt_thread_init(struct rt_thread *thread,
                                const char       *name,
                                void (*entry)(void *parameter),
                                void             *parameter,
                                void             *stack_start,
                                rt_uint32_t       stack_size,
                                rt_uint8_t        priority,
                                rt_uint32_t       tick)
{
    /* init thread list */
    rt_list_init(&(thread->tlist));

    thread->entry = (void *)entry;
    thread->parameter = parameter;

    /* stack init */
    thread->stack_addr = stack_start;
    thread->stack_size = stack_size;

    /* init thread stack */
    rt_memset(thread->stack_addr, '#', thread->stack_size);
    thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                          (void *)((char *)thread->stack_addr + thread->stack_size - 4),
                                          (void *)rt_thread_exit);

    /* priority init */
    RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
    thread->init_priority    = priority;
    thread->current_priority = priority;

    thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
    thread->number = 0;
    thread->high_mask = 0;
#endif

    /* tick init */
    thread->init_tick      = tick;
    thread->remaining_tick = tick;

    /* error and flags */
    thread->error = RT_EOK;
    thread->stat  = RT_THREAD_INIT;

    /* initialize cleanup function and user data */
    thread->cleanup   = 0;
    thread->user_data = 0;

    /* init thread timer */
    rt_timer_init(&(thread->thread_timer),
                  thread->name,
                  rt_thread_timeout,
                  thread,
                  0,
                  RT_TIMER_FLAG_ONE_SHOT);

    /* initialize signal */
#ifdef RT_USING_SIGNALS
    thread->sig_mask    = 0x00;
    thread->sig_pending = 0x00;

    thread->sig_ret     = RT_NULL;
    thread->sig_vectors = RT_NULL;
    thread->si_list     = RT_NULL;
#endif

#ifdef RT_USING_LWP
    thread->lwp = RT_NULL;
#endif

    RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));

    return RT_EOK;
}
//上面这个函数想必是要刻进DNA罢(确信)

//下面这个函数有手就行
rt_uint8_t *rt_hw_stack_init(void       *tentry,//线程入口函数的地址
                             void       *parameter,//参数
                             rt_uint8_t *stack_addr,//传入栈地址
                             void       *texit)//线程退出函数的地址
{
    struct stack_frame *stack_frame;
    rt_uint8_t         *stk;
    unsigned long       i;

    //对传入的栈指针作对齐处理,使其能适应硬件的内存
    stk  = stack_addr + sizeof(rt_uint32_t);
    stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
    stk -= sizeof(struct stack_frame);

    stack_frame = (struct stack_frame *)stk;//获取上下文的栈帧的指针

    /* 初始化所有寄存器的默认值为0xdeadbeef */
    for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
    {
        ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
    }
	
    //根据ARM APCS调用标准,将第一个参数保存在r0寄存器
    stack_frame->exception_stack_frame.r0  = (unsigned long)parameter;
    
    //将剩下的参数寄存器都设置为0
    stack_frame->exception_stack_frame.r1  = 0;                        /* r1 */
    stack_frame->exception_stack_frame.r2  = 0;                        /* r2 */
    stack_frame->exception_stack_frame.r3  = 0;                        /* r3 */
    
    //将IP(Intra-Procedure-cell scratch register)设置为0
    stack_frame->exception_stack_frame.r12 = 0;                        /* r12 */
    
    //将线程退出函数的地址保存在lr寄存器
    stack_frame->exception_stack_frame.lr  = (unsigned long)texit;     /* lr */
    
    //将线程入口函数的地址保存在pc寄存器
    stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* 进入点为 pc */
    
    //设置psr的值为0x01_000_000L,表示默认切换过去是Thumb模式
    stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR */

    /* 返回当前线程的栈地址 */
    return stk;
}
上下文切换

在不同的CPU架构中,线程间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的,RTT的libcpu抽象层实现三个线程切换相关的函数

函数说明
rt_hw_context_switch_to()没有来源线程,切换到目标线程,在调度器启动第一个线程时被调用
rt_hw_context_switch()线程环境下,从当前线程切换到目标线程
rt_hw_context_switch_interrupt ()中断环境下,从当前线程切换到目标线程

线程环境下进行切换和中断环境下进行切换是存在差异的:线程环境下调用rt_hw_context_switch可马上进行上下文切换;终端环境下需要等待中断处理函数完成后才能进行切换

在Cortex-M系列CPU中基于自动部分压栈和PendSV特性,上下文切换都是统一使用PendSV异常来完成,更加简洁,切换部分没有差异;但在ARM9等平台,两个函数的实现并不一样。中断处理程序中如果触发了线程的调度,调度函数里会调用rt_hw_context_switch_interrupt ()触发上下文切换,中断处理程序执行完毕后在中断退出之前会检查rt_hw_context_switch_interrupt_flag变量,如果变量值为1,则根据rt_interrupt_from_thread 变量和 rt_interrupt_to_thread 变量,完成线程的上下文切换

【PendSV异常】PendSV也称为可悬起的系统调用,它是一种异常,可以像普通中断一样被挂起,他专门用来辅助操作系统进行上下文切换,PendSV异常始终被初始化为最低优先级的异常

Cortex-M系列CPU的线程切换过程如下图所示

在这里插入图片描述

线程-线程切换过程可以描述为:保护现场(from线程寄存器压栈)——进入PendSV异常——替换参数(保存from线程参数并恢复to线程参数)——退出PendSV异常——替换恢复现场(to线程寄存器出栈)

在这里插入图片描述

中断-线程切换过程可以描述为:保护现场(from线程寄存器压栈)——执行中断服务程序——【咬尾中断】——进入PendSV异常——替换参数(保存from线程参数并恢复to线程参数)——退出PendSV异常——替换恢复现场(to线程寄存器出栈)

所以在Cortex-M内核中rt_hw_context_switch()和rt_hw_context_switch_interrupt()功能一致,所以只需实现一份代码即可

rt_hw_context_switch_to()的实现如下

;/*
; * void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
rt_hw_context_switch_to    PROC
    EXPORT rt_hw_context_switch_to
    ; r0的值是一个指向to线程的线程控制块的SP成员(线程栈的栈顶指针)的指针
    ; 将r0的值保存到rt_interrupt_to_thread变量里
    LDR     r1, =rt_interrupt_to_thread
    STR     r0, [r1]

    IF      {FPU} != "SoftVFP"
    ; CLEAR CONTROL.FPCA
    MRS     r2, CONTROL             ; read
    BIC     r2, #0x04               ; modify
    MSR     CONTROL, r2             ; write-back
    ENDIF

    ; 设置from线程为空,表示不需要保存from的上下文
    LDR     r1, =rt_interrupt_from_thread
    MOV     r0, #0x0
    STR     r0, [r1]

    ; 设置切换标志为1,代表需要切换线程,此变量rt_thread_switch_interrupt_flag将在PendSV异常处理函数中切换时清零
    LDR     r1, =rt_thread_switch_interrupt_flag
    MOV     r0, #1
    STR     r0, [r1]

    ; 设置PendSV和SysTick异常优先级为最低优先级
    LDR     r0, =NVIC_SYSPRI2
    LDR     r1, =NVIC_PENDSV_PRI
    LDR.W   r2, [r0,#0x00]       ; read
    ORR     r1,r1,r2             ; modify
    STR     r1, [r0]             ; write-back

    ; 触发将引起PendSV异常处理程序的PendSV异常
    LDR     r0, =NVIC_INT_CTRL
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]

    ; 放弃芯片启动到第一次上下文切换之前的栈内容,将MSP设置为启动时的值
    LDR     r0, =SCB_VTOR
    LDR     r0, [r0]
    LDR     r0, [r0]
    MSR     msp, r0

    ; 在处理器级别使能全局中断和全局异常,使能后直接进入PendSV异常处理函数
    CPSIE   F
    CPSIE   I

    ; 程序不会执行到这里
    ENDP

rt_hw_context_switch_interrupt()和rt_hw_context_switch()的实现如下

;/*
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interrupt
    EXPORT rt_hw_context_switch_interrupt	; 俩函数放一块了
rt_hw_context_switch    PROC
    EXPORT rt_hw_context_switch

    ; 检查rt_thread_switch_interrupt_flag是否为1
    ; 如果变量为1则表明需要切换线程,直接跳过更新from线程的内容
    LDR     r2, =rt_thread_switch_interrupt_flag
    LDR     r3, [r2]
    CMP     r3, #1
    BEQ     _reswitch
    ; 设置rt_thread_switch_interrupt_flag的值为1
    MOV     r3, #1
    STR     r3, [r2]

	; 从参数r0里更新rt_interrupt_from_thread变量
    LDR     r2, =rt_interrupt_from_thread
    STR     r0, [r2]

_reswitch
	; 从参数r1里更新rt_interrupt_to_thread变量
    LDR     r2, =rt_interrupt_to_thread
    STR     r1, [r2]

	; 触发将引起PendSV异常处理程序的PendSV异常,直接进入PendSV异常处理函数里完成上下文切换
    LDR     r0, =NVIC_INT_CTRL
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]
    BX      LR
    ENDP

PendSV异常处理函数被命名为PendSV_Handler()

异常处理函数的实现如下

; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
PendSV_Handler   PROC
    EXPORT PendSV_Handler	;PendSV异常处理函数

    ; 关闭中断,保护程序进行
    MRS     r2, PRIMASK
    CPSID   I

    ; 检查rt_thread_switch_interrupt_flag是否为0
    ; 如果为0则跳转到pendsv_exit
    LDR     r0, =rt_thread_switch_interrupt_flag
    LDR     r1, [r0]
    CBZ     r1, pendsv_exit         ; pendsv already handled

    ; 清零rt_thread_switch_interrupt_flag
    MOV     r1, #0x00
    STR     r1, [r0]

	; 检查rt_interrupt_from_thread变量,如果为0则不进行from线程的上下文切换
    LDR     r0, =rt_interrupt_from_thread
    LDR     r1, [r0]
    CBZ     r1, switch_to_thread    ; 跳过在第一次的切换
	
	; 保存from线程上下文
    MRS     r1, psp                 ; 获取from线程的栈指针SP
    
    ; 下面是针对FPU寄存器的处理
    IF      {FPU} != "SoftVFP"
    TST     lr, #0x10               ; if(!EXC_RETURN[4])
    VSTMFDEQ  r1!, {d8 - d15}       ; push FPU register s16~s31
    ENDIF

	; 还是保存from线程上下文
    STMFD   r1!, {r4 - r11}         ; 将r4 - r11入栈

	; 下面是针对FPU寄存器的处理
    IF      {FPU} != "SoftVFP"
    MOV     r4, #0x00               ; flag = 0
    TST     lr, #0x10               ; if(!EXC_RETURN[4])
    MOVEQ   r4, #0x01               ; flag = 1
    STMFD   r1!, {r4}               ; push flag
    ENDIF
	
	; 依旧是保存from线程上下文
    LDR     r0, [r0]
    STR     r1, [r0]                ; 更新线程控制块的SP指针

switch_to_thread	; 线程替换
    LDR     r1, =rt_interrupt_to_thread
    LDR     r1, [r1]
    LDR     r1, [r1]                ; 获取to线程的栈指针SP

	; 下面是针对FPU寄存器的处理
    IF      {FPU} != "SoftVFP"
    LDMFD   r1!, {r3}               ; pop flag
    ENDIF

    LDMFD   r1!, {r4 - r11}         ; 恢复to线程寄存器值:r4 - r11寄存器出栈

	; 下面是针对FPU寄存器的处理
    IF      {FPU} != "SoftVFP"
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    VLDMFDNE  r1!, {d8 - d15}       ; pop FPU register s16~s31
    ENDIF
	
	; 更新r1的值到栈指针psp
    MSR     psp, r1

	; 下面是针对FPU寄存器的处理
    IF      {FPU} != "SoftVFP"
    ORR     lr, lr, #0x10           ; lr |=  (1 << 4), clean FPCA.
    CMP     r3,  #0                 ; if(flag_r3 != 0)
    BICNE   lr, lr, #0x10           ; lr &= ~(1 << 4), set FPCA.
    ENDIF

pendsv_exit
    ; 恢复全局中断状态
    MSR     PRIMASK, r2

	; 修改lr寄存器的bit2,确保进程使用PSP堆栈指针!
    ORR     lr, lr, #0x04
    ; 退出异常处理函数
    BX      lr
    ENDP
时钟节拍SysTick的实现

RTT依靠时钟节拍进行线程调度、延时、软件定时器等操作

只要确保rt_tick_increase()函数在时钟节拍中断里被周期性调用即可,调用周期取决于rtconfig.h的宏RT_TICK_PER_SECOND的值

在Cortex-M系列CPU里通过SysTick的中断处理函数实现时钟节拍

实现如下

void SysTick_Handler(void)
{
    /* 告知系统进入中断 */
    rt_interrupt_enter();
    HAL_IncTick();//systick++
    rt_tick_increase();//周期性调用rt_tick_increase()
    /* 告知系统退出中断 */
    rt_interrupt_leave();
}

__weak void HAL_IncTick(void)
{
  uwTick++;
}

BSP移植

为了适配板卡具有的硬件资源,RTT提供了BSP抽象层来适配常见的板卡

BSP移植可以让操作系统控制开发板/产品板上除CPU外的硬件资源,建立让操作系统合理运行的环境

需要完成的主要工作如下

  1. 初始化CPU内部寄存器,设定RAM工作时序
  2. 实现时钟驱动中断控制器驱动,完善中断管理
  3. 实现串口GPIO驱动
  4. 初始化动态内存堆,实现动态堆内存管理

详细的BSP移植需要参考RTT驱动设计及使用部分

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值