RT_threadの自动初始化机制

19 篇文章 4 订阅
10 篇文章 10 订阅

前言

  在刚开始学习单片机嵌入式时,我们初始化一些外设资源都是直接在while(1)前面调用外设的初始化函数,这样写虽然可以清楚的看到系统中用到了哪些外设,但是如果外设很多的话,一连续的初始化函数看起来就有点不舒服。而在RT_thread中存在自动初始化机制,它的原理就是用一个函数表,将要初始化的函数指针加入该表中,遍历该表执行每一个初始化函数。本文记录学习RT_thread的自动初始化机制。

1. 程序的内存分布

开发环境编译程序工程完毕之后,会有相应的程序占用空间提示信息,比如KEIL开发环境下编译结果:

linking...
Program Size: Code=58210 RO-data=53378 RW-data=1304 ZI-data=5904  
After Build - User command #1: fromelf --bin .\build\rtthread-stm32l4xx.axf --output rt-thread.bin
".\build\rtthread-stm32l4xx.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed:  00:00:09

从上面这段提示中,我们可以知道程序的大小被划分为:
  1)Code: 代码段,存放程序的代码部分(具备逻辑运算)
  2)RO_data: 只读数据段,存放程序中定义的常量数据
  3)RW_data: 读写数据段,存放初始化为非0值的全局变量
  4)ZI_data: 0数据段,存放未初始化的全局变量及未初始化为0的变量
  除了看编译后的提示信息,还可以查看.map文件最后几行,例如:

==============================================================================


      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   

     58210      11630      53378       1304       5904    1283970   Grand Totals
     58210      11630      53378        228       5904    1283970   ELF Image Totals (compressed)
     58210      11630      53378        228          0          0   ROM Totals

==============================================================================

    Total RO  Size (Code + RO Data)               111588 ( 108.97kB)
    Total RW  Size (RW Data + ZI Data)              7208 (   7.04kB)
    Total ROM Size (Code + RO Data + RW Data)     111816 ( 109.20kB)

==============================================================================

1)RO Size 包含了Code及RO_data,表示程序占用Flash空间大小
2)RW_Size: 包含了RW_data及ZI_data,表示运行时占用的Ram大小
3)ROM Size:包含了Code、RO Data、RW Data,表示程序占用的FLASH空间大小
  编译完了之后就可以将编译结果(bin/Hex文件)烧录到单片机中的FLASH中,bin/Hex文件包含RO段和RW段,其中RO段保存了Code、RO data的数据,RW段保存了RW data的数据,由于ZI data都是0,所以并没有包含在文件里面。
下图是MCU的内存分布:
在这里插入图片描述
  简单了解了MCU程序的内存分布,接下来就来看看RT_thread的自动初始化机制,它用到的有上面提到RO data段。

2.自动初始化机制

2.1 自动初始化原理解析

  自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。
  在RT_thread的系统启动流程中,可以看到这么一个函数调用:

#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

  该函数的原型如下:

typedef int (*init_fn_t)(void);
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
	/**/
#else
    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_board_init定义一个指向const init_fnt类型的函数指针,遍历INIT_BOARD_EXPORT(fn) 申明的函数表,执行函数表中的函数。这个rt_components_board_init函数执行的比较早,在RT_thread未启动调度器的时候调用,完成板级硬件驱动的初始化。在RT_thread开启调度器之前Rt_thread创建了一个main线程,里面又使用到了函数rt_components_init(),这个函数会遍历通过剩下的其他几个宏申明的初始化函数表。函数原型如下:

void rt_components_init(void)
{
#if RT_DEBUG_INIT
  	/**/
#else
    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
}

  上文提到的INIT_BOARD_EXPORT(fn) 是一个宏,先看它的表现形式:

#define SECTION(x)                  __attribute__((section(x)))
#define INIT_EXPORT(fn, level)                                                       \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

  这里我们看到了__attribute__((section(x))),这个是什么意思,怎么理解呢?其实这个是ARM开发工具的一个语法, 其作用是将函数或者变量放到指定的段里面,关于它的用法介绍可以查看KEIL的帮助,点击KEIL软件菜单栏上的help搜索关键词可以查找到:
在这里插入图片描述
  仔细看上图对__attribute__(section(name))的介绍和使用,第二处画红线标出的是它的应用例子:“int variable __ attribute __((section(“foo”))) = 10;”。我们试着将上面的宏INIT_BOARD_EXPORT(fn) 展开,得到

RT_USED const init_fn_t __rt_init_fn SECTION(".rti_fn.1") = fn
  这样看起来就比较清楚了,定义了一个const init_fn_t的变量(函数),该变量存在名为“.rti_fn.1”的输入段中,值等于fn。细心的可以留意map文件中,某一section在内存中是连续的,比如定义了一个名为“.zhengJb”的段,这个段我们存了几条信息,这些信息在内存中是连续的,类比数组。那么我们将上面的函数**rt_components_init**和函数**rt_components_board_init**的函数表遍历简单看成数组的遍历,是不是就清楚很多了, &__rt_init_rti_board_end 就相当于数组首个元素的地址。

  到此,就明白了rt_thread的自动初始化机制是怎么样子的。Rt_thread对自动初始化的函数做了分类,总共有6类,如下表:

初始化顺序宏接口描述
1INIT_BOARD_EXPORT(fn)非常早期的初始化
2INIT_PREV_EXPORT(fn)主要是用于纯软件的初始化、没有太多的依赖函数
3INIT_DEVICE_EXPORT(fn)外设驱动初始化相关,比如网卡设备
4INIT_COMPONENT_EXPORT(fn)组件初始化,比如文件系统,LWIP等等
5INIT_ENV_EXPORT(fn)系统环境初始化,比如挂载文件系统
6INIT_APP_EXPORT(fn)应用初始化,比如线程初始化应用等等

  关于其他5类,实现的方法跟INIT_BOARD_EXPORT(fn) 一样,这里不做记录。

2.2 实践测试自动初始化

  将ADC的初始化加入到板级初始化输入段中:

static int stm32_adc_init(void)
{
	/**/
}
//RT_USED const init_fn_t __rt_init_stm32_adc_init __attribute__((section(".zhengjinbo.""1"))) = stm32_adc_init
INIT_BOARD_EXPORT(stm32_adc_init);

编译完了以后可以在.map文件中找到:
在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值