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

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

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

 1 #ifdef RT_USING_COMPONENTS_INIT
2 typedef int (*init_fn_t)(void);
3 #ifdef _MSC_VER /* we do not support MS VC++ compiler */
4 #define INIT_EXPORT(fn, level)
5 #else
6 #if RT_DEBUG_INIT
7 struct rt_init_desc
8 {
9 const char* fn_name;
10 const init_fn_t fn;
11 };
12 #define INIT_EXPORT(fn, level) \
13 const char __rti_##fn##_name[] = #fn; \
14 RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
15 { __rti_##fn##_name, fn};
16 #else
17 #define INIT_EXPORT(fn, level) \
18 RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
19 #endif
20 #endif
21 #else
22 #define INIT_EXPORT(fn, level)
23 #endif
针对上面代码,逐句分析下。

首先RT_USING_COMPONENTS_INIT宏需要在config.h中定义,否则自启动是无效的。

然后使用typedef定义了一个函数指针类型

这里补充一下关于typedef:

目前我知道的typedef有两种用法,其一是起别名,其二是定义新的类型,举个例程说明这两种用法
 1 //生产了新类型fun_p
2 typedef int (*fun_p)(void);
3 int app(void)
4 {
5 return 0;
6 }
7
8 typedef struct sTest
9 {
10 fun_p * app_p;
11 }Test_s;
12
13 Test_s test;
14 tset.app_p = app;

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

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


首先看看RT_USED这个宏,通用定义也在rtdeh.h中
 1 #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的数据段中去。
例如系统中有如下语句

 1 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中

一上来便定义了一些标杆,用来区间化之前准备往里塞的函数指针

 1 static int rti_start(void)
2 {
3 return 0;
4 }
5 INIT_EXPORT(rti_start, "0");
6
7 static int rti_board_start(void)
8 {
9 return 0;
10 }
11 INIT_EXPORT(rti_board_start, "0.end");
12
13 static int rti_board_end(void)
14 {
15 return 0;
16 }
17 INIT_EXPORT(rti_board_end, "1.end");
18
19 static int rti_end(void)
20 {
21 return 0;
22 }
23 INIT_EXPORT(rti_end, "6.end");

我们再看看map中的情况

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

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

按照系统执行顺序来分别看下自启动的两个函数:

首先是 rtthread_startup() →void rt_hw_board_init() →void rt_components_board_init(void)

1     const init_fn_t *fn_ptr;
2
3 for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
4 {
5 (*fn_ptr)();
6 }

其中__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();

1     const init_fn_t *fn_ptr;
2
3 for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
4 {
5 (*fn_ptr)();
6 }

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

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

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

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

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

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

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

我将之前的标杆修改为

 1 //测试用标志杆
2 static int rti_A(void)
3 {
4 return 0;
5 }
6 INIT_EXPORT(rti_A, "2");
7
8 //测试用标志杆
9 static int rti_a(void)
10 {
11 return 0;
12 }
13 INIT_EXPORT(rti_a, "2");
14
15 static int rti_1(void)
16 {
17 return 0;
18 }
19 INIT_EXPORT(rti_1, "2");

然后map结果是:

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

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

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

RTT笔记-分析自动初始化机制转的更多相关文章

  1. ArrayList-源码分析-自动扩容机制

    ArrayList类: public class ArrayList....{ ...... private static final int DEFAULT_CAPACITY = 10; //默认容 ...

  2. SOFA 源码分析 — 自动故障剔除

    前言 集群中通常一个服务有多个服务提供者.其中部分服务提供者可能由于网络,配置,长时间 fullgc ,线程池满,硬件故障等导致长连接还存活但是程序已经无法正常响应.单机故障剔除功能会将这部分异常的服 ...

  3. Java虚拟机学习笔记——JVM垃圾回收机制

    Java虚拟机学习笔记——JVM垃圾回收机制 Java垃圾回收基于虚拟机的自动内存管理机制,我们不需要为每一个对象进行释放内存,不容易发生内存泄漏和内存溢出问题. 但是自动内存管理机制不是万能药,我们 ...

  4. 【数组】- ArrayList自动扩容机制

    不同的JDK版本的扩容机制可能有差异 实验环境:JDK1.8 扩容机制: 当向ArrayList中添加元素的时候,ArrayList如果要满足新元素的存储超过ArrayList存储新元素前的存储能力, ...

  5. 部署自动初始化Schema的数据库

    我们使用容器的方式部署数据库组件,特别是企业有大量的项目开发业务的,部署的开发.测试数据库组件较多时.经常会遇到以下问题: 业务需要使用数据库,但部署完数据库后,需要在数据库中执行创建schema的操 ...

  6. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  7. .NET中那些所谓的新语法之一:自动属性、隐式类型、命名参数与自动初始化器

    开篇:在日常的.NET开发学习中,我们往往会接触到一些较新的语法,它们相对以前的老语法相比,做了很多的改进,简化了很多繁杂的代码格式,也大大减少了我们这些菜鸟码农的代码量.但是,在开心欢乐之余,我们也 ...

  8. Unity3.0基于约定的自动注册机制

    前文<Unity2.0容器自动注册机制>中,介绍了如何在 Unity 2.0 版本中使用 Auto Registration 自动注册机制.在 Unity 3.0 版本中(2013年),新 ...

  9. 《深入浅出WPF》重点摘要(—)Binding自动通知机制

    最近因为公司的项目需要用WPF开发,就学习了一下WPF.刚开始只是用到什么就百度什么,虽然功能是实现了,但还是没有弄清楚原理(如果不弄清原理,会感觉很心虚,整个人会没底气),所以决定找个教程系统地学一 ...

  10. 【Zookeeper】源码分析之Watcher机制(三)之Zookeeper

    一.前言 前面已经分析了Watcher机制中的大多数类,本篇对于ZKWatchManager的外部类Zookeeper进行分析. 二.Zookeeper源码分析 2.1 类的内部类 Zookeeper ...

随机推荐

  1. 在老项目中单独引入vue.js,使用自定义指令

    传统项目,做一个表格渲染,然而数据过多,传统方式费时费力,便引入vue和elelment  ui,由于表格需要渲染的数据过多一个表格上千条,导致切换tab更新表格时缓慢,体验太差,于是做了自定义指令, ...

  2. Windows10使用VMware安装centos

    系统环境: Windows 10 安装步骤: 1.下载centos http://mirrors.aliyun.com/centos/ 2.使用VMware安装centos 3.配置网络 $ cd / ...

  3. Redis Stack(生产适配-仅提供 Redis Stack 服务器) (6.2.4-v2版本)

    一.使用docker安装(生产适配-仅提供 Redis Stack 服务端) (6.2.4-v2版本) 简介 安装命令根据实际部署情况调整 版本地址:官方镜像主页 | 发布版Tags 指定版本命令-推 ...

  4. ChatGPT回答的关于maxscript + python

  5. SpringCloudBus实现配置文件动态更新

    前言 在SpringCloud之配置中心(config)的使用的基础上加上SpringCloudBus实现配置文件动态更新 在此之前需要修改版本,否则会出现"Endpoint ID 'bus ...

  6. iOS第三方库汇总(转)

    原文:http://mp.weixin.qq.com/s?__biz=MjM5OTM0MzIwMQ==&mid=2652551221&idx=1&sn=617f4d42bc52 ...

  7. SSB调制与解调(Simulink&Matlab)

    题目:基于Simulink的SSB信号调制与解调仿真 参考文章 一.实验目的与要求 目的:学习SSB信号的调制与解调仿真 要求: 具有MATLAB的仿真结果并附上代码 具有基于Simulink的模块的 ...

  8. 记录 springboot 整合swagger2 出现documentationPluginsBootstrapper&&NullPointerException异常

    记录 由于springboot版本高 整合swagger2出现null异常 springboot 版本: swagger2 依赖版本: 出现的异常: 解决方案: application 添加如下依赖 ...

  9. AcWing 841. 字符串哈希 2022/5/25

    include include using namespace std; typedef unsigned long long ULL; char str[N]; //存放字符串 int h[N], ...

  10. oracle 索引操作

    1 查询表中所有的索引 -- 固定写法"tb_user"(注意大小写)为表名 select * from user_indexes where table_name='tb_use ...