RT-Thread系列--组件初始化

news/2025/3/15 0:58:39/

一、目的

RT-Thread里面有个特别有意思的软件设计叫做组件自动初始化。

有些小伙伴可能是第一次听说,所以这边我解释一下,请看下面的代码片段

static void clock_init() {// 时钟初始化
}
static void uart_init() {// 串口初始化
}
static void i2c_init() {// I2C初始化
}
int main() {clock_init();uart_init();i2c_init();// 业务代码
}

在main函数中我们依次调用了clock_init/uart_init/i2c_init这些必要的初始化操作,如果后续我们还要添加pwm的初始化,我们需要再次在main函数里面添加pwm_init调用这样的代码;但是当一个系统中各种各样的初始化比较多时,我们很容易忘记对某个模块或者功能调用初始化函数。

那有没有一种更加高效简单的并且不易出错的方式呢?那就是组件初始化。

本篇就给大家详细讲解一下RT-Thread的组件初始化实现原理。

本篇涉及到的知识点比较多,每个知识点都需要理解。

二、介绍

在正式介绍之前,大家需要知道几个基本知识点。

  • 函数指针类型和函数指针变量

  • 编译器基本知识

  • GNU属性扩展__attribute__

  • 指针引用与解引用

在RT-Thread源码有这样两个宏定义

#define RT_SECTION(x)               __attribute__((section(x)))

通常情况下编译会将代码放置在.code段中,数据放置在.data或者.bss段;有些时候我们可能想将某个代码或者数据放置在特殊的段内,就可以使用section属性,具体用法如下

int a RT_SECTION(".mydata") = 10;
RT_SECTION(".mybss") int b; int my_function() RT_SECTION(".mycode");
int my_funciton() {return 0;
}

上面的代码片段中,全局变量a放置在.mydata段,全局变量b放置在.mybss段,my_function函数放置在.mycode段。

关于section的详细说明请查看

Variable Attributes - Using the GNU Compiler Collection (GCC)


#define RT_USED                     __attribute__((used))

有些时候我们可能定义了一些函数或者变量并没有被引用,编译可能会将这些函数或者变量从目标文件中去除,used属性的作用就是保留这些符号。

接下来我们来看一下跟组件初始化有关的宏定义

/* initialization export */
#ifdef RT_USING_COMPONENTS_INIT
typedef int (*init_fn_t)(void);
#ifdef _MSC_VER
#pragma section("rti_fn$f",read)#if RT_DEBUG_INITstruct rt_init_desc{const char* level;const init_fn_t fn;const char* fn_name;};#define INIT_EXPORT(fn, level)                                  \const char __rti_level_##fn[] = ".rti_fn." level;       \const char __rti_##fn##_name[] = #fn;                   \__declspec(allocate("rti_fn$f"))                        \RT_USED const struct rt_init_desc __rt_init_msc_##fn =  \{__rti_level_##fn, fn, __rti_##fn##_name};#elsestruct rt_init_desc{const char* level;const init_fn_t fn;};#define INIT_EXPORT(fn, level)                                  \const char __rti_level_##fn[] = ".rti_fn." level;       \__declspec(allocate("rti_fn$f"))                        \RT_USED const struct rt_init_desc __rt_init_msc_##fn =  \{__rti_level_##fn, fn };#endif
#else#if RT_DEBUG_INITstruct 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 RT_SECTION(".rti_fn." level) = \{ __rti_##fn##_name, fn};#else#define INIT_EXPORT(fn, level)                                                       \RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn#endif
#endif
#else
#define INIT_EXPORT(fn, level)
#endif/* board init routines will be called in board_init() function */
#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 initialization) */
#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")
/* application initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

首先看看一下关于INIT_EXPORT的宏定义

    typedef int (*init_fn_t)(void);#if RT_DEBUG_INITstruct 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 RT_SECTION(".rti_fn." level) = \{ __rti_##fn##_name, fn};#else#define INIT_EXPORT(fn, level)                                                       \RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn#endif

首先关于代码行

typedef int (*init_fn_t)(void);

这是一个函数指针类型声明,没有入参,返回值为int。关于函数指针类型和函数指针变量的说明请看上面的链接,这边不再赘述。

其次代码行

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

RT_USED我们已经讲解过了,用于限定函数或者变量属性的。

##是用于宏定义中拼接字符使用的

假如我在代码中按照下面的代码片段调用INIT_EXPORT宏

int myfunction() {return 0;
}
INIT_EXPORT(myfunction, "1");

展开后

__attribute__((used)) const init_fn_t __rt_init_myfunction __attribute__((section(".rti_fn.1"))) = myfunction;

注意此处的__rt_init_myfunction是个函数指针类型变量,指向myfunction这个函数,既然是变量那么其类型就是Data,并且这个变量最终放在.rti_fn.1段中。


/* board init routines will be called in board_init() function */
#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 initialization) */
#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")
/* application initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

这几个宏都是INIT_EXPORT的扩展,区别在于通过不同的宏限定后其段名的区别分别为.rti_fn.1、.rti_fn.2、.rti_fn.3、.rti_fn.4、.rti_fn.5

注意这边关于编译链接有个知识点

编译器会按照段名对符号进行排序,排序方式默认是按照字符的升序排序;也就是说用INIT_BOARD_EXPORT的限定的函数符号的地址肯定比INIT_APP_EXPORT限定的地址小。

有些博客中没有提到这点,或许是认为大家都对编译链接的过程很了解。

有了上面介绍的知识点后,我们从代码层面来详细说明组件初始化的实现

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_start/rti_board_start/rti_board_end/rti_end本身之外,还定义了__rt_init_rti_start/__rt_init_rti_board_start/__rt_init_rti_board_end//__rt_init_rti_end这四个init_fn_t类型的函数指针类型变量,并且这些变量依次指向对应的函数。

我们通过map文件可以确认上面的描述

首先上图中的每个符号类型都是Data,也就是变量,其次因为每个变量都是函数指针类型,故大小都是4字节,最后每个变量的地址也是按照段名的升序排序。

RT-Thread中关于组件初始化代码

void rt_components_board_init(void)
{
#if RT_DEBUG_INITint result;const struct rt_init_desc *desc;for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++){rt_kprintf("initialize %s", desc->fn_name);result = desc->fn();rt_kprintf(":%d done\n", result);}
#elsevolatile 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_DEBUG_INIT */
}

我们只关注这段代码

volatile 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)();
}

for循环中首先对变量__rt_init_rti_board_start取地址赋值给fn_ptr这个函数指针类型的指针,然后再解引用,因为__rt_init_rti_board_start这个变量的值是一个函数地址,所以

(*fn_ptr)();

就是执行__rt_init_rti_board_start变量引用的函数;由于通过组件初始化宏

INIT_BOARD_EXPORT(fn) 

限定的函数都有一个对应的变量来记录其地址,这些变量的地址都被限定在变量__rt_init_rti_board_start/__rt_init_rti_board_end的地址区间内,故可以通过此循环依次执行这些函数,例如上图中__rt_init_mpu_init变量就保存的是mpu_init函数的地址。


void rt_components_init(void)
{
#if RT_DEBUG_INITint result;const struct rt_init_desc *desc;rt_kprintf("do components initialization.\n");for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++){rt_kprintf("initialize %s", desc->fn_name);result = desc->fn();rt_kprintf(":%d done\n", result);}
#elsevolatile 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 /* RT_DEBUG_INIT */
}

其中代码段

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

代码基本相同,只是for循环限定的函数是__rt_init_rti_board_end/__rt_init_rti_end两个地址区间内的。

另外需要注意的是rt_components_init是main_thread_entry线程函数执行,也就是说此时调度器已经运行;rt_components_board_init在rt_hw_board_init函数被调用,此时调度器还未运行。

最后关于组件初始化的一些参考资料大家可以查看RT-Thread官网组件初始化


http://www.ppmy.cn/news/12695.html

相关文章

自动驾驶专题介绍 ———— 超声波雷达

文章目录介绍工作原理特点常见参数介绍 在汽车碰撞事故中&#xff0c;有大约15%的事故是因为倒车时汽车的后视能力不足引起的&#xff0c;因为增加汽车的后视能力的超声波雷达的研究成为了当下的热点之一。安全避免碰撞的前提是快速准确的测量障碍物于汽车之间的距离。超声波雷…

Opencv图像及视频基本操作

✨ 原创不易,还希望各位大佬支持一下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下 👍 点赞,你的认可是我创作的动力! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

Https为什么比Http安全?

Https是在Http之上做了一层加密和认证&#xff1b; 主要的区别是Https在TLS层对常规的Http请求和响应进行加密&#xff0c;同时对这些请求和响应进行数字签名。 Http请求的样式&#xff1a; 明文传输&#xff0c;通过抓包工具可以抓到 GET /hello.txt HTTP/1.1 User-Agent: c…

【JavaEE初阶】第二节.进程篇

文章目录 前言 一、操作系统 二、进程 2.1 进程的概念 2.2 进程的管理​​​​​​​​​​​​​​ 2.3 PCB 2.3.1 PCB里面的一些属性 2.3.2 进程的调度 2.3.3 进程的虚拟地址空间 2.3.4 进程间通信 总结 前言 本节内容我们继续对JavaEE的有关内容进行学习&#xff0c;…

[JavaEE]阻塞队列

专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录: 1.阻塞队列的概念 2.标准库中的阻塞队列 3.生产者…

nacos在国产银河麒麟系统飞腾CPU部署安装

1 jdk安装 1.1 首先查看系统是否自带jdk java -version1.2 卸载系统自带的openjdk apt-get remove openjdk*或者先查看安装的java&#xff1a;dpkg -l | grep java&#xff0c;再根据对应java的package卸载java&#xff1a;sudo apt-get remove ca-certificates-java1.3 安装…

OpenCV(12)-OpenCV的机器学习

OpenCV的机器学习 基本概念 计算机视觉是机器学习的一种应用&#xff0c;而且是最有价的应用 人脸识别 哈尔(Haar)级联方法深度学习方法(DNN) Haar人脸识别方法 哈尔(Haar)级联方法是专门为解决人脸识别而推出的&#xff0c;在深度学习还不流行时&#xff0c;哈尔已可以商…

35岁高龄程序员的 4 条出路,提早布局,避免出局!

目录 一、40岁回首往事&#xff1a;自己竟没有任何核心优势二、公司遇到危机时40岁大龄程序员会怎么样三、适合大龄程序员的几条职业发展路线四、最后的寄语 这篇文章&#xff0c;给大家聊聊Java工程师的职业发展规划的一些思考&#xff0c;同时也给不少20多岁、30多岁&#…