RT-Thread(一)--系统启动过程

        今天5月1号,趁着四天假期,我选择好好提升自己,打算这四天学习一个物联网操作系统,物联网时代的到来,mcu上的物联网操作系统,就变得尤为重要,上周在ST峰会上看到了阿里的著作,被深深的吸引住了。立下flag,一:好好学习英语 二:学习两门的物联网联网技术:蓝牙mesh和lora组网  三:学习一门物联网操作系统 四:接触物联网云平台搭建 。本来是想学习阿里的AliOS Things操作系统的,但是刚出的系统,资料很是少, 从而学习了开源的RT—Thread。不说废话,我们直接来看看系统的运行过程。

      RT—Thread提供了丰富的资料,我觉得官网推出的资料就是最好的,因为没有人比开发自己的开发人员更加熟悉自己的操作系统。官方文档地址:https://www.rt-thread.org/document/site/tutorial/

      这个文档已经说了很明白了,我再这只是做一个补充说明,加深我的记忆。

      刚入门首先来用mdk的模式器来仿真一下系统的工作流程吧,毕竟还没把代码移植到板子上。首先得获得官方的一个最简单的例程, 在上面的网址上有,需要的请自己去下载,我们来介绍一下系统的文件结构

各个目录所包含的文件类型的描述如下表所示:

目录名描述
applicationsRT-Thread 应用程序。
rt-threadRT-Thread 的源文件。
- componentsRT-Thread 的各个组件目录。
- includeRT-Thread 内核的头文件。
- libcpu各类芯片的移植代码,此处包含了 STM32 的移植文件。
- srcRT-Thread 内核的源文件。
- toolsRT-Thread 命令构建工具的脚本文件。
driversRT-Thread 的驱动,不同平台的底层驱动具体实现。
LibrariesST 的 STM32 固件库文件。
kernel-sample-0.1.0RT-Thread 的内核例程。

在目录下,有一个 project.uvprojx 文件,它是本文内容所引述的例程中的一个 MDK5 工程文件,双击 “project.uvprojx” 图标,打开此工程文件:

 

在工程主窗口的左侧 “Project” 栏里可以看到该工程的文件列表,这些文件被分别存放到如下几个组内,分别是:

目录组描述
Applications对应的目录为 rtthread_simulator_v0.1.0/applications,它用于存放用户应用代码。
Drivers对应的目录为 rtthread_simulator_v0.1.0/drivers,它用于存放 RT-Thread 底层的驱动代码。
STM32_HAL对应的目录为 rtthread_simulator_v0.1.0/Libraries/CMSIS/Device/ST/STM32F1xx,它用于存放 STM32 的固件库文件。
kernel-sample对应的目录为 rtthread_simulator_v0.1.0/kernel-sample-0.1.0,它用于存放 RT-Thread 的内核例程。
Kernel对应的目录为 rtthread_simulator_v0.1.0/src,它用于存放 RT-Thread 内核核心代码。
CORTEX-M3对应的目录为 rtthread_simulator_v0.1.0/rt-thread/libcpu,它用于存放 ARM Cortex-M3 移植代码。
DeviceDrivers对应的目录为 rtthread_simulator_v0.1.0/rt-thread/components/drivers,它用于存放 RT-Thread 驱动框架源码。
finsh对应的目录为 rtthread_simulator_v0.1.0/rt-thread/components/finsh,它用于存放 RT-Thread 命令行 finsh 命令行组件。

 现在我们点击一下窗口上方工具栏中的按钮img,对该工程进行编译,如图所示:

编译的结果显示在窗口下方的 “Build” 栏中,没什么意外的话,最后一行会显示“0 Error(s), * Warning(s).”,即无任何错误和警告。

在编译完 RT-Thread/STM32 后,我们可以通过 MDK-ARM 的模拟器来仿真运行 RT-Thread。点击窗口右上方的按钮img或直接按 “Ctrl+F5” 进入仿真界面,再按 F5 开始运行,然后点击该图工具栏中的按钮或者选择菜单栏中的 “View→Serial Windows→UART#1”,打开串口 1 窗口,可以看到串口的输出只显示了 RT-Thread 的 LOGO,这是因为用户代码是空的,其模拟运行的结果如图所示:

 我们可以通过输入Tab键或者 help + 回车 输出当前系统所支持的所有命令,如下图所示。

系统启动代码

一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。以 MDK-ARM 为例,MDK-ARM 的用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统功能初始化,最后进入用户程序入口 main()。

下面我们来看看在 components.c 中定义的这段代码:

//components.c 中定义
/* re-define main function */
int $Sub$$main(void)
{
    rt_hw_interrupt_disable();
    rtthread_startup();
    return 0;
}

 在这里 $Sub$$main 函数仅仅调用了 rtthread_startup() 函数。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一入口点,所以 $Sub$$main 函数只需调用 rtthread_startup() 函数即可。例如采用 GNU GCC 编译器编译的 RT-Thread,就是直接从汇编启动代码部分跳转到 rtthread_startup() 函数中,并开始第一个 C 代码的执行的。在 components.c 的代码中找到 rtthread_startup() 函数,我们将可以看到 RT-Thread 的启动流程:

int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    /* board level initalization
     * 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;
}

这部分启动代码,大致可以分为四个部分

1. 初始化与系统相关的硬件;

2. 初始化系统内核对象,例如定时器,调度器;

3. 初始化系统设备,这个主要是为 RT-Thread 的设备框架做的初始化;

4. 初始化各个应用线程,并启动调度器。

在这里我要做几点说明, 因为官方文档在这里讲解的比较少,比较难理解:

1. 首先来说说$Sub$$main和$Super$$main 函数的用法

这是一种特殊模式:用于有一个已经存在且不能被改变的函数的情况(比如不能更改的库函数);使用这两个模式可以帮原函数打补丁,如存在一个函数foo();

$Sub$ $foo :定义的新功能函数,在foo()函数之前/后使用$Sub$ $foo 可以添加一些新的程序代码。

$Super$ $foo :就是原始的未修补的foo函数,使用这个$Super$ $foo函数将直接跳转到foo()函数。

下面以stm32f10xxx的int main(void)函数为例:

(1)  上电后,运行启动代码startup_stm32f10xxx.s

(2)  从系统初始化(SystemInit)开始执行,将函数地址赋给R0寄存器,跳转到R0地址执行并返回此处。

(3)  将__main函数地址给R0,将函数地址赋给R0,跳转到R0地址执行,不返回。

 

(4)  跳转到$Sub$$main(自己定义该函数)。

#if defined (__CC_ARM)
     extern int $Super$$main(void);
  /* re-define main function */
  int $Sub$$main(void)
  {
    preWork() ;      // do somthing before call main

       $Super$$main();      //  跳转到 main() 
    return 0;
  }

#endif

讲到这里我们来看看代码中关于这个得用法:

首先在components.c文件中

这里表示,这个函数在main函数之前运行,那么这个函数首先关闭了中断,然后再是rttread系统的启动函数。在该文件下还有

 

$Super$$main()函数表示将跳转到main函数运行。系统之所以这样的做法是,把用户的main函数和系统的main函数分开,系统的main函数比用户的main函数更前执行,这样就可以避免接触底层,注重应用层开发。

2.接着我们详细来介绍一下系统的运行过程:

(1)我们就不分析汇编代码了,直接从c文件入手,首先进入的是components.c文件中的int $Sub$$main(void)函数,该函数首先运行rt_hw_interrupt_disable()函数, 该函数失能中断

(2)接着运行rtthread_startup()函数,该函数对系统做了初始化,首先rt_hw_interrupt_disable()对失能了硬件中断,为啥要失能两次?? 有待思考, 接着rt_hw_board_init() 板级的初始化,rt_show_version()函数打印了系统的版本号 rt_system_timer_init()函数初始化了系统的定时器。rt_system_scheduler_init()初始化系统调度器。rt_system_signal_init()初始化信号量 ,rt_application_init() 函数是应用初始化,我们具体来看看这个函数

(3)rt_application_init(): 首先定义了一个rt的事件结构体变量,然后调用rt_thread_create创建了一个指向main_thread_entry函数的事件,最后启动了这个事件

(4)我们接着看看main_thread_entry函数,这个函数首先初始化了components, 然后调用了main函数,这里应该不能马上调用,因为系统还没有跑起来,只有等系统调用了,才能运行,分析到这我们就不分析了,接着就分析rt_thread_idle_init函数了, 这是初始化idle,至于这个是啥  我暂时也不知道, 然后再rt_system_scheduler_start 系统调度开始了,上面我们创建了一个main函数的事件, 这就可以运行了

 

用户入口代码

上面的启动代码基本上可以说都是和 RT-Thread 系统相关的,那么用户如何加入自己的应用程序的初始化代码呢?RT-Thread 将 main 函数作为了用户代码入口,只需要在 main 函数里添加自己的代码即可。

int main(void)
{
  /* user app entry */
  return 0;
}

提示

注:为了在进入 main 程序之前,完成系统功能初始化,可以使用 $sub$$ 和 $super$$ 函数标识符在进入主程序之前调用另外一个例程,这样可以让用户不用去管 main() 之前的系统初始化操作。详见 ARM® Compiler v5.06 for µVision® armlink User Guide

我们到此第一节就介绍完了,我们接着学习第二节

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零涂

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值