• 内核移植学习


    内核移植

    内核移植就是指将RT-Thread内核在不同的芯片架构、不同的板卡上运行起来。

    移植可分为CPU架构移植和BSP板级支持包移植两部分。

    CPU架构移植

    在嵌入式领域有多种不同CPU架构,例如Cortex-M、ARM920T、MIPS32、RISC-V等等。

    为了使RT-Thread能够在不同CPU架构的芯片上运行,RT-Thread提供了一个libcpu抽象层来适配不同的CPU架构。
    libcpu层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。

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

    libcpu移植相关API
    在这里插入图片描述
    rt_uint32_t rt_thread_switch_interrupt_flag;表示需要再中断里进行切换的标志

    rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; 在线程进行上下文切换时候,用来保存 from 和 to 线程

    实现全局中断开关

    无论内核代码还是用户的代码,都可能存在一些变量,需要在多个线程或者中断里面使用,如果没有相应的保护机制,那就可能导致临界区问题。
    RT-Thread里为了解决这个问题,提供了一系列的线程间同步和通信机制来解决。但是这些机制都需要用到libcpu里提供的全局中断开关函数。

    rt_base_t rt_hw_interrupt_disbale(void);
    
    void rt_hw_interrupt_enable(rt_base_t level);
    
    • 1
    • 2
    • 3

    关闭全局中断

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

    1. 保存当前的全局中断状态,并把状态作为函数的返回值
    2. 关闭全局中断
    rt_hw_interrupt_disable		PROC
    	EXPORT rt_hw_interrupt_disable		
    	MRS r0,PRIMASK
    	CPSID I
    	BX LR
    	ENDP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    r0存储的数据就是函数的返回值。

    打开全局中断

    rt_hw_interrupt_enable PROC
    	EXPORT rt_hw_interrupt_enable 
    	MSR PRIMASK,r0
    	BX LR
    	ENDP
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现线程栈初始化

    在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_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] = oxdeadbeef;
    	}
    	//将一个参数保存在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 寄存器 */
        stack_frame->exception_stack_frame.r12 = 0;
        stack_frame->exception_stack_frame.lr = (unsigned long)texit;
        stack_frame->exception_stack_frame.pc = (unsigned long)tentry;
        stack_frame->exception_stack_frame.psr = 0x01000000L;//设置psr的值为这个,表示默认切换过去是Thumb模式
        return stk;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    实现上下文切换

    在不同的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() 和 rt_hw_context_switch_interrupt() 的实现并不一样。

    在中断处理程序里如果触发了线程的调度,调度函数里会调用rt_hw_context_switch_interrupt()触发上下文切换。
    中断处理程序里处理完中断事务之后,中断退出之前,检查flag变量,如果变量的值为1,就根据from_thread和to_thread变量,完成线程的上下文切换。

    在Cortex-M处理器架构里,基于自动部分压栈和PendSV的特性,上下文切换可以实现地更加简洁。

    在这里插入图片描述
    硬件在进入PendSV中断之前自动保存了from线程的PSR、PC、LR、R12、R3-R0寄存器,然后PendSV里保存from线程的R4-R11寄存器,以及恢复to线程的R4-R11寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0~R3、R12、LR、PC、PSR 寄存器。

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

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

    实现PendSV中断

    在Cortex-M3里,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 
    	
    	;关闭全局中断
    	MRS r2,PRIMASK
    	CPSID I
    	
    	; 检查 rt_thread_switch_interrupt_flag 变量是否为 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
        ; 如果为 0,就不进行 from 线程的上下文保存
        LDR     r0, =rt_interrupt_from_thread
        LDR     r1, [r0]
        CBZ     r1, switch_to_thread
    
    ; 保存 from 线程的上下文
        MRS     r1, psp                 ; 获取 from 线程的栈指针
        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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
  • 相关阅读:
    抖音矩阵系统,抖音矩阵系统,抖音矩阵系统,讲三遍。
    树莓派(Raspberry Pi)miniDLNA服务搭建
    关于工作方法和高效工作的建议
    Matlab图像处理-最大类间方差阈值选择法(Otsu)
    elasticsearch api 调用
    Python爬虫实战-批量爬取下载网易云音乐
    增速波动!W「下」AR「上」!HUD前装供应商比拼硬核能力
    NVIDIA 7th SkyHackathon(二)开发套件的安装与测试
    基本的SELECT语句——“MySQL数据库”
    Linux系统初识(使用centos7镜像 )--项目部署与简单命令、静态ip
  • 原文地址:https://blog.csdn.net/Caramel_biscuit/article/details/136190709