RT-Thread
RT-Thread 版权属于上海睿赛德电子科技有限公司,于 2006年 1月首次发布,初始版 本号为0.1.0,经过 10来年的发展,如今主版本号已经升级到3.0,累计开发者达到数百万, 在各行各业产品中装机量达到了惊人的2000多万,占据国产RTOS的鳌头。
在接触RT-Thread之前我已经深入学习过FreeRTOS实时操作系统,感觉这些操作系统的形式都大差不差,自认为无非是各种功能的函数名字不一样,时间片的实现,启动流程有些许不同。
RT-Thread和FreeRTOS类似,都拥有线程管理(在RT-Thread中叫线程,在FreeRTOS中叫任务),消息队列,信号量(二值,计数),互斥量,事件,软件定时器,内存管理,中断管理。比
FreeRTOS多了个邮箱(目前看来根据野火的源码分析,感觉和消息队列功能差不多)。
启动流程
接下来讲一下RT-Thread的启动流程
当你拿到一个移植好的 RT-Thread工程的时候,你去看 main函数,只能在 main函数 里面看到创建线程和启动线程的代码,硬件初始化,系统初始化,启动调度器等信息都看 不到。
那是因为RT-Thread拓展了main函数,在main函数之前把这些工作都做好了。
我们知道,在系统上电的时候第一个执行的是启动文件里面由汇编编写的复位函数 Reset_Handler。复位函数的最后会调用 C 库函数__main,__main 函数的主要工作是初始化系统的堆和栈,最后调用 C 中 的main函数,从而去到C的世界。
执行完__main之后,并不是跳转到C 中的main函数,而是跳转到component.c中的$Sub$$main函数,这是为什么?因为RT Thread使用编译器(这里仅讲解KEIL,IAR或者GCC稍微有点区别,但是原理是一样的) 自带的$Sub$$和$Super$$这两个符号来扩展了main函数。
使用$Sub$$main可以在执行 main之前先执行$Sub$$main,在$Sub$$main函数中我们可以先执行一些预操作,当做完这 些预操作之后最终还是要执行main函数。
这个就通过调用$Super$$main来实现。当需要扩展的函数不是main的时候,只需要将main换成你要扩展的函数名即可,即$Sub$$function 和$Super$$function。(mdk的扩展功能)
首先执行$Sub$$main函数,在其中包含的内容有:
- 关闭中断,除了硬FAULT和NMI可以响应外,其它统统关掉。
- 执行rtthread_startup()函数。
rtthread_startup()函数
RT-Thread 启动的时候会调用一个名为 rt_hw_board_init()的函数, 从函数名称我们可以知道它是用来初始化开发板硬件的,需要把硬件相关的初始化都放在 rt_hw_board_int()函数里面完成,比如时钟,比如串口等,具体初 始化什么由用户选择。
当这些硬件初始化好之后,RT-Thread 才继续往下启动,(就是把各种外设初始化放在这个函数之中去执行),RT-Thread该函数需要我们在board.c中自己编写。
由图片可见,在rtthread_startup()函数中先是关闭中断,板级硬件初始化。打印RT-Thread的版本号(要想成功打印,必须重映射一个控制台到rt_kprintf函数 )后面进行定时器,调度器,信号的初始化
最终创建一个初始线程。这个初始线程类似于FREERTOS中的创建任务的任务,就是在这个任务中去创建工程所用到的所有的任务。
RT-Thread的启动流程是这样的: 即先创建一个初始线程,等调度器启动之后,在这个初始线程里面创建各种应用线程,当 所有应用线程都成功创建好后,初始线程就把自己关闭。那么这个初始线程就在 rt_application_init()里面创建。
该函数也在component.c里面定义,我们可以去component.c中修改,一般情况下是在rt_application_init()中创建main主函数的线程,在main主函数线程中去创建其他的线程,其中硬件各种外设的初始化已经在rt_hw_board_int()中完成了,所以main主函数中只需要去创建其他的线程就可以了。
rt_application_init()函数
在这个函数中判断使用静态还是动态方法创建main线程,也就是void main_thread_entry(void*parameter),并初始化相关参数。
void main_thread_entry(void*parameter)
在main主函数线程中除了调用rt_components_init()函数进行 RT-Thread的组件初始化外,最终是调用main的扩展函数$Super$$main()回到main函数。
这个是必须的,因为我们一开始在进入main函数之前,通过$Sub$$main()函数扩展了main 函数,做了一些硬件初始化,RTOS系统初始化的工作,当这些工作做完之后最终还是要 回到main函数,那只能通过调用$Super$$main()函数来实现。$Sub$$和$Super$$是MDK 自带的用来扩展函数的符号,通常是成对使用。
main函数执行到最后,通过LR寄存器指定的链接地址退出,在 创建main线程的时候,线程栈对应LR寄存器的内容是rt_thread_exit()函数,在 rt_thread_exit里面会把main线程占用的内存空间都释放掉。
至此,结束了,RT-Thread的整个启动流程。
在我看来这也是RT-Thread和FreeRTOS的主要区别。
重映射串口到rt_kprintf函数
在RT-Thread中,有一个打印函数rt_kprintf()供用户使用,方便在调试的时候输出各 种信息。如果要想使用rt_kprintf(),则必须将控制台重映射到rt_kprintf(),这个控制台可以 是串口、CAN、USB、以太网等输出设备,用的最多的就是串口,
SysTick_Handler()
这一部分是rtthread用于更新时基,实现时间片的功能代码。将这一部分直接放在SysTick_Handler()中执行。
在线程里面的延时函数必须使用RT-Thread里面提供的延时函数,并 不能使用我们裸机编程中的那种延时。这两种的延时的区别是RT-Thread里面的延时是阻 塞延时,
即调用rt_thread_delay()函数的时候,当前线程会被挂起,调度器会切换到其它就 绪的线程,从而实现多线程。如果还是使用裸机编程中的那种延时,那么整个线程就成为 了一个死循环,如果恰好该线程的优先级是最高的,那么系统永远都是在这个线程中运行, 根本无法实现多线程。
该特性在所有操作系统都是一样的,可以在所有外设初始化的时候使用delay,在任务或者线程中只能使用操作系统提供的延时函数。
关于RT-Thread的其他系统功能,其实和FreeRTOS差不多,总而言之还是去查询API函数的使用方法在工程中。感觉掌握了一款实时操作系统其他的操作系统入门相对来说也更加容易了。
共勉,绝望之为虚妄,正与希望相对。我的理解是现实有多绝望的同时,希望就有多大!