#define app_run(func) layer_initcall_def(func, run, "app_run")
#define layer_initcall_def(func, layer, clayer) \
layer_initcall(func, layer, clayer, 0)
#define layer_initcall(func, layer, clayer, priority) \
static const init_call_t USED_ATTR __zinitcall_##layer##_##func \
__attribute__((section(".zinitcall." clayer #priority ".init"))) = (func)
代码逐行拆解:内核/RTOS 模块自动初始化注册机制(类Linux initcall 分段初始化)
整套宏作用:把应用函数func按分类+优先级放到指定段,系统启动时统一遍历段自动调用,不用手动逐个调用函数。
1. 顶层调用宏
#define app_run(func) layer_initcall_def(func, run, "app_run")
app_run(xxx_func)等价展开:
layer_initcall_def(xxx_func, run, "app_run")
• 用途:标记一个应用入口函数属于 run 分类、分类名字符串app_run
• 业务含义:所有用app_run(函数名)修饰的函数,归类为APP运行初始化函数。
2. 中间转接宏
#define layer_initcall_def(func, layer, clayer) \
layer_initcall(func, layer, clayer, 0)
补充默认优先级=0,转接到底层定义宏:
layer_initcall_def(func,run,"app_run") → layer_initcall(func, run, "app_run", 0)
3. 底层核心定义宏(关键)
#define layer_initcall(func, layer, clayer, priority) \
static const init_call_t USED_ATTR __zinitcall_##layer##_##func \
__attribute__((section(".zinitcall." clayer #priority ".init"))) = (func)
3.1 符号拼接 ## 令牌粘合
##layer##_##func:拼接变量名
示例:layer=run、func=test_app
生成变量名:__zinitcall_run_test_app
3.2 变量类型与修饰
• init_call_t:函数指针类型,原型:typedef void (*init_call_t)(void);,存初始化函数地址
• static const:只读静态全局,防止被优化删除
• USED_ATTR:一般是__attribute__((used)),禁止编译器未引用优化删掉该变量(关键:即使没代码引用变量,也保留在段里)
3.3 __attribute__((section("xxx"))) 自定义段(核心原理)
section(".zinitcall." clayer #priority ".init")
• #priority:#字符串化,数字变字符串
priority=0 → "0"
• clayer="app_run"
最终拼接段名:
.zinitcall.app_run0.init
示例:app_run(test_app)展开后完整代码:
static const init_call_t __zinitcall_run_test_app
__attribute__((section(".zinitcall.app_run0.init"))) = test_app;
含义:定义一个全局函数指针变量,把test_app函数地址存入自定义段.zinitcall.app_run0.init
4. 链接脚本 + 启动调用逻辑(配套隐含逻辑)
1. 链接脚本lds:把所有同名.zinitcall.app_runX.init段连续排布在内存固定区域;同分类不同priority(X数字)按段名排序,数字越小优先级越高,先执行。
2. 系统启动代码:
在启动初始化阶段,从链接脚本获取 .zinitcall.app_run0.init 段的起始地址、结束地址,循环遍历段内所有init_call_t函数指针,逐个调用:
// 伪代码启动遍历
extern init_call_t __zinit_app_run_start[], __zinit_app_run_end[];
for(int i=0; __zinit_app_run_start+i < __zinit_app_run_end; i++){
__zinit_app_run_start[i](); // 自动执行所有app_run注册的函数
}
5. 使用示例
// 定义应用初始化函数
void app_led_init(void){ /*LED初始化*/ }
void app_key_init(void){ /*按键初始化*/ }
// 注册到自动初始化
app_run(app_led_init);
app_run(app_key_init);
编译后:
app_led_init、app_key_init 的地址被放进 .zinitcall.app_run0.init 段,系统上电自动执行两个函数,无需手动调用。
6. 扩展特性
• 改priority参数即可调整执行顺序:
封装app_run_p1(func) layer_initcall_def(func,run,"app_run",1),段名变为.zinitcall.app_run1.init,优先级低于0
• 不同分类:可新增app_board(func),修改clayer="board",段变成.zinitcall.board0.init,分批次初始化:板级初始化→APP初始化。
总结
这套是嵌入式/RTOS通用组件自动化初始化框架,
不用在main里挨个调用初始化函数,通过宏注册+自定义段+链接排布,实现组件解耦、按需注册、按优先级自动初始化。和Linux内核module_init、__initcall实现思路一模一样。
__attribute__ 详解(GCC 编译器扩展语法,只用于GCC/ARM-GCC,标准C没有)
1、作用总述
__attribute__((xxx)):给变量/函数/结构体附加编译器特殊属性指令,告诉编译器:存储位置、优化规则、对齐、占用段、是否丢弃等。
格式:
__attribute__((属性1,属性2,...))
2、你代码里用到的两个属性
① __attribute__((section("段名")))
__attribute__((section(".zinitcall.app_run0.init")))
• 默认:全局变量放 .data / .rodata
• 加 section:强制把这个变量放到自定义段
你的初始化指针全部归集在同一个自定义段,链接脚本统一收拢,启动时遍历整段自动调用,就是这套初始化框架的核心。
② __attribute__((used))(对应宏 USED_ATTR)
• 不加:变量没有代码直接引用,编译器优化裁剪删掉
• used:告诉编译器:即便没代码引用,也保留目标文件符号,不被优化删除
初始化变量只有链接脚本使用,C代码从不直接写名字调用,必须加used防止被优化丢段。
3、嵌入式常用其他attribute
1. __attribute__((packed)):结构体取消对齐,紧凑排布(串口协议、寄存器结构体常用)
typedef struct{char a;int b;} __attribute__((packed)) test;
2. __attribute__((aligned(N))):指定N字节对齐
3. __attribute__((noreturn)):函数永不返回(如死机死循环函数)
4. __attribute__((weak)):弱符号,可被同名函数覆盖(驱动分层常用)
4、补充要点
1. 双下划线 __attribute__ 是GCC专属,MDK(ARMCC)用 __attribute / __at,IAR语法不同。
2. 只在编译阶段生效,不改变C语言语法逻辑,是编译器指令。
结合你源码一句话总结
__attribute__((section("xxx")))
= 指定存放段
USED_ATTR=__attribute__((used))
= 禁止编译器优化删除