自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。这篇文章就来探索一下其中的奥秘, 简单理解其原理!
| 知识点补充
__attribute__((section(x)))是GNU C的一个特色之一,它可以用于将变量或函数放置在指定的段中。例如,你可以使用__attribute__((section(".my_section")))将变量或函数放置在名为my_section的段中。这对于嵌入式系统编程和操作系统内核编程非常有用。
__attribute__((used))是GCC编译器提供的一个特性,用于告诉编译器在目标文件中保留一个静态变量或函数,即使它没有被引用。这样可以避免链接器删除未使用的节,或者确保某些特定的变量或函数被输出。
__attribute__((unused))是GCC编译器提供的一个特性,用于告诉编译器某个变量或函数可能未被使用,从而避免编译器产生未使用变量或函数的警告。在变量或函数前加上__attribute__((unused))即可使用该特性。
__attribute__((aligned(n)))是GCC编译器提供的一个特性,用于设置变量、类型、函数的对齐方式。它的作用是告诉编译器在分配内存空间时,要求以n个字节为边界。
__attribute__((weak))是GCC编译器提供的一个特性,用于声明或定义一个弱符号(weak symbol)。弱符号是指在链接时,如果存在同名的强符号(strong symbol),则会被强符号覆盖。

| 原理研究
深入研究了一下, 发现这样使用宏真的很奇妙, 这里就简单介绍一下原理:
export.h文件
#ifndef __EXPORT_H
#define __EXPORT_H
#define EXPORT_USED __attribute__((used))
#define EXPORT_SECTION(x) __attribute__((section(x)))
typedef int (*export_init_fn_t)(void);
#define EXPORT_INIT_EXPORT(fn,level)
EXPORT_USED const export_init_fn_t __export_call_##fn EXPORT_SECTION(".export_call." level) = fn
//板级初始化 顺序1
#define EXPORT_BOARD_INIT(fn) EXPORT_INIT_EXPORT(fn,"1")
//设备初始化 顺序3
#define EXPORT_DEVICE_INIT(fn) EXPORT_INIT_EXPORT(fn,"2")
//组件初始化 顺序4
#define EXPORT_COMPONENT_INIT(fn) EXPORT_INIT_EXPORT(fn,"3")
//环境初始化 顺序5
#define EXPORT_ENV_INIT(fn) EXPORT_INIT_EXPORT(fn,"4")
//APP初始化 顺序6
#define EXPORT_APP_INIT(fn) EXPORT_INIT_EXPORT(fn,"5")
void export_components_init(void);
#endif
export.c文件
#include "export.h"
#include "stdio.h"
static int test_0_start(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_0_start,"0");
static int test_0_0(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_0_0,"0");
static int test_0_1(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_0_1,"0");
static int test_0_end(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_0_end,"0.end");
static int test_1_start(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_1_start,"1");
static int test_1_0(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_1_0,"1");
static int test_1_1(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_1_1,"1");
static int test_1_end(void)
{
return 0;
}
EXPORT_INIT_EXPORT(test_1_end,"1.end");
// 自动初始化(在main函数调用)
void export_components_init(void)
{
printf("pfn1:%p
", &__export_call_test_0_start);
printf("pfn2:%p
", &__export_call_test_0_0);
printf("pfn3:%p
", &__export_call_test_0_1);
printf("pfn4:%p
", &__export_call_test_0_end);
printf("pfn5:%p
", &__export_call_test_1_start);
printf("pfn6:%p
", &__export_call_test_1_0);
printf("pfn7:%p
", &__export_call_test_1_1);
printf("pfn8:%p
", &__export_call_test_1_end);
volatile const export_init_fn_t *pfn;
for(pfn = &__export_call_test_0_start; pfn < &__export_call_test_1_end; pfn++)
{
printf("%p
", pfn);
// (*pfn)();
}
}
结果输出:
pfn1:08000c50 pfn2:08000c54 pfn3:08000c58 pfn4:08000c5c pfn5:08000c60 pfn6:08000c64 pfn7:08000c68 pfn8:08000c6c 08000c50 08000c54 08000c58 08000c5c 08000c60 08000c64 08000c68
过程分析:
这个测试代码片段主要定义和使用了两个段, 每个段定义了开始和结束, 并且在开始和结束间插入了若干个函数, 通过观察地址的变化会发现, 它们是按规律递增的, 就可以使用遍历来调用指针指向的函数, 从而实现自动初始化外设的目的.
细节分析:
// 定义一个函数指针
typedef int (*export_init_fn_t)(void);
// 宏定义
#define EXPORT_INIT_EXPORT(fn,level)
EXPORT_USED const export_init_fn_t __export_call_##fn EXPORT_SECTION(".export_call." level) = fn
// 假设调用
EXPORT_INIT_EXPORT(test_1_0,"1");
// 一顿操作后, 内存就存在了一个export_init_fn_t __export_call_test_1_0存放在".export_call."的输入段中,并指定其属于第一级初始化段
// 就可以通过指针调用指针指向的函数来调用指定的函数,实现自动化初始化
| EventOS的EXPORT
这个先待定, 后续有时间再移植, export需要参考elab, 涉及到assertcommonexportlog, 感兴趣的读者可以参考:

链接//gitee.com/event-os/eventos/tree/dev_df/examples/stm32g070
使用了export机制可以让代码变得更加简洁, 感兴趣的读者可以在理解原理后进行完善和优化.
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !