RT-Thread 操作系统介绍、移植及内核介绍
一、介绍
1.1、愿景
做世界级的 OS,让万物互联,信息畅通无阻,成为未来 AIoT 领域最为主流的操作系统平台。
1.2、简介
RT-Thread 是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,由熊谱翔先生带领并集合开源社区力量开发而成,RT-Thread 也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。RT-Thread 具备一个 IoT OS 平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。经过11年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过 8亿 台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
RT-Thread 拥有良好的软件生态,支持市面上所有主流的编译工具如 GCC、Keil、IAR 等,工具链完善、友好,支持各类标准接口,如 POSIX、CMSIS、C++应用环境、Javascript 执行环境等,方便开发者移植各类应用程序。商用支持所有主流MCU架构,如 ARM Cortex-M/R/A, MIPS, X86, Xtensa, C-Sky, RISC-V,几乎支持市场上所有主流的 MCU 和 Wi-Fi 芯片。
1.3、发展历史
诞生于2006年,最初源于对当时小型RTOS现状的诸多不满,RT-Thread要做一个精致而优雅的操作系统
1.4、RTT OS概述
参考资料:https://www.rt-thread.org/document/site/tutorial/quick-start/introduction/introduction/
RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,在 RT-Thread 系统中,任务通过线程实现的。
RT-Thread 主要采用 C 语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非
常好。针对资源受限的微控制器(MCU)系统,可通过方便易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 版本(NANO 是 RT-Thread 官方于
2017 年 7 月份发布的一个极简版内核);而对于资源丰富的物联网设备,RT-Thread 又能使用在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,
无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。
相较于 Linux 操作系统,RT-Thread 体积小,成本低,功耗低、启动快速,除此以外 RT-Thread 还具有实时性高、占用资源小等特点,非常适用于各种资源受限
的场合。虽然 32 位 MCU 是它的主要运行平台,实际上很多带有 MMU、基于 ARM9、ARM11 甚至 Cortex-A 系列级别 CPU 的应用处理器在特定应用场合也适合使用
RT-Thread。
1.5、RT-Thread 架构
近年来,物联网(Internet Of Things,IoT)概念广为普及,物联网市场发展迅猛,嵌入式设备的联网已是大势所趋。终端联网使得软件复杂性大幅增加,传统的 RTOS 内核
已经越来越难满足市场的需求,在这种情况下,物联网操作系统(IoT OS)的概念应运而生。物联网操作系统是指以操作系统内核(可以是 RTOS、Linux 等)为基础,包括如文
件系统、图形库等较为完整的中间件组件,具备低功耗、安全、通信协议支持和云端连接能力的软件平台,RT-Thread 就是一个 IoT OS。
RT-Thread 与其他很多 RTOS 如 FreeRTOS、uC/OS 的主要区别之一是,它不仅仅是一个实时内核,还具备丰富的中间层组件,如下图所示
二、RT-Thread移植
2.1、分类
2.1.1、标准版本
如上为标准版本
2.1.2、Nano版本
RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。
下图是 RT-Thread Nano 的软件框图,包含支持的 CPU 架构与内核源码,还有可拆卸的 FinSH 组件:
功能:线程管理、线程间同步与通信、时钟管理、中断管理、内存管理。
Nano 的特点:https://docs.rt-thread.org/#/rt-thread-version/rt-thread-nano/an0038-nano-introduction?id=nano-%e7%9a%84%e7%89%b9%e7%82%b9
☐ 下载简单
☐ 代码简单
☐ 移植简单
☐ 使用简单
☐ 资源占用小
☐ 开源免费
2.1.3、Smart版本
RT-Thread Smart(简称 rt-smart)是基于 RT-Thread 操作系统衍生的新分支,面向带 MMU,中高端应用的芯片,例如 ARM Cortex-A 系列芯片,MIPS 芯片,带 MMU 的 RISC-V 芯片等。rt-smart 在 RT-Thread 操作系统的基础上启用独立、完整的进程方式,同时以混合微内核模式执行。
RT-Thread Smart 是基于 RT-Thread 操作系统上的混合操作系统,简称为 rt-smart,它把应用从内核中独立出来,形成独立的用户态应用程序,并具备独立的地址空间(32 位系统上是 4G 的独立地址空间)
2.2、标准版RTT移植
下载安装RT-Thread_Studio,安装完成后注册并登录后方可创建RTT工程。
1、新建工程,打开 IDE,点击【文件】-【新建】-【RT-Thread 项目】
2、进入新建工程设置向导
3、工程创建完毕,连接硬件,可直接进行编译下载,如下所示:
4、通过修改 board.c 的 SystemClock_Config() 更改系统时钟
5、由于在创建工程向导中配置了控制台串口号及其引脚号,所以工程中已经实现了 uart 的驱动以及 rt_hw_console_output() ,默认可以进行打印。打开串口终端,可以发现在终端中执行了打印
6、添加 FinSH
双击 RT-Thread Settings 进入配置,打开组件,勾选 FinSH Shell,保存配置。此操作将把 FinSH 组件的源码加入工程中。
其中,rt_hw_console_getchar() 已经在 drv_uart.c 中实现,无需再实现对接 FinSH 的代码。
7、链接硬件,编译下载后,在串口终端中按下 Tab 键,可查看系统中的命令:
三、RT-Thread内核介绍
3.1、内核框架
内核是操作系统最基础也是最重要的部分。下图为 RT-Thread 内核架构图,内核处于硬件层之上,内核部分包括内核库、实时内核实现。
内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。这部分根据编译器的不同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带更多的标准 C 库实现。
实时内核的实现包括:
☐ 对象管理
☐ 线程管理及调度器
☐ 线程间通信管理
☐ 时钟管理
☐ 内存管理
☐ 设备管理
内核最小的资源占用情况是 3KB ROM,1.2KB RAM
3.1.1、线程调度
线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。支持 256 个线程优先级(也可通过配置文件更改为最大支持 32 个或 8 个线程优先级,针对 STM32 默认配置是 32 个线程优先级),0 优先级代表最高优先级,最低优先级留给空闲线程使用;同时它也支持创建多个具有相同优先级的线程,相同优先级的线程间采用时间片的轮转调度算法进行调度,使每个线程运行相应时间;另外调度器在寻找那些处于就绪状态的具有最高优先级的线程时,所经历的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关。
3.1.2、时钟管理
RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的时钟单位。RT-Thread 的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止定时器否则将永远持续执行下去。
另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器可以设置为 HARD_TIMER 模式或者 SOFT_TIMER 模式。
通常使用定时器定时回调函数(即超时函数),完成定时服务。用户根据自己对定时处理的实时性要求选择合适类型的定时器。
3.1.3、线程间同步
RT-Thread 采用信号量、互斥量与事件集实现线程间同步。线程通过对信号量、互斥量的获取与释放进行同步;互斥量采用优先级继承的方式解决了实时系统常见的优先级翻转问题。线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。线程通过对事件的发送与接收进行同步;事件集支持多事件的 “或触发” 和“与触发”,适合于线程等待多个事件的情况。
3.1.4、线程间通信
RT-Thread 支持邮箱和消息队列等通信机制。邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。邮箱效率较消息队列更为高效。邮箱和消息队列的发送动作可安全用于中断服务例程中。通信机制支持线程按优先级等待或按先进先出方式获取。
3.1.5、内存管理
RT-Thread 支持静态内存池管理及动态内存堆管理。当静态内存池具有可用内存时,系统对内存块分配的时间将是恒定的;当静态内存池为空时,系统将申请内存块的线程挂起或阻塞掉 (即线程等待一段时间后仍未获得内存块就放弃申请并返回,或者立刻返回。等待的时间取决于申请内存块时设置的等待时间参数),当其他线程释放内存块到内存池时,如果有挂起的待分配内存块的线程存在的话,则系统会将这个线程唤醒。
动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 SLAB 内存管理算法。
还有一种动态内存堆管理叫做 memheap,适用于系统含有多个地址可不连续的内存堆。使用 memheap 可以将多个内存堆 “粘贴” 在一起,让用户操作起来像是在操作一个内存堆。
3.1.6、I/O 设备管理
RT-Thread 将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。实现了按名称访问的设备管理子系统,可按照统一的 API 界面访问硬件设备。在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。
3.2、RTT内核启动流程
RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main(),如下图所示:
3.2.1、汇编阶段
Reset_Handler: //复位
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
ldr r2, =_sbss
b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss
/* Call the clock system intitialization function.*/
bl SystemInit
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.*/
bl entry
bx lr
主要过程:
☐ 从Flash中拷贝数据段到SRAM中
☐ 清空BSS段(BSS段清零)
☐ 初始化系统时钟(SystemInit)
☐ 进入entry入口
3.2.2、C阶段
【1】系统时钟初始化
system_stm32f1xx.c 中的系统初始化函数,参考之前章节内容
时钟系统配置文件board.h
使用外部高速时钟,时钟源晶振8MHz,系统时钟72MHz
【2】entry入口
int entry(void)
{
rtthread_startup();
return 0;
}
3.2.2、rtthread_startup函数
int rtthread_startup(void)
{
rt_hw_interrupt_disable(); //关闭硬件中断
/*
* board level initialization
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* signal system initialization */
rt_system_signal_init();
#endif
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
#ifdef RT_USING_SMP
rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/
/* start scheduler */
rt_system_scheduler_start();
return 0;
}
主要过程:
☐ 初始化系统相关硬件
☐ 初始化系统内核对象、例如定时器、调度器、信号
☐ 创建主线程、定时器线程、idle线程
☐ 启动调度器
3.2.3、创建主线程
线程函数入口:main_thread_entry
栈大小:2048
优先级:10
同等优先级时间片轮询时间:20 个OS Tick rfconfig.h 中配置 :
#define RT_TICK_PER_SECOND 1000 Tick每秒1000次,一次的时间为1ms
//创建线程,线程函数main_thread_entry
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
//开启线程 —— 将线程加入到系统的线程队列中,等待系统线程调度器遍历队列调用
rt_thread_startup(tid);
开启线程调度器
//选择优先级最高的线程开始调度
rt_system_scheduler_start();
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
#ifdef RT_USING_COMPONENTS_INIT
/* RT-Thread components initialization */
rt_components_init();
#endif
#ifdef RT_USING_SMP
rt_hw_secondary_cpu_up();
#endif
/* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */ //进入用户的main函数入口
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}
注意:
S
u
b
Sub
Sub
f
o
o
:定义的新功能函数,在
f
o
o
(
)
函数之前
/
后使用
foo :定义的新功能函数,在foo()函数之前/后使用
foo:定义的新功能函数,在foo()函数之前/后使用Sub$ $foo 可以添加一些新的程序代码。
S
u
p
e
r
Super
Super
f
o
o
:就是原始的未修补的
f
o
o
函数,使用这个
foo :就是原始的未修补的foo函数,使用这个
foo:就是原始的未修补的foo函数,使用这个Super$ $foo函数将直接跳转到foo()函数。