当前位置: 首页 > news >正文

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 的处理方式,直
到所有应用线程创建完成,最后初始线程把自己关闭,完成自己的使命。

相关文章:

  • char * 与 二维字符数组
  • Python 教程之控制流(4)Python 中的循环技术
  • 【历年IJCAI论文下载(含IJCAI2022)】图神经网络(GNN)(多行为推荐、多模态食谱表示学习、同质图表示学习)
  • UNIAPP实战项目笔记3 Swiper部分开发
  • Spring启动源码分析以及生命周期
  • 斜率优化dp
  • 前端开发常用网站整理
  • 直流有刷电机电流采集基于STM32F302R8+X-NUCLEO-IHM07M1
  • 27_GitGitHub
  • 微信公众号在线查题功能系统使用
  • WPS JS宏示例-批量添加链接
  • Java核心——面向对象编程(上)包-继承-多态
  • Ambari自动部署Hadoop集群实战
  • 33.0、C语言——C语言预处理(1) - 翻译环境详解
  • java-php-python-springboot网上订餐系统计算机毕业设计
  • (三)从jvm层面了解线程的启动和停止
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • create-react-app项目添加less配置
  • Go 语言编译器的 //go: 详解
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • Java超时控制的实现
  • Java方法详解
  • JAVA之继承和多态
  • Js基础——数据类型之Null和Undefined
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • Material Design
  • PHP的类修饰符与访问修饰符
  • React-flux杂记
  • Redis 中的布隆过滤器
  • spark本地环境的搭建到运行第一个spark程序
  • Terraform入门 - 1. 安装Terraform
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 翻译:Hystrix - How To Use
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 关于Flux,Vuex,Redux的思考
  • 关于Java中分层中遇到的一些问题
  • 讲清楚之javascript作用域
  • 前端js -- this指向总结。
  • 前端路由实现-history
  • 前端设计模式
  • 说说动画卡顿的解决方案
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 学习Vue.js的五个小例子
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 字符串匹配基础上
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • (Git) gitignore基础使用
  • (二开)Flink 修改源码拓展 SQL 语法
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (黑马C++)L06 重载与继承
  • (强烈推荐)移动端音视频从零到上手(上)
  • (十一)图像的罗伯特梯度锐化