zoukankan      html  css  js  c++  java
  • RTT学习之启动流程

    1. 总结RT-Thread的启动流程。
    2. 非运行时与运行时的image文件分别是什么样的,请画下来。是谁将 RW 段中的  RW-data(初始化的全局变量)搬运到 RAM 中?
    3. MDK环境下各种数据段存储的什么数据?
    4. 在RT-Thread启动时,关了中断,那么在什么时候开启的中断?
    5. 总结自动初始化原理。
    6. 总结BSP制作过程。

    1RT-Thread启动流程

     

     

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

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

    (2) 初始化系统内核对象,例如定时器、调度器、信号;

    (3) 创建 main 线程,在 main 线程中对各类模块依次进行初始化;

    (4) 初始化定时器线程、空闲线程,并启动调度器。

    启动流程中蓝色部分是自动初始化的数据段,使用自动初始化宏导出的函数放置到相应的数据段,在启动流程中对函   数进行遍历初始化。2、加载时地址与运行时地址映射

    image文件

     

     STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。

    分散装载配置文件里会有配置,关于code的地址,有两个设置,一个是存储地址(这个地址配置的是烧写器把代码  段写到flashrom的何处),一个是装载运行地址,也就是你程序在什么地方运行

    【如果想要深入了解的话,可以看看arm的连接器手册,或者是《ARM体系结构与编程》中也讲到了】

    3MDK环境下各种数据段存储的什么数据?

    code:代码段,存放程序

    RO:只读数据段,存放程序中定义的常量RW:读写数据段,存放非0全局变量

    ZI:0数据段,存放未初始化的全局变量与初始化为0的变量

    MDK 在编译完成之后

    Total RO Size (Code + RO Data) 53668 ( 52.41kB) Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)

    Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)

    1) RO Size 包含了 Code  RO-data,表示程序占用 Flash 空间的大小;

    2) RW Size 包含了 RW-data  ZI-data,表示运行时占用的 RAM 的大小;

    3) ROM Size 包含了 Code、RO Data 以及 RW Data,表示烧写程序所占用的 Flash 空间的大小;

    4、在RT-Thread启动时,关了中断,那么在什么时候开启的中断?

    在启动调度器,切换到第一个线程时开启的中断【直接使用CPSIE I 开了中断的】。代码详见:

    rt_system_scheduler_start() rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);

     5、总结自动初始化原理。

    RT-Thread 的自动初始化机制使用了自定义 RTI  符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。

    用来实现自动初始化功能的宏接口定义详细描述如下表所示:

    初始化顺序

    宏接口

    描述

    1

    INIT_BOARD_EXPORT(fn)

    非常早期的初始化,此时调度器还未启动

    2

    INIT_PREV_EXPORT(fn)

    主要是用于纯软件的初始化、没有太多依赖的函数

    3

    INIT_DEVICE_EXPORT(fn)

    外设驱动初始化相关,比如网卡设备

    4

    INIT_COMPONENT_EXPORT(fn)

    组件初始化,比如文件系统或者 LWIP

    5

    INIT_ENV_EXPORT(fn)

    系统环境初始化,比如挂载文件系统

    6

    INIT_APP_EXPORT(fn)

    应用初始化,比如 GUI 应用

    初始化函数主动通过这些宏接口进行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到 RTI 符号段中,该符号段位于内存分布的 RO 段中,该 RTI 符号段中的所有函数在系统初始化时会被自动调用。

    原理:

    在rtdef.h中,使用SECTION(x)定义: 

    #define SECTION(x)  attribute ((section(x)))

         attribute ((section("name"))):将作用的函数或数据放入指定名为"name"的输入段中。(在不同的编译器中实现的方式也有所不同。)

    将SECTION(".rti_fn."level)使用 INIT_EXPORT(fn, level) 这个宏进行定义,fn是函数  

    #define INIT_EXPORT(fn, level)  RT_USED const init_fn_t rt_init_##fn SECTION(".rti_fn."level) = fn

    分段:

     compnents.c中:

    static int rti_start(void)
    {
    return 0;
    }
    INIT_EXPORT(rti_start, "0");
    
    
    static int rti_board_start(void)
    {
    return 0;
    }
    INIT_EXPORT(rti_board_start, "0.end");
    
    
    static int rti_board_end(void)
    {
    return 0;
    }
    INIT_EXPORT(rti_board_end, "1.end");
    
    
    static int rti_end(void)
    {
    return 0;
    }
    INIT_EXPORT(rti_end, "6.end");

    所以就有: 

    段名

    函数指针/

    .rti_fn.0

        rt_init_rti_start

    .rti_fn.0.end

        rt_init_rti_board_start

    .rti_fn.1

    INIT_BOARD_EXPORT(fn)

    .rti_fn.1.end

        rt_init_rti_board_end

    .rti_fn.2

    INIT_PREV_EXPORT(fn)

    .rti_fn.3

    INIT_DEVICE_EXPORT(fn)

    .rti_fn.4

    INIT_COMPONENT_EXPORT(fn)

    .rti_fn.5

    INIT_ENV_EXPORT(fn)

    .rti_fn.6

    INIT_APP_EXPORT(fn)

    .rti_fn.6.end

        rt_init_rti_end

    非调试模式下rt_components_board_init():for循环会遍历位于 rt_init_rti_board_start 到

         rt_init_rti_board_end 之间保存的函数指针,然后依次执行这些函数

    void rt_components_board_init(void)
    {
    const init_fn_t *fn_ptr;
    
    
    for (fn_ptr = & rt_init_rti_board_start; fn_ptr < & rt_init_rti_board_end; fn_ptr++)
    {
    (*fn_ptr)();
    }
    #endif
    }

    非调试模式下rt_components_init():for循环会遍历位于  rt_init_rti_board_end 到  rt_init_rti_end 之间保存的函数指针,然后依次执行这些函数

    void rt_components_init(void)
    {
    const init_fn_t *fn_ptr;
    
    
    for (fn_ptr = & rt_init_rti_board_end; fn_ptr < & rt_init_rti_end; fn_ptr ++)
    {
    (*fn_ptr)();
    }
    #endif
    }

    举例:

    main函数中添加了函数pin_beep_sample(),并使用INIT_APP_EXPORT()进行自动初始化。  

    INIT_APP_EXPORT(pin_beep_sample);

    那么,展开为:

    INIT_APP_EXPORT(pin_beep_sample) INIT_EXPORT(pin_beep_sample, "6")

    也就是  

    const init_fn_t rt_init_pin_beep_sample SECTION(".rti_fn.""6") = pin_beep_sample

    表示把函数pin_beep_sample的地址赋值给常量函数指针 rt_init_pin_beep_sample,然后放入名称为".rti_fn.6"的数据段中。(其中init_fn_t是一个函数指针类型,原型为typedef int (*init_fn_t)(void) 。)

    在编译后的.map文件中可以查看到:

    Symbol Name Value Ov Type Size Object(Section)

      

  • 相关阅读:
    k8s采坑记
    [dotnet] 封装一个同时支持密码/安全密钥认证的SFTP下载器,简单易用。
    亲测可用,iptables实现NAT转发。
    【转】干货,Kubernetes中的Source Ip机制。
    k8s实践
    干货!分享一款windows下的磁盘分析神器。
    干货,不小心执行了rm -f,除了跑路,如何恢复?
    Java8函数式编程
    搭建git服务器
    Python3安装
  • 原文地址:https://www.cnblogs.com/jieruishu/p/11806757.html
Copyright © 2011-2022 走看看