RTT笔记-分析自动初始化机制_fnrtt-程序员宅基地

技术标签: 嵌入式/stm32  MDK  ARM  

原文:https://www.jianshu.com/p/9d377ddc8acc

首先全局搜索一个任意的自启动宏,便能找到在rtdef.h中由如下

定义

#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

关于宏INIT_EXPORT的定义就就在上方

#ifdef RT_USING_COMPONENTS_INIT 
typedef int (*init_fn_t)(void);
#ifdef _MSC_VER /* we do not support MS VC++ compiler */
    #define INIT_EXPORT(fn, level)
#else
    #if RT_DEBUG_INIT
        struct rt_init_desc
        {
            const char* fn_name;
            const init_fn_t fn;
        };
        #define INIT_EXPORT(fn, level)                                                       \
            const char __rti_##fn##_name[] = #fn;                                            \
            RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
            { __rti_##fn##_name, fn};
    #else
        #define INIT_EXPORT(fn, level)                                                       \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
    #endif
#endif
#else
#define INIT_EXPORT(fn, level)
#endif

针对上面代码,逐句分析下。
首先RT_USING_COMPONENTS_INIT宏需要在config.h中定义,否则自启动是无效的。
然后使用typedef定义了一个函数指针类型
这里补充一下关于typedef:
目前我知道的typedef有两种用法,其一是起别名,其二是定义新的类型,举个例程说明这两种用法

//生产了新类型fun_p
typedef int (*fun_p)(void);
int app(void)
{
    return 0;
}

typedef struct sTest
{
    fun_p * app_p;
}Test_s; 

Test_s test;
tset.app_p = app;

回到上文,由于#ifdef后的宏均是未定义,所以一路走else,那么就仅仅剩一句话

RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

首先看看RT_USED这个宏,通用定义也在rtdeh.h中

#define RT_USED                     __attribute__((used))
  • attribute((used))标识符作用是使定义被标记的函数或数据即使未使用也不会被编译器优化。
  • init_fn_t是一个函数指针类型
  • __rt_init_##fn是将__rt_init_和我们传入的需要自启动的函数名进行拼接
  • SECTION(".rti_fn."level)也就是 __attribute__((section( ".rti_fn."level ))),

__attribute __((section(“name”)))

该函数便是实现自动初始化的关键了,他的作用是将标记的数据或者函数在编译时放到name的数据段中去。
例如系统中有如下语句

components.c(60) : INIT_EXPORT(rti_start, "0");

在编译后生成的map文件中能够找到对应信息,名叫__rt_init_rti_start 的指针被保存在了.rti_fn.0字段中去

__rt_init_rti_start 0x0801e6b8 Data 4 components.o(.rti_fn.0)

综上那么完整语句的翻译便是: 定义了一个名为(_rt_init+需要自动启的函数名)的函数指针,将其保存在(.rti_fn.level)数据段中,并且及时不使用也不会被编译器优化。

到这里基本就能明白自启动的方式了。也就是逐个建立一个指针指向需要自启动的函数,然后将这些指针保存到特定的数据段中。main启动时候,只需要将数据段中的指针函数全部执行一遍即可。

接下来我们看执行初始化的地方,也就是在components.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");

我们再看看map中的情况

    __rt_init_rti_start                      0x0801e6dc   Data           4  components.o(.rti_fn.0)
    __rt_init_rti_board_start                0x0801e6e0   Data           4  components.o(.rti_fn.0.end)
    __rt_init_rt_hw_spi_init                 0x0801e6e4   Data           4  drv_spi.o(.rti_fn.1)
    __rt_init_rti_board_end                  0x0801e6e8   Data           4  components.o(.rti_fn.1.end)
    __rt_init_ulog_init                      0x0801e6ec   Data           4  ulog.o(.rti_fn.2)
    __rt_init_ulog_console_backend_init      0x0801e6f0   Data           4  console_be.o(.rti_fn.2)
    __rt_init_finsh_system_init              0x0801e6f4   Data           4  shell.o(.rti_fn.6)
    __rt_init_fal_init                       0x0801e6f8   Data           4  fal.o(.rti_fn.6)
    __rt_init_rti_end                        0x0801e6fc   Data           4  components.o(.rti_fn.6.end)
   

我们想自启动的函数__rt_init_rt_hw_spi_init、__rt_init_ulog_init 等都被包夹在了这些标识杆中间。至于他的排序问题,文末将用代码进行测试推论。

按照系统执行顺序来分别看下自启动的两个函数:
首先是 rtthread_startup() →void rt_hw_board_init() →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)();
    }

其中__rt_init_rti_board_start和__rt_init_rti_board_end便是上面的两个标志杆,是经过宏里面的##拼接后的结果,然后我们再看看上面的map,就发现这个for循环实际上是执行了被包夹的__rt_init_rti_board_start和__rt_init_rt_hw_spi_init,拆解一下就是函数rti_board_start和rt_hw_spi_init。

我们再看第二个自启动的函数
rtthread_startup() →rt_application_init()→void main_thread_entry(void *parameter)→rt_components_init();

    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }

这里和上面类似,只是标志杆变为了level26之间的函数了。也就是level02间的函数是一起执行的,level2~6间的函数是一起执行的。

接下来我们研究一下这个字段的排序问题
首先由已知,在.rti_fn.后面是以数由小到大排序。

image.png


那么尝试一下在后面添加字符,添加两个新的标志杆

image.png


字符排在了数字后面,然后再添加一个大写字母

image.png


A排序到了小写字母之前数字之后,也就是这个排序可能就是ascii码的排序了。

 

还有个问题就是同字段的两个函数指针的顺序如何呢,例如

    __rt_init_ulog_init                      0x0801e6f8   Data           4  ulog.o(.rti_fn.2)
    __rt_init_ulog_console_backend_init      0x0801e6fc   Data           4  console_be.o(.rti_fn.2)

我将之前的标杆修改为了

//测试用标志杆
static int rti_A(void)
{
   return 0;
}
INIT_EXPORT(rti_A, "2");

//测试用标志杆
static int rti_a(void)
{
   return 0;
}
INIT_EXPORT(rti_a, "2");

static int rti_1(void)
{
   return 0;
}
INIT_EXPORT(rti_1, "2");

然后map结果是:

    __rt_init_rti_start                      0x0801e6e8   Data           4  components.o(.rti_fn.0)
    __rt_init_rti_board_start                0x0801e6ec   Data           4  components.o(.rti_fn.0.end)
    __rt_init_rt_hw_spi_init                 0x0801e6f0   Data           4  drv_spi.o(.rti_fn.1)
    __rt_init_rti_board_end                  0x0801e6f4   Data           4  components.o(.rti_fn.1.end)
    __rt_init_rti_A                          0x0801e6f8   Data           4  components.o(.rti_fn.2)
    __rt_init_rti_a                          0x0801e6fc   Data           4  components.o(.rti_fn.2)
    __rt_init_rti_1                          0x0801e700   Data           4  components.o(.rti_fn.2)
    __rt_init_ulog_init                      0x0801e704   Data           4  ulog.o(.rti_fn.2)
    __rt_init_ulog_console_backend_init      0x0801e708   Data           4  console_be.o(.rti_fn.2)
    __rt_init_finsh_system_init              0x0801e70c   Data           4  shell.o(.rti_fn.6)
    __rt_init_fal_init                       0x0801e710   Data           4  fal.o(.rti_fn.6)
    __rt_init_rti_end                        0x0801e714   Data           4  components.o(.rti_fn.6.end)
  

可以看出排序和我代码顺序有关,也就是应该和编译顺序有关。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liming0931/article/details/109402342

智能推荐

jQuery调用php函数-程序员宅基地

文章浏览阅读1.7k次。jquery调用php一般是用AjAx方式来调用的。这里是列表文本首先确认需要调用的php文件,可以是绝对路径,也可以是相对路径这里输入引用文本这里以Jquery为例子解释上面的问题这里是列表文本如下图就是jquery的实现代码,下图的url就是需要请求的php地址; ..._jquery调用php函数

ImportError: DLL load failed while importing _pywrap_tensorflow_internal:-程序员宅基地

文章浏览阅读2.7k次。在anaconda里安装tensorflow的时候,出现了此问题。我的原因是python的版本和tensorflow的版本不兼容,我安装的是Python3.8,降级到Python3.7就好了

Android Toast基础与原理_android aidl中toast-程序员宅基地

文章浏览阅读2.2k次。一、Toast的使用方式Toast.makeText(context,text,duration)public Toast(Context context)在Android系统中,给我们提供了两种方式来创建一个Toast对象。第一种是通过makeText方法快速构建Toast对象。第二种是通过Toast的构造方法进行创造一个空的(不含View)的Toast对象。注意,通过构造方法创_android aidl中toast

LBS-程序员宅基地

文章浏览阅读1.5k次。Location Based ServiceLBS定位与GPS定位的区别https://blog.csdn.net/weixin_36397141/article/details/70170702微信、陌陌的架构方案分析https://blog.csdn.net/it_man/article/details/39640327Mysql Mongodb LBS快速实现方案https://...

[Leetcode daily] 303. 区域和检索 - 数组不可变_I-Hsien的博客-程序员宅基地

文章浏览阅读82次。Description给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。实现 NumArray 类:NumArray(int[] nums) 使用数组 nums 初始化对象int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], … , nums[j]))示例:输入:["NumArray",

(Android实战)ProgressBar+AsyncTask实现界面数据异步加载(含效果图)-程序员宅基地

文章浏览阅读1.3k次。1 效果图 加载数据时 加载数据完成时 加载数据异常时 2 实现说明 加载前:界面显示异步加载控件,隐藏数据显示控件,加载异常控件 加载成功:根据加载的数据,初始化数据显示控件 加载失败:显示加载异常的控

随便推点

LLVM(还没译完, 回头再译)-程序员宅基地

文章浏览阅读795次。This chapter discusses some of the design decisions that shaped LLVM1, an umbrella project that hosts and develops a set of close-knit low-level toolchain components (e.g., assemblers, compilers, de

【BugFix】Mysql执行SQL转义字符问题,后吞了反斜杠\_qsql exec 反斜杠消失-程序员宅基地

文章浏览阅读1.2k次。1.现象update regrex set regrex='[^(a-zA-Z0-9\u4e00-\u9fa5)]' where id=1执行完之后,对应的字符串中没有了反斜杠\执行后 反斜杠被mysql转义没了2.原因mysql执行会对内容进行转义,\是特殊对转义字符,转义后消失了,如需要保存反斜杠\ 需要对反斜杠进行转义3.解决方案转义字符特殊处理,再加一个反..._qsql exec 反斜杠消失

使用Redis SETNX 命令实现分布式锁-程序员宅基地

文章浏览阅读10w+次,点赞25次,收藏143次。使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其实现方法。SETNX命令命令格式 SETNX key value将 key 的值设为 value,当且仅当 key 不存在 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是SET if Not eXists的简写。返回值返回整数,具体为 - 1,当 key 的值被设置 - 0,当 key 的值没被设

Windows版本Wireshark中增加对snmp mib的解析-程序员宅基地

文章浏览阅读654次。注意事项只能用32bit的版本,64bit版本由于没有对应的libsmi,所以是不支持这个功能的.Wireshark设置正常安装wireshark版本, 2012-2-1本人采用的是稳定的1.6.5版本.安装完成后,打开"Edit"->"Preference"对话框, 点击左侧的"Name Resolution",然后将右侧的"Enable OID resol..._wireshark4 设置mib

启动Android模拟器后,在file Explorer中看不到任何文件-程序员宅基地

文章浏览阅读1.3k次。这里可能存在一个先后的问题:1.先要在Window/show/other中打开Android相关视图,如file Explorer2.然后在启动Android模拟器成功启动。这时会看到android相关视图中出现信息。如果仍然看不到信息。可尝试重启Eclipse,或者更新ADT插件。本人的问题,是重启Eclipse就好了。今天又出现了

手把手教你用Python进行时间序列分解和预测_python rolling mean-程序员宅基地

文章浏览阅读1.8k次。前言本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理。PS:如有需要Python学习资料的小伙伴可以点击下方链接自行获取Python免费学习资料、代码以及交流解答点击即可加入预测是一件复杂的事情,在这方面做得好的企业会在同行业中出类拔萃。时间序列预测的需求不仅存在于各类业务场景当中,而且通常需要对未来几年甚至几分钟之后的时间序列进行预测。如果你正要着手进行时间序列预测,那么本文将带你快速掌握一些必不可少的概念。目录什么是时间序列? _python rolling mean