RT-Thread 内核移植(学习)

news2025/6/20 5:56:50

内核移植

内核移植就是指将RT-Thread内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步和通信、定时器管理等功能。

移植可分为CPU架构移植和BSP(Board support package,板级支持包)移植两部分。

CPU架构移植

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

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

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

CPU架构移植需要实现的接口和变量。

  • rt_base_t rt_hw_interrupt_disable(void); 关闭全局中断
  • void rt_hw_interrupt_enbale(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 线程

实现全局中断开关

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

rt_base_t rt_hw_interrupt_disable(void);

void rt_hw_interrupt_enable(rt_base_t level);

Cortex-M为了快速开关中断,实现了CPS指令,可以用在此处。

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

关闭全局中断

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

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

基于MDK,在Cortex-M内核上实现关闭全局中断:

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

r0存储的就是函数的返回值。
中断可以发生在 “MRS r0, PRIMASK” 指令和 “CPSID I” 之间,这并不会导致全局中断状态的错乱。

实现线程栈初始化

在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init()
_rt_thread_innit()函数会调用栈初始化函数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;   
    // 将线程退出函数的地址保存在lr寄存器
    stack_frame->exception_stack_frame.lr = (unsigned long)texit;
    // 将线程入口函数的地址保存在pc寄存器
    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()和rt_hw_context_switch_interrupt()的实现不一样。

在中断处理程序里如果触发了线程的调度,调度函数里会调用rt_hw_context_switch_interrupt()触发上下文切换。

中断处理程序处理完中断事务之后,中断退出之前,检查rt_thread_switch_interrupt_flag变量,如果该变量的值为1,就根据rt_interrupt_from_thread 变量和 rt_interrupt_to_thread 变量,完成线程的上下文切换。

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

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

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

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

实现rt_hw_context_switch_to()

  1. 将参数to保存到rt_interrupt_to_thread变量
  2. 将rt_interrupt_from_thread变量设置为0
  3. 将rt_thread_switch_interrupt_flag变量设置为1
  4. 设置PendSV异常优先级,触发PendSV中断
  5. 恢复MSP的默认值
  6. 使能全局中断
  7. 结束
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]

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

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

	;设置PendSV异常优先级为最低优先级
	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()/ rt_hw_context_switch_interrupt()

函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。
在这里插入图片描述

实现PendSV中断

在Cortex-M3里,PendSV中断处理函数是PendSV_Handler()。
在这里插入图片描述

PendSV_Handler PROC
	EXPORT PendSV_Handler
	
	;关闭全局中断
	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

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

	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

实现时钟节拍

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

libcpu的移植需要完成的工作,就是确保rt_tick_increase()函数会在时钟节拍的中断里被周期性的调用,调用周期取决于rtconfig.h的宏RT_TICK_PER_SECOND的值。

在Cortex-M中,实现SysTick的中断处理函数即可实现时钟节拍功能。

void SysTick_Handler(void)
{
	rt_interrupt_enter();
	rt_tick_increase();
	rt_interrupt_leave();
}

BSP移植

相同的CPU架构在实际项目中,不同的板卡上可能使用相同的CPU架构,搭载不同的外设资源,完成不同的产品,所以需要针对板卡做适配工作。

RT-Thread提供了BSP抽象层来适配常见的板卡。
如果希望在一个板卡上使用RT-Thread内核,除了需要有相应的芯片架构的移植,还需要有针对板卡的移植,也就是实现一个基本的BSP。

主要任务是建立让操作系统运行的基本环境,需要完成的主要工作是:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1086833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

催交费通知单套打单纸设置说明

2.0系统打印催交费通知单设置尺寸操作展示如下,仅供参考。具体如下: 一、Win7系统 1.找到设备和打印机,选中对应打印机后点击上方打印服务器属性; 2.创建一个宽14cm,高14cm的表单; 二、win10系统 1.找到打印机,点管理,选择打印首选项;

Unity关键词语音识别

一、背景 最近使用unity开发语音交互内容的时候&#xff0c;遇到了这样的需求&#xff0c;就是需要使用语音关键字来唤醒应用程序&#xff0c;然后再和程序做交互&#xff0c;有点像智能音箱的意思。具体的技术方案方面&#xff0c;也找了一些第三方的服务&#xff0c;比如百度…

当涉及到API接口数据分析时,主要可以从以下几个方面展开

当涉及到API接口数据分析时&#xff0c;主要可以从以下几个方面展开&#xff1a; 请求分析&#xff1a;可以统计每个API接口的请求次数、请求成功率、失败率等基础指标。这些指标可以帮助你了解API接口的使用情况&#xff0c;比如哪个API接口被调用的次数最多&#xff0c;哪个…

2023年09月 C/C++(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 Python编程&#xff08;1~6级&#xff09;全部真题・点这里 第1题&#xff1a;酒鬼 Santo刚刚与房东打赌赢得了一间在New Clondike 的大客厅。今天&#xff0c;他来到这个大客厅欣赏他的奖品。房东摆出了一行瓶子在酒吧上…

《向量数据库指南》——向量数据库与 ANN 算法库的区别

向量数据库与 ANN 算法库的区别 我们经常听到一个这样的错误观念——向量数据库只是在 ANN(approximate nearest neighbor,近似最近邻)算法上封装了一层。但这种说法大错特错。 向量数据库可以处理大规模数据,而 ANN 算法库只能处理小型的数据集 从本质上来看,以 Milvus 为…

Adobe Premiere Pro 和 After Effects 安装出错的解决路径

在有点年头的电脑上安装Premiere Pro 和 After Effects 遇到了前所未有的的麻烦&#xff0c;请了某宝上的小哥进行远程安装&#xff0c;两个软件倒是可以用了&#xff0c;但Win11系统无法正常关机&#xff0c;用了几天系统除了关机时会蓝屏几十秒&#xff0c;其他没有发现毛病&…

centos 7 lamp owncloud

OwnCloud是一款开源的云存储软件&#xff0c;基于PHP的自建网盘。基本上是私人使用&#xff0c;没有用户注册功能&#xff0c;但是有用户添加功能&#xff0c;你可以无限制地添加用户&#xff0c;OwnCloud支持多个平台&#xff08;windows&#xff0c;MAC&#xff0c;Android&a…

计算机网络 | 物理层

计算机网络 | 物理层 计算机网络 | 物理层基本概念数据通信基本知识&#xff08;一&#xff09;一个数据通信流程的例子数据通信相关术语三种通信方式数据传输方式串行传输和并行传输同步传输和异步传输 小结 数据通信基本知识&#xff08;二&#xff09;码元&#xff08;Symbo…

【Java 进阶篇】JavaScript 一元运算符详解

在JavaScript中&#xff0c;一元运算符是一类操作符&#xff0c;它们作用于单一操作数&#xff08;一个值&#xff09;。这些运算符执行各种操作&#xff0c;包括递增、递减、类型转换等。本文将详细介绍JavaScript中的一元运算符&#xff0c;解释它们的用途&#xff0c;提供示…

MySQL MVCC详细介绍

MVCC概念 MVCC(Multi-Version Concurrency Control) 多版本并发控制&#xff0c;是一种并发控制机制,用于处理数据库中的并发读写操作&#xff0c;它通过在每个事务中创建数据的快照&#xff0c;实现了读写操作的隔离性&#xff0c;从而避免了读写冲突和数据不一致的问题。 M…

VUE echarts 柱状图、折线图 双Y轴 显示

weekData: [“1周”,“2周”,“3周”,“4周”,“5周”,“6周”,“7周”,“8周”,“9周”,“10周”], //柱状图横轴 jdslData: [150, 220, 430, 360, 450, 680, 100, 450, 680, 200], // 折线图的数据 cyslData: [100, 200, 400, 300, 500, 500, 500, 450, 480, 400], // 柱状图…

基于VScode 使用plantUML 插件设计状态机

本文主要记录本人初次在VScode上使用PlantUML设计 本文只讲述操作的实际方法&#xff0c;假设java已安装成功 。 1. 在VScode下安装如下插件 2. 验证环境是否正常 新建一个文件夹并在目录下面新建文件test.plantuml 其内容如下所示: startuml hello world skinparam Style …

ubuntu|23 安装Gnome主题

ubuntu23 安装主题 进入网站选择需要的主题 https://www.opendesktop.org/s/Gnome/p/1357889 1 资源下载 经常加载不出来&#xff0c; 这里直接进入github下载源码 下载zip 2 安装主题 根据文档提示&#xff0c; 执行install.sh就能安装 3 切换主题 安装 tweak工具 sudo …

Win10玩游戏老是弹回桌面的解决方法

在Win10电脑中&#xff0c;用户不仅可以办公&#xff0c;也可以畅玩各种各样的游戏。但是&#xff0c;有时候用户在玩游戏的时候&#xff0c;遇到了游戏老是自己弹回桌面的问题&#xff0c;这样是非常影响游戏体验的&#xff0c;却不清楚具体的解决方法。下面小编给大家带来了简…

力扣第235题 二又搜索树的最近公共祖先 c++

题目 235. 二叉搜索树的最近公共祖先 中等 &#xff08;简单&#xff09; 相关标签 树 深度优先搜索 二叉搜索树 二叉树 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&…

抖音短视频SEO是什么?抖音SEO系统源码/SEO系统源码搭建/

一.数据市场 二、AI视频创意 (1)工作台 (2)创造性工程 (3)材料管理 (4)裂变视频 (5)创作灵感 场景:实现单体设计模式&#xff1b; 想法:与创建相关的是construct的陷阱&#xff0c;每次我们都返回相同的实例。 (1)推广词库(2)三维排名查询(3)算术指数四。帐户矩阵操作(1)账户管…

谷歌浏览器 ERR_MANDATORY_PROXY_CONFIGURATION_FAILED 报错的处理方式

今天早上到公司 还是和往常一样 电脑开机 打开谷歌浏览器 搜索资料结果给我报错了 2.原先我的谷歌浏览器配置的搜索引擎为百度 3.电脑上面的火狐 ie浏览器都是可以的排除电脑网络方面的问题(dns也有解析谷歌路径的这个有兴趣的可以研究) 最终还是刷新了电脑的dns ,清除了谷歌…

混淆技术研究笔记(六)如何基于yGuard实现?

确定参考 <adjust> 作为入口后&#xff0c;就需要详细了解这部分代码的逻辑。 需要看yguard源码了&#xff0c;你会如何阅读一个完全不了解的源码&#xff1f; 我通常的策略都是找一个目标&#xff0c;添加代码依赖&#xff0c;写好demo&#xff0c;debug跟踪代码看。如…

大功率回馈式直流电子负载箱的运用

大功率回馈式直流电子负载箱能够模拟各种负载条件&#xff0c;可以在实验室环境中对电源、电池、太阳能电池板等电子设备进行全面的性能测试和模拟负载&#xff0c;具有高功率输出、高精度、高稳定性和高可靠性的特点&#xff0c;能够满足各种应用场景的需求。 电源测试方面大功…

深入了解桶排序:原理、性能分析与 Java 实现

桶排序&#xff08;Bucket Sort&#xff09;是一种排序算法&#xff0c;通常用于将一组数据分割成有限数量的桶&#xff08;或容器&#xff09;&#xff0c;然后对每个桶中的数据进行排序&#xff0c;最后将这些桶按顺序合并以得到排好序的数据集。 桶排序原理 确定桶的数量&am…