RTXThread 基于rt-thread和RTX5的衍生版操作系统
Git链接
基于GD32F303
ARM Cortex-M4的示例:
https://github.com/Yanye0xFF/RTXThread
概述
这并不是一个全新的实时操作系统,而是基于rt-thread设备驱动框架
和RTX5内核
组合而来的衍生版系统。其中操作系统的应用层接口使用了CMSIS RTOS2 API
,这是ARM公司为RTOS内核制定的一套通用接口协议,并且有很多实时系统内核的实现。
CMSIS-RTOS2兼容层 | 链接 |
---|---|
RT-Thread | https://github.com/RT-Thread-packages/CMSIS_RTOS2 |
FreeRTOS | https://github.com/ARM-software/CMSIS-FreeRTOS |
LiteOS | https://support.huaweicloud.com/lib-LiteOS/zh-cn_topic_0314621635.html |
RTX5的优势:
- 系统调度采用SVC软中断进入内核态(中断环境),因此内核函数使用主栈空间(MSP)和线程栈(PSP)空间分离。调度器相关逻辑不会关闭中断,因此可以利用Cortex-m的中断嵌套(NVIC)优先保证高实时的中断优先处理,非常适合做运动控制相关。
- 支持Cortex-M,Cortex-A系列核心,以及多核。由于内核函数全部使用了系统调用(SysCall),便于在此基础上拓展出用户应用和系统内核分离的微内核架构操作系统(cortex-m以固定地址的multibin实现,不带MPU的型号无法支持保护模式)。
- RTX5只提供实时内核的基本功能,代码量和复杂度较低,以它为基础进行二次开发比较灵活自由。
- 最重要的是RTX5的汽车级,工业级,医疗和铁路安全认证已经通过。
标准 | 说明 |
---|---|
ISO 26262 (ASIL D) | 汽车级最高安全认证 |
IEC 61508 (SIL 3) | 工业级认证 |
IEC 62304 (Class C) | 医疗认证 |
EN 50128 (SIL 4) | 运输/铁路安全认证 |
文档链接:
https://arm-software.github.io/CMSIS_5/RTOS2/html/index.html
源码链接:
https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/RTOS2
rt-thread的优势:
- 开源社区比较活跃,用的人很多,相比于rtx5,有非常多的第三方外设驱动以及内核组件。
- 面向对象c语言思想,linux代码风格,适合已掌握java/cpp这类高级语言的人学习和使用。
- 安全认证有IEC61508 SIL3,EN50128 SIL4,ISO 26262 ASIL-D 参考链接:https://www.rt-thread.org/newsDetail.html?id=3bf39c57321d895a
- rt-thread类似linux的POSIX接口的设备驱动框架给外设驱动兼容带来了巨大便利,我认为统一的外设驱动框架也是rt-thread有很多第三方组件的重要基础。
源码链接:
https://github.com/RT-Thread/rt-thread
RTXThread系统架构:
主要特性:
- 既有RTX5实时内核的高性能,又能享受到rt-thread的设备驱动框架以及丰富的软件生态。
- 任务调度,线程间通信(信号量,互斥锁,消息队列...)完全使用RTX5的实现,无任何改动。
- 全局内存池(Global Memory Pool)移除了RTX5的
rtx_memory.c
,使用rt-thread中的SMALL_MEM
(mem.c 实际上是lwip项目中的动态内存实现),可以同时使用rt-thread和CMSIS RTOS2的内存分配和释放API。 - C库继承自rt-thread使用
newlib
,适配了printf,malloc,free等常见的桩函数(stub-function)。 - 完整适配了rt-thread设备驱动框架,支持spi,can,uart等。
- 支持finsh控制台,自动初始化(INIT_BOARD_EXPORT...),精简的cm_backtrace。
- 默认的调试输出组件使用
Segger RTT
,通过SWD接口输出。 - 使用rt-thread设备驱动框架规避了RTX中间件的版权问题(RTX的中间件RL-FlashFS, RL-USB, RL-TCPnet等需要购买正版的KEIL-MDK)。
由于携带了以上的一些外围组件,在软件跨平台和开发调试上带来很大方便,但也导致了RTXThread不是一个轻量系统,其编译后占用的ROM和RAM空间也是比较大的。
提供示例的编译选项:
- O2优化等级
- 关闭newlib
--specs=nano.specs
, - 使用浮点数printf支持
-u _printf_float
- Segger RTT输出缓存占用4KB RAM
Invoking: GNU Arm Cross Print Size
arm-none-eabi-size --format=berkeley "RTXThread.elf"
text data bss dec hex filename
70792 1636 7856 80284 1399c RTXThread.elf
Finished building: RTXThread.siz
若启用newlib-nano
属性,关闭浮点数printf支持,.text段(ROM)能减少13KB。
若关闭Segger RTT功能,.bss段(RAM)能减小4KB。
体验预览
- 系统启动
00> RTXThread Operating System
00> Powered by RT-Thread and RTX5
00> Version info:
00> RT-Thread 4.1.1
00> CMSIS RTOS2 API 20010003
00> RTX Kernel 50050004
00> app version:12, fcpu:120000000Hz
00> msh >
- 目前支持的shell命令
< help
00> help
00> RTXThread shell commands:
00> reboot - reboot system
00> date - get date and time or set (local timezone) [year month day hour min sec]
00> dac - dac function
00> pwm_get - pwm_get <pwm_dev> <channel>
00> pwm_set - pwm_set <pwm_dev> <channel> <period> <pulse>
00> pwm_disable - pwm_disable <pwm_dev> <channel>
00> pwm_enable - pwm_enable <pwm_dev> <channel>
00> list - list objects
00> list_device - list device in system
00> show_mstack - show main stack usage
00> list_isr - list interrupt handlers
00> list_timer - list timer in system
00> list_thread - list thread
00> version - show RTXThread version information
00> clear - clear the terminal screen
00> free - Show the memory usage in the system.
00> ps - List threads in the system.
00> help - RTXThread shell help.
00> poke - write memory
00> peek - read memory
00> gpio - test gpio
00> test_div0 - divided by zero
00> test_busfault - exception bus fault
- 列出所有的线程
< ps
00> ps
00> thread pri status sp stack size stack base max used left tick error
00> ------------ --- ------- ---------- ---------- ---------- ------ ---------- ---
00> tshell 16 running 0x20003768 0x00000400 0x200033c0 39% 4 OK
00> tidle0 1 ready 0x200027e8 0x00000200 0x20002628 00% 0 OK
00> main 24 suspend 0x200031f8 0x00000800 0x20002acc 31% 2235 OK
00> timer 40 suspend 0x200029c0 0x00000200 0x20002828 08% -1 OK
00> sys_evt 23 suspend 0x20004180 0x00000400 0x20003dfc 05% -1 OK
00> msh >
- 列出当前注册到驱动框架的外设
< list_device
00> list_device
00> device type ref count
00> ------------ -------------------- ----------
00> spi-flash SPI Device 0
00> g-sensor SPI Device 0
00> spi0 SPI Bus 0
00> rtc RTC 1
00> gpio Pin Device 0
00> dac0 DAC Device 0
00> can0 CAN Device 0
00> adc0 ADC Device 0
- 异常测试:尝试写入未定义地址
int test_busfault(int argc, char **argv) {
float a = 100.0f;
float b = 2.0f;
float c = a / b;
printf("a:%f\n", c);
int *ptr = (int *)0x9f0000000;
*ptr = 100;
return 0;
}
MSH_CMD_EXPORT(test_busfault, exception bus fault);
< test_busfault
00> test_busfault
00> a:50.000000
00> hard fault on thread: "tshell"
00> thread pri status sp stack size stack base max used left tick error
00> ------------ --- ------- ---------- ---------- ---------- ------ ---------- ---
00> tshell 16 running 0x20003660 0x00000400 0x200032b8 83% 4 OK
00> tidle0 1 ready 0x200026e0 0x00000200 0x20002520 00% 0 OK
00> main 24 suspend 0x200030f0 0x00000800 0x200029c4 31% 3615 OK
00> timer 40 suspend 0x200028b8 0x00000200 0x20002720 08% -1 OK
00> sys_evt 23 suspend 0x20004078 0x00000400 0x20003cf4 05% -1 OK
00>
00> =================== Registers information ====================
00> R0 : 00000000 R1 : 22400000 R2 : 00000064 R3 : 0000000e
00> R4 : 0000000d R5 : 200031fe R6 : 08007de5 R7 : 20003658
00> R8 : 20003658 R9 : 00000000 R10: 0801222c R11: 00000000
00> R12: 0000000c LR : 08007df1 PC : 08005f40 PSR: 61010000
00> ==============================================================
00>
00> FPU is active!
00> bus fault:
00> SCB_CFSR_BFSR:0x04, Bus fault is caused by imprecise data access violation
00> System will reboot after 1 second.
- 异常测试:制造除0异常(由于我未使能
UsageFault
,异常将由中断号靠前Hard Fault
接管)
int test_div0(int argc, char **argv) {
int a = 100;
int b = 0;
int c = a / b;
printf("c:%d\n", c);
return 0;
}
MSH_CMD_EXPORT(test_div0, divided by zero);
< test_div0
00> test_div0
00> hard fault on thread: "tshell"
00> thread pri status sp stack size stack base max used left tick error
00> ------------ --- ------- ---------- ---------- ---------- ------ ---------- ---
00> tshell 16 running 0x20003660 0x00000400 0x200032b8 10% 4 OK
00> tidle0 1 ready 0x200026e0 0x00000200 0x20002520 00% 0 OK
00> main 24 suspend 0x200030f0 0x00000800 0x200029c4 31% 4485 OK
00> timer 40 suspend 0x200028b8 0x00000200 0x20002720 08% -1 OK
00> sys_evt 23 suspend 0x20004078 0x00000400 0x20003cf4 05% -1 OK
00>
00> =================== Registers information ====================
00> R0 : 00000001 R1 : 20003658 R2 : 00000000 R3 : 0000000a
00> R4 : 00000009 R5 : 200031fe R6 : 08007e05 R7 : 20003658
00> R8 : 20003658 R9 : 00000000 R10: 0801222c R11: 00000000
00> R12: 00000000 LR : 08005f41 PC : 08007e04 PSR: 61000000
00> ==============================================================
00>
00> usage fault:
00> SCB_CFSR_UFSR:0x01, Usage fault is caused by attempts to execute an undefined instruction
00> System will reboot after 1 second.
系统启动流程
RTXThread的启动流程和rt-thread基本相同,但是最终运行的是RTX5内核。
裸机阶段
+ startup_gd32f30x_hd.S
RTXThread/src/libcpu/startup_gd32f30x_hd.S是整个系统的汇编入口,MCU上电后进入Reset_Handler
子程序,在经过copy data section
和zero bss section
之后,进入SystemInit
函数,在这里进行平台相关时钟配置。由于SystemInit各个单片机的实现都不同,需要根据自己选用的芯片型号,从官方库中提取。
+ components.c
在时钟配置完成后,跳转到C语言入口entry
函数,该函数位于rtthread/components.c
,entry函数直接跳转到了rtthread_startup
:
- 首先执行板级初始化,
SystemCoreClockUpdate
函数主要是将SystemInit
配置的时钟再次读取出来更新到SystemCoreClock全局变量,方便用户验证是否配置成功以及后期使用。 - 设置中断控制器,在Cortex-M处理器中,RTX5使用SysTick_IRQn,PendSV,SVC三个中断,需要保证
SysTick_IRQn
,PendSV
都位于最低优先级,SVC
要求比最低优先级高一个等级(SVC也可以是最高优先级),即SVC > PendSV = SysTick_IRQn,如果启用了抢占优先级(中断嵌套)需要保证SysTick/PendSV的抢占优先级低于SVC。这里我使用了非抢占中断模式,4bit全部用于表示响应优先级。
在此配置情况下:
os_systick.c
SysTick_IRQn默认为最低优先级15,
rtx_core_cm.h
PendSV默认为15,SVC默认为0。
如果NVIC控制器配置成4bit全表示抢占优先级,则这三个内核中断的优先级如下:
SysTick_IRQn | PendSV | SVC |
---|---|---|
15 | 15 | 14 |
3. 执行INIT_BOARD_EXPORT
导出的初始化函数,这属于rt-thread组件自动初始化的一部分。(这里就不详细展开了)需要注意的是INIT_BOARD使用裸机栈,OS环境此时还未建立。
4. 配置堆内存,rt_system_heap_init
将bss段后面的空余RAM作为系统堆区,注意:堆内存分配器使用的互斥锁对象的控制块内存(control block)需要静态分配,因为此时内存分配器还未初始化。
5. 调用osKernelInitialize
做一些内核初始化,主要是设置isr_queue大小,时间片调度间隔,通用内存初始化(osRtxMemoryInit,RTXThread中这一步什么都不做,因此不要使用可变大小对象池),固定对象大小的内存池的初始化(osRtxMemoryPoolInit,RTXThread中可正常使用),在RTXThread系统中,osRtxMemory通用堆内存被上一步的rt_system_heap接管。
RTX_Config.h
配置文件中,以下宏需要定义为0,定义为其他值不起作用。
#define OS_THREAD_OBJ_MEM 0
#define OS_THREAD_USER_STACK_SIZE 0
#define OS_MEMPOOL_OBJ_MEM 0
#define OS_MEMPOOL_DATA_SIZE 0
#define OS_MSGQUEUE_OBJ_MEM 0
#define OS_MSGQUEUE_DATA_SIZE 0
#define OS_DYNAMIC_MEM_SIZE 0
rt_application_init
建立main
线程,最后通过osKernelStart
开启调度器。
main线程阶段
用户程序的入口是user_main
,位于application/main.c
文件中,application包中提供了一个默认的消息循环实现(类似Android中的Message Handler),用于线程间异步分发事件使用,通过system_event_loop_create
进行初始化。