阅读引言: 最近在学习RT-Thread的内部机制,觉得这个启动流程和一些底层原理还是挺重要的, 所以写下此文。
目录
1, RT-Thread简介
2,RT-Thread任务的几种状态
3, 学习资源推荐
4, 启动流程分析开始
1, RT-Thread简介
RT-Thread是一个来自中国的开源、中英文双语的实时操作系统(RTOS),它适用于各种资源受限的嵌入式系统。自2006年由熊谱翔(Bernard Xiong)创建以来,RT-Thread已经发展成为一个功能丰富、高度可伸缩、完全开源的实时操作系统。
以下是RT-Thread的一些主要特性:
-
实时性能:RT-Thread提供了高实时性能,能够满足嵌入式系统对实时性的需求。
-
可伸缩性:系统设计为可伸缩,可以运行在从几百字节内存的简单嵌入式设备到拥有大量内存的复杂系统。
-
组件丰富:RT-Thread拥有丰富的组件库,包括文件系统、TCP/IP网络协议栈、设备驱动框架等。
-
易用性:RT-Thread提供了简洁明了的API设计,易于学习和使用。
-
开源社区:拥有活跃的开源社区,众多开发者和公司贡献代码和组件。
-
跨平台:支持多种处理器架构,如ARM Cortex-M、Cortex-R、Cortex-A系列,以及MIPS、x86、XTensa等。
-
工具链支持:与多种编译器和集成开发环境(IDE)兼容,如Keil、IAR、GCC等。
-
软件包生态:RT-Thread Studio提供了丰富的软件包管理,方便开发者快速集成和使用。
-
文档和教程:提供详细的文档和教程,帮助开发者快速上手。
-
商业友好:RT-Thread的BSD许可协议非常友好,允许商业和个人用户免费使用和修改。
RT-Thread适用于各种嵌入式应用场景,包括智能家居、智能穿戴、智能安防、工业自动化、车载设备等。它的设计目标是为嵌入式开发提供稳定、高效、易用的解决方案。
2,RT-Thread任务的几种状态
状态 | 特点 |
---|---|
初始状态 | 创建完任务还未开启任务调度器, 此时任务就属于此种状态 |
就绪状态 | 启动调度器后, 任务属于此种状态 |
挂起(阻塞) | 任务因为等待某种资源而阻塞 |
运行 | 运行 |
关闭 | 线程退出 |
- 初始状态
当线程刚开始创建还没开始运行时就处于初始状态:
使用rt_thread_init()创建,但是未调用rt_thread_startup使它就绪
使用rt_thread_create()创建,但是未调用rt_thread_startup使它就绪
- 就绪状态
这个线程完全准备好了,随时可以运行:只是还轮不到它:这时它就处于就绪态(Ready)。
在下面几种情况下,线程都处于就绪状态:
我们创建线程后,使用rt_thread_startup()函数使它进入就绪态。
它在运行过程中,被更高优先级的线程抢占了,这时它处于就绪状态。
它在运行过程中,轮到同优先级的线程运行了,这时它处于就绪状态。
它因为等待某些资源而没有运行,别的线程或者中断函数把它唤醒了,这时它处于就绪状态。
- 运行状态
当处于就绪状态的线程运行时,它就处于运行状态。
- 挂起状态
在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于挂起状态。
重点在于:母亲在等待。
在RTOS中创建多个任务后, 如果别的这些任务中优先级最高的任务(线程)在循环执行过程中不调用休眠相关的函数让出CPU的资源, 那么比它优先级低的任务根本呢不会执行。
在实际产品中,我们不会让一个线程一直运行,而是使用"事件驱动"的方法让它运行:
线程要等待某个事件,事件发生后它才能运行
在等待事件过程中,它不消耗CPU资源
在等待事件的过程中,这个线程就处于挂起状态, 就是阻塞状态
在挂起状态的线程,它可以等待两种类型的事件:
时间相关的事件
可以等待一段时间:我等2分钟
也可以一直等待,直到某个绝对时间:我等到下午3点
同步事件:这事件由别的线程,或者是中断程序产生
例子1:线程A等待线程B给它发送数据
例子2:线程A等待用户按下按键
同步事件的来源有很多(这些概念在后面会细讲):
信号量(semaphores)
互斥量(mutexe)
事件集(event)
在等待一个同步事件时,可以加上超时时间。比如等待队里数据,超时时间设为10ms:
10ms之内有数据到来:成功返回
10ms到了,还是没有数据:超时返回
- 关闭状态
当线程运行结束时,将处于关闭状态:
可由运行状态正常退出,进入关闭状态
或者通过线程删除函数进入关闭状态
rt_err_t rt_thread_detach(),用来删除使用rt_thread_init()创建的线程
rt_err_t rt_thread_delete(),用来删除使用rt_thread_create()创建的线程
在进入关闭状态时,线程所占据的资源(比如栈)不会立即释放,需等到空闲进程运行时才能清理。在这两个删除任务的函数内部实现是将任务控制块从不同的状态链表中进行搬移, 实际在回收任务控制块和任务栈的任务是在操作系统的空闲任务中做的。
3, 学习资源推荐
FreeRTOS
RT-Thread
没有系统学习过RTOS的兄弟建议去学第一个, 正点原子推出的FreeRTOS教程, 讲得很仔细, 很底层。
4, 启动流程分析开始
追一下代码: 这里简单补充一下一般mcu的启动流程, 一般在固化程序运行完成之后就到了启动代码, 不太明白为什么mcu启动的第一个处理的函数(符号)是复位处理的可以去看我之前写的这篇文章: http://t.csdnimg.cn/DUkKK
Reset_Handler:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] @导出Reset_Handler符号, 别的源文件可见
IMPORT __main @导入编译器生成的__main引导函数
IMPORT SystemInit @导入SystemInit函数符号
LDR R0, =SystemInit @初始化时钟等
BLX R0
LDR R0, =__main
BX R0
ENDP
mcu上电之后执行完固化的引导程序之后, 会根据拨码开关的指示来存储器中取出指令执行, 执行的指令的开始是启动代码, 启动代码中的第一个函数符号是复位处理, 在复位处理中调用到了SystemInit函数, 接下来到了SystemInit函数了。
SystemInit:
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= 0x00000001U;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#if !defined(STM32F105xC) && !defined(STM32F107xC)
RCC->CFGR &= 0xF8FF0000U;
#else
RCC->CFGR &= 0xF0FF0000U;
#endif /* STM32F105xC */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= 0xFEF6FFFFU;
/* Reset HSEBYP bit */
RCC->CR &= 0xFFFBFFFFU;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= 0xFF80FFFFU;
#if defined(STM32F105xC) || defined(STM32F107xC)
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= 0xEBFFFFFFU;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000U;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000U;
#elif defined(STM32F100xB) || defined(STM32F100xE)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000U;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000U;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000U;
#endif /* STM32F105xC */
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
建议各位先看一些大型工程代码的时候使用SourceInsight。总结一下Systeminit函数做了啥, 初始化Flash, 系统时钟。
RTOS的启动, 需要在特定的编译环境下才能成功, 举个例子
在绝大多数的编程语言中, 数字、字母、下划线是构成字符串的要求, 这种写法防到标准C语言语法中肯定就报错了, 只是在嵌入式C语言中, 可以根据不同的平台, 不同的应用场景将编译器修改配套, 这样就不会报错了。
在复位处理中, 当SystemInit执行完之后, 会接着调用编译器提供的__main引导函数, 找到函数的main函数入口, 从而去执行用户代码。在这里有一点特殊的是, __main引导找到的函数是 $Sub$$main, 所有接下来我们该去分析一下 $Sub$$main做了什么了。
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initialization
* NOTE: please initialize heap inside board 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();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
可以看到在这个函数中还是做了蛮多事情的, 但是主要的就两个函数。
看一下这两个源文件的实现
可以看到比较核心的是创建了一个主线程, 要不先去看看这个主线程干了啥?
可以看到, 在初始化一些系统组件之后, 调用到了我们的main函数。
具体任务切换是如何切的就要看cpu架构了, 本质通过一些链表, 定时器, 找到需要运行的任务, 将正在运行的任务和需要运行的任务进行一次上下文切换。
到此, RT-Thread启动流程就大致的分析完成了。