本文旨在记录研究RT-thread的源码的过程。
Written by: Zhai Xiufeng
1. 概述
RT-thread是一个实时操作系统,也就是操作系统是以时间片和优先级共同来判定和分配CPU的执行资源。
2. 移植和搭建环境
2.1 RT系统源码和工具下载
源码下载地址:根据需求选择RT的系统版本即可。一般是选择某个大版本的最新版本。
GitHub - RT-Thread/rt-thread: RT-Thread is an open source IoT real-time operating system (RTOS).
ENV工具下载地址:
GitHub - RT-Thread/env: Python Scripts for RT-Thread/ENV
3. 系统挂载初始化
系统运行在MCU中,需要如同片上外设一般初始化,因为操作系统本质上是根据MCU的内核和片上外设的机制,来运行的一个功能。所以需要配置片上外设和基础变量初始化。
RT-Thread初始化接口的名称是rtthread_startup一般是执行在启动文件中_cmain中被执行。
初始化通过env建立新的工程时,会自动挂载,不太需要我们去管理。
4. 系统机制分析
实时操作系统(Real-Time Operating System,RTOS)具备一些关键功能,以满足实时应用的需求。以下是RTOS常见的功能:
任务管理:
多任务支持:允许多个任务并发运行。
任务优先级:任务可以分配不同的优先级,优先级高的任务会优先执行。
任务调度:实时调度器根据任务的优先级和时间限制,决定任务的执行顺序。
任务同步和通信:提供任务间同步和通信机制,如信号量、消息队列和事件标志。
中断管理:
快速中断响应:能够快速响应和处理硬件中断。
中断优先级:支持中断优先级设置,以确保关键中断优先处理。
内存管理:
静态和动态内存分配:支持静态和动态内存分配,确保内存使用的高效性和安全性。
内存保护:防止任务间的内存访问冲突,提高系统稳定性和安全性。
定时器和时钟管理:
精确定时:提供高精度的定时器,用于时间敏感的任务。
延时和超时功能:支持任务的延时和超时处理。
4.1 多任务支持
任务也就是当前执行业务代码,项目中任务会很多,比如显示、规约等等。在嵌入式中,任务也会被称作线程,一个进程有多个线程。根据嵌入式原理,业务代码是通过寄存器堆栈执行在内核中。但是由于一般MCU只存在一个内核和一些其他的限制,无法自动支持多任务运行,这个时候就需要软件辅助来促使多个任务在内核中被执行。
多任务支持(Multitasking Support)是实时操作系统(RTOS)和一般操作系统,使用一个核心让进程中能够在看似同时地运行多个任务。
多任务的支持内容的核心就是任务极短时间内完成切换,中间无法感知到线程执行的延时。
这其中有个特殊的中断(PendSV_Handler),该中断是多任务能够实行的必要条件。PendSV(可悬挂的系统服务调用,Pendable Service Call)是 ARM Cortex-M 系列处理器中的一种特殊中断。它设计用于操作系统的上下文切换,它是为了切换任务而专门设计的。PendSV 中断的优先级通常被设置为最低,这样它可以在其他更高优先级的中断处理完之后再执行。
原理如下:
保存当前任务上下文:
保存当前任务的寄存器状态(如程序计数器、堆栈指针、通用寄存器)。
将当前任务的上下文信息存储在任务控制块(管理和调度任务的关键数据结构)中。
选择下一个任务:
调度器从就绪队列中选择下一个最高优先级的任务。
更新当前任务指针以指向新任务的任务控制块。
恢复新任务上下文:
从新任务的任务控制块中恢复寄存器状态。
设置程序计数器指向新任务的执行地址。
恢复堆栈指针指向新任务的堆栈。
注意:当 PendSV_Handler 中的 BX LR 指令执行时,处理器会根据 PSP 恢复 R0-R3, R12, LR, PC, xPSR,这些寄存器的值是在新线程的堆栈中保存的。因此,恢复的新值是新线程的上下文。流程是当前线程执行指令时,中断来临,执行完当前的指令,LR保存当前线程的下一个指令,STM32自动压栈保存特殊寄存器的值。执行以下指令实现保存普通寄存器的值,最后执行BX LR,弹出PSP内的特殊寄存器值,跳回到另一个线程前面进入中断的原来LR压栈内容。
实现以上功能的代码如下:
; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
EXPORT PendSV_Handler
PendSV_Handler:
; 禁用中断以保护上下文切换
MRS r2, PRIMASK ; 将当前中断状态(PRIMASK寄存器)保存到r2中
CPSID I ; 禁用中断,防止嵌套的PendSV处理
; 获取 rt_thread_switch_interrupt_flag
LDR r0, =rt_thread_switch_interrupt_flag ; 加载线程切换中断标志的地址
LDR r1, [r0] ; 加载线程切换中断标志的值到r1中
CBZ r1, pendsv_exit ; 如果标志为0,表示PendSV已经处理,直接退出
; 将 rt_thread_switch_interrupt_flag 清零
MOV r1, #0x00 ; 将r1设为0