首先全局搜索一个任意的自启动宏,便能找到在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. UISelector

    1.UiSelector的基本方法 UiSelector对象可以理解为一种条件对象,描述的是一种条件,可以配合UiObject使用得到某个符合条件的控件对象. 所有的方法都是public的,且都返回U ...

  2. 记录一次Python环境安装出现的问题(已安装java)

    之前是在其他电脑上安装python环境,没有问题. 换了一台电脑开始安装(注:已配置java环境) 安装包准备好 ( 这里使用的是python 3.6.5(64位) ,下载地址选择官网) 教程百度都有 ...

  3. Fast Report 分栏分页

    Layout 设置布局 AcrossThenDown是水平分栏  DownThenAcross是垂直分栏

  4. URLSearchParams(鲜为人知处理URL地址的技能)

    最近学习中无意发现url新处理方式,看到之后十分感兴趣就整理了一下. URLSearchParams URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串.参照 URL ...

  5. Java Development Kit下载地址

    Java Development Kit下载地址 官网下载 一般最新版本无需登录即可下载,其他历史版本需要登录Oracle账户才可以下载. 最新版下载地址: https://www.oracle.co ...

  6. docker 部署minio

    1 docker pull minio/minio:RELEASE.2022-08-26T19-53-15Z 2 docker run -p 9000:9000 -p 9090:9090  --nam ...

  7. docker指令 —— MySQL一条龙服务

    一.拉取MySQL镜像 简单粗暴拉取: docker pull mysql 拉取合适的版本,docker mysql Tag: # 例如拉取8.0.25 docker pull mysql:8.0.2 ...

  8. angular 路由守卫Observable异步请求串联

    假设路由守卫有这种场景 需要使用observable同时发送多个Http 请求,判断request2返回的数据中是否存在request1返回的数据 使用async await export class ...

  9. 解决GitHub下载速度慢下载失败的问题

    最近在GitHub上拉取代码时,每次git clone都是文件下载完了发现连接就断掉了,或者下载压缩包显示网络连接错误的情况.下面介绍找到的解决方法: 1.打开码云(当然不是福报)https://gi ...

  10. Java中类似c语言的printf

    System.out.printf("%4d",x); printf("%4d",x); 保留小数点后两位也可以用%.2f 相对来说很好记了 回车用\n