自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。这篇文章就来探索一下其中的奥秘, 简单理解其原理!
| 知识点补充
__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条评论
快来发表一下你的评论吧 !