RT-Thread快速入门-内核移植

news2024/12/26 22:00:49

1RT-Thread快速入门-内核移植

RT-Thread 快速入门系列前面的文章介绍了内核相关的知识,以及内核提供的接口函数和如何使用。

本篇文章主要介绍如何将 RT-Thread 内核移植到某个硬件平台之上。移植分为两部分:

  • CPU 架构移植

  • BSP 移植

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

CPU 架构移植,会用到 CPU 架构的汇编指令,因此如果要自己动手移植,需要熟悉一下目标 CPU 的汇编指令。

备注:在介绍移植过程的各个部分时,会以 Cortex-M 为例进行说明。

2CPU 架构移植

为了方便将 RT-Thread 移植到不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层。该抽象层用来适配不同的 CPU 架构,起到承上启下的作用。

  • 向上对内核提供统一的函数接口。包括全局中断的开关、线程初始化、上下文切换等等。

  • 向下提供了统一的 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 to);没有来源线程的上下文切换,在调度器启动第一个线程的时候调用
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 线

1. 实现全局中断开关

RT-Thread 为了解决临界区问题,提供了一系列的线程间同步和同步机制。这些机制内部实现都需要用到 libcpu 里提供的全局中断开关函数,他们分别是:

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

以 Cortex-M 为例,其快速开关中断的指令如下(汇编代码):

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

关闭全局中断

关闭全局中断函数 rt_hw_interrupt_disable() 内部需要依次完成如下功能:

  • 保存当前的全局中断状态,并把状态作为函数的返回值

  • 关闭全局中断。

基于 MDK,在 Cortex-M 内核上实现关闭全局中断,代码如下(已经添加注释):

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

上面代码中,寄存器 r0 存储的数据就是函数的返回值。

打开全局中断

打开全局中断函数 rt_hw_interrupt_enable(rt_base_t level) 中,参数 level 为需要恢复的全局中断状态。

基于 MDK,在 Cortex-M 内核上打开全局中断的代码如下(已经添加注释):

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

该函数用 MSR 指令将寄存器 r0 的值写入到 PRIMASK 寄存器中,恢复之前的中断状态。

2. 实现线程栈初始化

在线程创建过程中,会调用栈初始化函数 rt_hw_stack_init(),对线程栈进行初始化。该函数内部,会构造一个上下文内容,这个上下文内容被当作每个线程第一次执行的初始值。

Cortex-M 的栈初始化代码如下:

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

3. 实现上下文切换

CPU 架构不同,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能会有所差异。在 Cortex-M 里面上下文切换都是统一使用 PendSV 异常来完成,切换部分并没有差异。

为了能适应不同的 CPU 架构, RT-Thread 的 libcpu 抽象层需要实现三个线程切换相关的函数:

  • rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候 被调用。

  • rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。

  • rt_hw_context_switch_interrupt():在中断环境下,从当前线程切换到目标线程。

线程环境下,调用 rt_hw_context_switch() 函数,可以立即进行上下文切换。在中断环境下,需要等待中断服务函数完成之后才能进行切换。

实现 rt_hw_context_switch_to()

该函数只有目标线程,没有来源线程。实现流程图:

图片

在 Cortex-M3 内核上的 rt_hw_context_switch_to() 实现(基于 MDK),其代码如下:

;/*
; * 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]
  
  ; 设置from 线程为空,表示不需要从保存 from 的上下文
  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] ; 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()

这两个函数都有两个参数 from 和 to,他们实现从 from 线程切换到 to 线程的功能。流程图如下:

图片

在 Cortex-M3 内核上的 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 实现(基于 MDK),如下代码的所示:

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

实现 PendSV 中断

在 Cortex-M3 里, PendSV 中断处理函数是 PendSV_Handler()。在该函数里完成线程切换的实际工作,下图是具体的流程图:

图片

image-20220313230627420

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_thread_switch_interrupt_flag 变量
  ; 如果为 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

4. 实现系统时钟节拍

开关全局中断和上下文切换功能实现之后,就可以完成 RTOS 的线程/任务创建、运行、调度等功能。

除此之外,还需要实现系统的时钟节拍。系统定时器、延时,以及相同优先级线程之间的时间片轮转调度,都需要时钟节拍的支持。

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();
}

3BSP 移植

相同的 CPU 架构在实际项目中,不同的板卡上可能使用相同的 CPU 架构,搭载不同的外设资源,完成不同的产品,所以我们也需要针对板卡做适配工作。RT-Thread 提供了 BSP 抽象层来适配常见的板卡。

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

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

  • 1)初始化 CPU 内部寄存器,设定 RAM 工作时序。

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

  • 3)实现串口和 GPIO 驱动。

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

RT-Thread 源码中,已经适配好了很多 CPU 架构。如果自己所用的板卡或者CPU架构已经被官方支持,那么就不需要自己去移植了,直接就可以把移植好的文件拿来用。

图片

4小结

本篇文章重点介绍了 RT-Thread 移植的具体流程,以及移植工作涉及到的原理知识等。

通过学习 RT-Thread 的移植过程,可以了解到 RTOS 移植需要完成的工作。真正的移植工作,需要了解 CPU 架构、系统内核移植需要完成的函数等等,对开发者的技术要求比较高。

但是,对于初学者来说,不用担心。RTOS 官方一般会将主流的 CPU 移植适配好,直接拿来用即可。

另外,若想将一款 RTOS 用到项目中,可以先看看官方使用文档,会有详细的指导说明。主流的 CPU 架构一般都会适配,按照文档说明,可以快速将系统用起来。

OK,今天先到这,下次继续。加油~

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

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

相关文章

MySQL第二课表的增删插改

&#x1f49b; 后端进行的表的操作增删查改 现在是建了一个成绩表&#xff0c;注意哈。 decimal(2,1). 2是M表示有两个有效数字长度&#xff0c;1是D的长度&#xff0c;即小数点后有一位(10分制) &#x1f493;开始 1.增加&#xff1a; insert into 表名 values(值&#xff0…

安装VS Code 和 MiKTeX开发环境

下载&#xff1a; Getting MiKTeX 然后以管理员方式运行安装。 配置VS Code 之后配置VS Code&#xff0c;选择扩展&#xff08;两个位置都可以&#xff09;&#xff0c;然后搜索Latex&#xff1a; 然后打开设置&#xff1a; 这样就打开了setting.json文件&#xff0c; 然后…

SQL注入之Oracle环境搭建

SQL注入之Oracle环境搭建 前言 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性…

gitlab上传新项目全过程

gitlab上传新项目全过程 一、前期准备1.1 gitlab配置1.2 gitlab安装1.3 需要在gitlab上新建一个空项目 二、本地操作2.1 gitlab上传新项目全过程2.2 gitlab将远程项目拉取到本地全过程 三、常见问题及解决四、常用命令4.1 代码更新提交命令4.2 其他指令 一、前期准备 1.1 gitl…

less中引入自定义字体文件

前言 一般做后台管理系统UI没有影响要求可以不使用自定义字体。但是在大屏项目中&#xff0c;高度自定义化&#xff0c;就肯定需要UI导出字体文件&#xff0c;然后放到服务器上或者是我们项目文件中&#xff0c;我们前端引入后在页面中使用。 下面以放在项目文件中为例。 各…

【linux】五种IO模型与非阻塞IO

文章目录 一、IO的概念二、IO的五种模型2.1 概念2.2 对比五种IO 三、非阻塞IO3.1 fcntl文件描述符控制3.2 以非阻塞轮询方式读取标准输入 一、IO的概念 前面我们说过其实IO就是拷贝数据。 先说一下读取的接口&#xff1a; 当系统调用read/recv的时候会有两种情况 ①没有数据&a…

【C语言】指针进阶(3)

目录 指针和数组笔试题解析 一维数组 字符数组 二维数组 指针笔试题 在前面两篇文章&#xff0c;我们已经学完了指针进阶的所有知识点。在这篇文章中&#xff0c;我们主要学习的是一些常见的笔试题的总结。 指针和数组笔试题解析 在做题之前&#xff0c;我们先复习一下之…

第三讲:k8s核心概念和专业术语

序言&#xff1a;这里只对概念继续基础阐述&#xff0c;不做具体案例&#xff0c;这位博主写的特别详细&#xff0c;想要对k8s深入的了解可以跳转了&#xff0c;作为小白的我看的有点懵&#xff0c;毕竟没实践过 链接地址→ http://t.csdn.cn/ZYtEF 这篇文章写了将近两万字对各…

mybatis-plus 缓存深入实践(二)

mybatis-plus 缓存&#xff08;一&#xff09;回顾、缓存&#xff08;二&#xff09;深入实践

3D测量之圆柱轴线直线度测量

视频演示效果 圆柱轴线直线度测量 零、效果图 一、目标 测量圆柱轴线的直线度误差&#xff1b; 二、测量方法–轴截面法[1] 本文主要是通过最小二乘法确定各截面中心坐标值。由各截面测得的实际中心构成测得中心线。按误差评定方法进行数据处理&#xff0c;求出轴线的直线度误…

启动es容器错误

说明&#xff1a;启动es容器&#xff0c;刚启动就停止&#xff0c;查看日志&#xff0c;出现以下错误信息&#xff08;java.lang.IllegalArgumentException: Plugin [analysis-ik] was built for Elasticsearch version 8.8.2 but version 7.12.1 is running&#xff09; 解决&…

【状态估计】基于UKF、AUKF的电力系统负荷存在突变时的三相状态估计研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码及数据 &#x1f4a5;1 概述 基于UKF和AUKF的电力系统负荷存在突变时的三相状态估计研究是一种利用无迹卡尔曼滤波&#xff08;Unscented Kalman Filter, UKF&#xff09…

学习Dart语言---2023-07-23

环境搭建---windows Dart for WindowsDart installer for Windows. Installs the latest Dart SDK and Dartium.https://gekorm.com/dart-windows/选择标准版&#xff0c;下载安装&#xff0c;一直next 验证安装成功&#xff1a; IDEA中配置dart SDK 下载dart插件 创建dart文…

用Python脚本自动采集金融网站当天发布的免费报告

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 其间旦暮闻何物&#xff1f;杜鹃啼血猿哀鸣。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python群【林生】问了一个Python数据采集的问题&#x…

PCL点云处理之最小二乘直线拟合(2D| 方法2)(❤亲测可用❤)(二百零一)

PCL点云处理之最小二乘直线拟合(2D| 方法2)(❤亲测可用❤)(二百零一) 一、算法简介二、算法实现1.代码2.结果一、算法简介 在二百章中,我们介绍了一种最小二乘拟合直线点云(2D)的方法,可以获取直线方程系数k,b,这里介绍另一种拟合直线点云的方法,更为简单方便,结果…

引入第三方字体库 第三方字体库Google Fonts

googlefonts官方网站 googlefonts中国网站 本人是在微信小程序中引入 在static中建一个文件夹font-family 例如字体链接&#xff1a;https://fonts.font.im/css?familyKirangHaerang 将该链接的返回的资源的复制到css文件中 font-family.css /* [0] */ font-face {font-fam…

Linux学习之Ubuntu 20.04安装内核模块

参考博客&#xff1a;Ubuntu20.04编译内核教程 sudo lsb_release -a可以看到我当前的系统是Ubuntu 20.04.4&#xff0c;sudo uname -r可以看到我的系统内核版本是5.4.0-100-generic。 sudo apt-get install -y libncurses5-dev flex bison libssl-dev安装所需要的依赖。 su…

国密SSL优势及应用场景

国密SSL的优势主要有以下几点&#xff1a; 更高的安全性&#xff1a;国密算法采用的是国家密码管理局推荐的算法&#xff0c;相对于传统的SSL协议更加安全。 更好的性能&#xff1a;国密算法是国家密码管理局推荐的算法&#xff0c;其加密效率与密钥长度相比传统算法更高。 更…

Java集合之List

ArrayLsit集合 ArrayList集合的特点 ArrayList集合的一些方法 ①.add(Object element) 向列表的尾部添加指定的元素。 ②.size() 返回列表中的元素个数。 ③.get(int index) 返回列表中指定位置的元素&#xff0c;index从0开始。 public class Test {public static void m…

【Python学习笔记】记载解决Python报错HTTP Error 403: Forbidden的一波三折过程

【Python学习笔记】记载解决Python报错HTTP Error 403: Forbidden的一波三折过程 当前进度&#xff1a;还没有解决&#xff0c;但是已经尝试了好几种办法&#xff0c;此处做个记录&#xff0c;也许能帮上忙。 本帖是整理回顾帖&#xff0c;不是教程帖&#xff0c;追求一个完美…