RT-Thread 内核移植

发布时间:2024年01月12日

内核移植

内核移植就是指将RT-Thread内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步和通信、定时器管理等功能。
移植可分为CPU架构移植和BSP(Board support package,板级支持包)移植两部分。

CPU架构移植

在嵌入式领域有多种不同CPU架构,例如Cortex-M、ARM920T、MIPS32、RISC-V等等。
为了使RT-Thread能够在不同CPU架构的芯片上运行,RT-Thread提供了一个libcpu抽象层来适配不同的CPU架构。

libcpu层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。

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

下表是CPU架构移植需要实现的接口和变量。
在这里插入图片描述

实现全局中断开关

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

RT-Thread里为了解决这个问题,提供了一系列的线程间同步和通信机制来解决。但是这些机制都需要用到libcpu里提供的全局中断开关函数。

rt_base_t rt_hw_interrupt_disable(void);

void rt_hw_interrupt_enbale(rt_base_t level);

下面介绍在Cortex-M架构上如何实现这两个函数,前文中曾提到过,Cortex-M为了快速开关中断,实现了CPS指令,可以用在此处。

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

关闭全局中断

在rt_hw_interrupt_disable()函数里面需要依次完成的功能是:

  1. 保存当前的全局中断状态,并把状态作为函数的返回值。
  2. 关闭全局中断。

基于MDK,在Cortex-M内核上实现关闭全局中断,如下代码所示:

rt_hw_interrut_disable PROC
	EXPORT rt_hw_interrupt_disable
	MRS R0,PRIMASK
	CPSID I
	BX LR
	ENDP

上面的代码首先是使用MRS指令将PRIMASK寄存器的值保存到r0寄存器里,然后使用CPSID I指令关闭全局中断,最后使用BX指令返回。r0存储的数据就是函数的返回值。

打开全局中断

rt_hw_interrupt_enbale PROC
	EXPORT rt_hw_interrupt_enable
	MSR PRIMASK,R0
	BX LR
	ENDP

实现线程栈初始化

在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init()函数会调用栈初始化函数rt_hw_stack_init(),在栈初始化函数里会收到构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。

在这里插入图片描述

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;

	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-call scratch register.) 设置为 0 */
    stack_frame->exception_stack_frame.r12 = 0;                 /* r12 寄存器 */
    /* 将线程退出函数的地址保存在lr寄存器 */
    stack_frame->exception_stack_frame.lr = (unsigned long)texit;
    stack_frame->exception_stack_frame.pc = (unsigned long)tentry;

	/* 设置psr的值为0x01000000L,表示默认切换过去是Thumb模式 */
	stack_frame->exception_stack_frame.psr = 0x01000000L;
	return stk;
}

实现上下文切换

在不同的CPU架构里,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的。

在Cortex-M里面上下文切换都是统一使用PendSV异常来完成,切换部分并没有差异。但是为了适应不同的CPU架构,RT-Thread的libcpu抽象层还是需要实现三个线程相关的函数:

  1. rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器第一个线程的时候被调用。
  2. rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。
  3. rt_hw_context_switch_interrupt():在中断环境下,从当前线程切换到目标线程。

在线程环境下进行切换和在中断环境下进行切换是存在差异的。
线程环境下,如果调用rt_hw_context_switch()函数,那么可以马上进行上下文切换;而在中断环境下,需要等待中断处理函数完成之后才能进行切换。

由于这种差异,在ARM9等平台,这两个函数的实现并不一样。
在中断处理程序里如果触发了线程的调度,调度函数里会调用rt_hw_context_switch_interrupt()触发上下文切换。
中断处理程序里处理完中断事务之后,中断退出之前,检查rt_thread_switch_interrupt_flag变量,如果该变量的值为1,就根据from变量和to变量,完成线程的上下文切换。

在Cortex-M处理器架构里,基于自动部分压栈和PendSV的特性,上下文切换可以实现地更加简洁。
在这里插入图片描述
硬件在进入PendSV中断之前自动保存了from线程的PSR、PC、LR、R12、R3R0寄存器,然后PendSV里保存from线程的R4-R11寄存器,以及恢复to线程的R4-R11寄存器,最后硬件在退出PendSV中断之后,自动恢复to线程的R0R3、R12、LR、PC、PSR寄存器。

中断到线程的上下文切换可以用下图表示:
在这里插入图片描述
硬件在进入中断之前自动保存了from线程的PSR、PC、LR、R12、R3-R0寄存器,然后触发了PendSV异常。
在PendSV异常处理函数里保存from线程的R11R4寄存器,以及恢复to线程的R4R11寄存器,最后硬件在退出PendSV中断之后,自动恢复to线程的R0~R3、R12、PSR、PC、LR寄存器。

显然,在Cortex-M内核里rt_hw_context_switch()和rt_hw_context_switch_interrupt()功能一致,都是在PendSV里完成剩余上下文的保存和恢复。
所以,我们仅仅需要一份代码,简化移植的工作。

实现rt_hw_context_swtch_to()

在这里插入图片描述

rt_hw_context_switch_to PROC
	EXPORT rt_hw_context_switch_to
	LDR r1, = rt_interrupt_to_thread
	STR r0, [r1]

	LDR r1, =rt_interrupt_from_thread
	MOV r0, #0x0
	STR r0, [r1]

	; 设置标志为 1,表示需要切换,这个变量将在 PendSV 异常处理函数里切换的时被清零
    LDR     r1, =rt_thread_switch_interrupt_flag
    MOV     r0, #1
    STR     r0, [r1]

	;设置PendSV异常优先级为最低优先级
	LDR r0, =NVIC_SYSPRI2
	LDR r1, =NVIC_PENDSV_PRI
	LDR.W r2, [r0,#0x00]
	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()

在这里插入图片描述

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 异常处理函数里完成上下文切换
    LDR     r0, =NVIC_INT_CTRL
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]
    BX      LR

实现PendSV中断

在Cortex-M3里,PendSV中断处理函数是PendSV_Handler()。
在PendSV_Handler()里完成线程切换的实际工作。
在这里插入图片描述

PendSV_Handler PROC
	EXPORT PendSV_Handler

	;关闭全局中断
	MRS r2,PRIMASK
	CPSID I

	;检查switch_interrupt_flag是否为0,如果为零就退出
	LDR r0, =rt_thread_switch_interrupt_flag
	LDR r1, [r0]
	CBZ r1,pendsv_sxit

	MOV R1,#0x00
	STR r1, [r0]

	; 检查 rt_interrupt_from_thread 变量是否为 0
    ; 如果为 0,就不进行 from 线程的上下文保存
    LDR     r0, =rt_interrupt_from_thread
    LDR     r1, [r0]
    CBZ     r1, switch_to_thread

	;保存from线程的上下文
	MRS r1,psp
	STMFD r1!,{r4-r11} ;将r4-r11保存到线程的栈里
	LDR r0,[r0]
	STR r1,[r0] ;更新线程控制块的SP指针

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

    LDMFD   r1!, {r4 - r11}       ; 从 to 线程的栈里恢复 to 线程的寄存器值
    MSR     psp, r1                 ; 更新 r1 的值到 psp

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

    ; 修改 lr 寄存器的 bit2,确保进程使用 PSP 堆栈指针
    ORR     lr, lr, #0x04
    ; 退出中断函数
    BX      lr
    ENDP

实现时钟节拍

有了开关全局中断和上下文切换功能的基础,RTOS就可以进行线程的创建、运行、调度等功能了。
有了时钟节拍支持,RT-Thread可以实现对相同优先级的线程采用时间片轮转的方式来调度,实现定时器功能,实现rt_thread_delay()延时函数等等。

libcpu的移植需要完成的工作,就是确保rt_tick_increase()函数会在时钟节拍的中断里被周期性地调用。

BSP移植

相同CPU架构在实际项目中,不同的板卡上可能使用相同的CPU架构,搭载不同的外设资源,完成不同的产品,所以我们也需要针对板卡做适配工作。
RT-Thread提供了BSP抽象层来适配常见的板卡。
如果希望在一个板卡上使用RT-Thread内核,除了需要有相应的芯片架构的移植,还需要有针对板卡的移植,也就是实现一个基本的BSP。主要任务是建立让操作系统运行的基本环境,需要完成的主要工作是:
1)初始化 CPU 内部寄存器,设定 RAM 工作时序。

2)实现时钟驱动及中断控制器驱动,完善中断管理。

3)实现串口和 GPIO 驱动。

4)初始化动态内存堆,实现动态堆内存管理。

文章来源:https://blog.csdn.net/Caramel_biscuit/article/details/135547246
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。