RT-Thread启动流程

芯片启动到main函数之前的运行过程

不论是否有RTOS,芯片的启动过程是一致的,均是要从复位向量处取得上电复位后要执行的第一个语句,接下来进行系统时钟初始化等工作,随后跳转到main处。

寻找第一条被执行的指令的存放处

源程序生成机器码的基本过程‘

要将C语言源程序编程可以下载到MCU中运行的机器码,需要经过预编译、编译、汇编、链接等过程,这一切都是开发环境IDE自动完成的。

预编译 是将源文件和头文件进行预处理,预处理过程中主要处理那些源代码文件中以“
# ” 开始的预编译指令,比如将所有的宏定义(#define )展开,处理所有条件预编译指令(如 #if
#ifdef #elif #else 等),处理所有包含指令(即 #include 指令,将被包含的文件插入到该语
句的位置,该过程是递归执行的,因为一个文件可能又包含其他文件。)等等。预编译生成.i 文件。
编译 是将高级语言(此处为 C 语言)翻译成汇编语言的过程,编译生成汇编语言文件
(.s 为后缀)。汇编是将汇编代码转为机器可以直接执行的机器码。每条汇编指令基本都对应于一条或 多条机器指令,根据汇编指令和机器指令的对照表翻译完成,汇编生成目标代码文件(
.o 为 后缀)。但它们中的有关存储器的地址是相对的,绝对地址没有确定,需要参考链接文件(.ld ) 才能各个.o 文件“链接”在一起,实际地址确定下来,才能形成完整的可执行文件。
链接 是将生成的目标文件( .o )和静态链接库( .a )等,在链接文件( .ld )的指引下,
生成机器码文件(.hex 及 .elf 等)

 深入理解启动过程

总流程

启动过程大致如下:

1、板级硬件初始化:SysTick定时器配置、堆内存初始化等

2、定时器初始化

3、调度器初始化

4、设置并创建线程

5、设置并创建空闲线程

6、启动调度器

 在目前的RTOS中,主要有两种主流的启动方式

万事俱备,只欠东风

这种方法是在main函数中将硬件初始化、RTOS系统初始化,所有线程的创建这些都弄好,这个简称为万事俱备,只欠东风。最后只欠一道东风,即启动RTOS的调度器,开始多线程的调度,具体的伪代码实现见下面

int main(void)
{
    /*硬件初始化*/
    HardWare_Init();            (1)
    /*RTOS初始化*/
    RTOS_Init();                 (2)
    //创建线程1,但线程1不会运行,因为调度器还没有开启    (3)
    RTOS_ThreadCreate(Task1);
    //创建线程2,但线程2不会运行,因为调度器还没有开启
    RTOS_ThreadCreate(Task2);
    ...
    //继续创建各种线程
    //启动RTOS,开始调度        (4)
    RTOS_Start(); 
}

void Thread1(void *parameter        (5)
{
    while(1)
    {
        //线程实体,必须有阻塞的情况出现

    }

}

void Thread2(void *parameter)            (6)
{
    while(1)
    {
        //线程实体,必须有阻塞的情况出现
    }

}

1)硬件初始化

2)RTOS系统初始化

3)创建各种线程

4)启动RTOS调度器

5)6)线程实体通常都是一个不带返回值的无限循环的C函数

小心翼翼,十分谨慎

第二种我称之为小心翼翼,十分谨慎法,这种方法是在main函数中将硬件和RTOS系统初始化好,然后创建一个启动线程后就启动调度器,然后再启动线程里面在创建各种应用线程,当所有的线程都创建好之后,启动线程把自己删除,具体的伪代码实现如下

int main(void)
{
    //硬件初始化
    HardWare_Init();            (1)

    //RTOS系统初始化
    RTOS_Init()        (2)

    //创建一个线程
    RTOS_ThreadCreate(AppThreadStart);        (3)

    //启动RTOS,开始调度
    RTOS_Start();        (4)

}

void AppThreadCreate(void *arg)        (5)
{
    //创建线程1,然后执行
    RTOS_ThreadCreate(Task1);

    //创建线程2,然后执行
    RTOS_ThreadCreate(Task2)        (6)

    ...
    //创建各种线程
    //当创建完各种线程,关闭起始线程
    RTOSThread_Close(AppThreadStart);    (7)

}

void Thread1(void *parameter        (8)
{
    while(1)
    {
        //线程实体,必须有阻塞的情况出现

    }

}

void Thread2(void *parameter)            (9)
{
    while(1)
    {
        //线程实体,必须有阻塞的情况出现
    }

}

RT-Thread和FreeRTOS默认使用第二种启动方式

当你拿到一个RT-Thread工程时,你会发现在main函数里只有创建线程和启动线程的代码,硬件初始化和系统初始化以及启动调度器等信息看不到,那是因为RT-Thread拓展了main函数,在main函数之前做好了这些工作。

我们知道,在系统上电复位第一个执行的是启动文件的由汇编编写的复位函数Reset_Handler,复位函数最后会调用__main,__main函数的主要工作就是初始化系统的堆和栈,最后调用C库函数,最终去到C的世界。

复位函数代码

 这里如果你进入仿真模式下,复位之后单步调试的话,会发现执行完汇编会跳到components.c中如下位置,而不是去到main函数,这是为什么?因为RT-Thread使用编译器(这里针对的是MDK),自带的$Sub$$和$Super$$这两个符号拓展了main函数。使用$Sub$$可以在执行main之前先执行$Sub$$main,在$Sub$$main中我们可以先执行一些预操作,当做完这些预操作之后,最终还是要执行main函数,这个就通过调用$Super$$main来实现。当需要扩展的函数不是main时,只需要将main换成你要拓展的函数名即可,即$Sub$$function和$Sub$$function。

 $Sub$$main函数

知道了 $Sub$$和 $Super$$的用法之后,我们回到文件中可以看到关闭中断,除了硬件中断FAULT和NMI可以响应外,其他统统关掉,该函数时在接口文件中由汇编实现的。

 rtthread_startup()函数

 rt_application_init()函数

 其中main线程的入口函数main_thread_entry如下

 $Super$$main()函数

初始线程入口。该函数除了调用 rt_components_init() 函数进行 RT-Thread 的组件初始化外,最终是调用 main 的扩展函数 $Super$$main() 回到 main 函数。 这个是必须的,因为我们一开始在进入 main 函数之前,通过 $Sub$$main() 函数扩展了 main 函数,做了一些硬件初始化,RTOS 系统初始化的工作,当这些工作做完之后最终还是要 回到 main 函数,那只能通过调用 $Super$$main() 函数来实现。 $Sub$$ $Super$$ MDK 自带的用来扩展函数的符号,通常是成对使用
初始线程优先级
初始线程优先级的优先级默认配置为最大优先级/3,控制最大优先级的宏RT_THREAD_PRIORITY_MAX在rt_config.h中定义,目前默认配置为32,那么初始线程main的优先级就是10,那么在初始线程里创建的各种应用线程的优先级又该如何配置?
分三种情况:
1 、应用线程的优先级比初始线程的优先级高,那创建完后立马去执行刚 刚创建的应用线程,当应用线程被阻塞时,继续回到初始线程被打断的地方继续往下执行, 直到所有应用线程创建完成,最后初始线程把自己关闭,完成自己的使命;
2 、应用线程的
优先级与初始线程的优先级一样,那创建完后根据线程的时间片来执行,直到所有应用线
程创建完成,最后初始线程把自己关闭,完成自己的使命;
3、应用线程的优先级比初始线
程的优先级低,那创建完后线程不会被执行,如果还有应用线程紧接着创建应用线程,如
果应用线程的优先级出现了比初始线程高或者相等的情况,请参考 1 2 的处理方式,直
到所有应用线程创建完成,最后初始线程把自己关闭,完成自己的使命。
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值