初始化函数表
2026/4/26大约 7 分钟结构体初始化数据结构
初始化函数表
📚 本节导读
用途: 索引各个初始化级别的函数表
来源: kernel/source/os_startup.c
一、概述
1.1 概念定义
gs_init_call_table 是 OneOS 多级初始化机制中用于索引各个初始化级别函数表的指针数组。
1.2 作用
- 索引 8 个初始化级别的函数表起始地址
- 提供
_k_run_init_call()函数遍历的范围边界 - 利用编译器段机制实现零开销的初始化函数管理
二、数据结构定义
2.1 完整代码(第 127-155 行)
#if defined(__CC_ARM) || defined(__CLANG_ARM) /* ARM MDK Compiler */
static const init_call_entry_t *gs_init_call_table[] = {&_os_init_call_pre_kernel_1_start + 1,
&_os_init_call_pre_kernel_2_start + 1,
&_os_init_call_post_kernel_start + 1,
&_os_init_call_pre_device_start + 1,
&_os_init_call_device_start + 1,
&_os_init_call_component_start + 1,
&_os_init_call_application_start + 1,
#ifdef OS_USING_SMP
&_os_init_call_smp_start + 1,
#endif /*OS_USING_SMP*/
&_os_init_call_os_init_end};
#elif defined(__GNUC__) /* for GCC Compiler */
static const init_call_entry_t *gs_init_call_table[] = {__init_call_pre_kernel_1_start,
__init_call_pre_kernel_2_start,
__init_call_post_kernel_start,
__init_call_pre_device_start,
__init_call_device_start,
__init_call_component_start,
__init_call_application_start,
#ifdef OS_USING_SMP
__init_call_smp_start,
#endif /*OS_USING_SMP*/
__os_init_end};
#else
#error "not supported compiler"
#endif /*__CC_ARM*/2.2 分步详细解释
第一步:编译器分支判断
#if defined(__CC_ARM) || defined(__CLANG_ARM) /* ARM MDK Compiler */- 解释:判断编译器类型,支持 ARM MDK 编译器
- 语法解析:
#if defined():条件编译预处理指令__CC_ARM:ARM 编译器宏__CLANG_ARM:ARM Clang 编译器宏
- 作用:根据不同编译器选择不同的实现方式
第二步:初始化函数表变量声明
static const init_call_entry_t *gs_init_call_table[]- 解释:初始化函数表的变量声明
- 含义:
static:仅在当前文件可见,避免全局命名冲突const:指针指向的内容不可修改,确保初始化函数表在运行时只读init_call_entry_t *:指向init_call_entry_t结构体的指针[]:表示这是一个指针数组,每个元素都是一个init_call_entry_t *指针
- 作用:声明一个用于索引各个初始化级别函数表的指针数组
第三步:ARM MDK 编译器的数组初始化
= {&_os_init_call_pre_kernel_1_start + 1,
&_os_init_call_pre_kernel_2_start + 1,
&_os_init_call_post_kernel_start + 1,
&_os_init_call_pre_device_start + 1,
&_os_init_call_device_start + 1,
&_os_init_call_component_start + 1,
&_os_init_call_application_start + 1,
#ifdef OS_USING_SMP
&_os_init_call_smp_start + 1,
#endif /*OS_USING_SMP*/
&_os_init_call_os_init_end};- 解释:ARM MDK 编译器下的初始化函数表数组初始化
- 含义:
&_os_init_call_pre_kernel_1_start:获取标记函数的地址+ 1:指针向后偏移 1 个init_call_entry_t元素,跳过标记函数本身&_os_init_call_os_init_end:作为结束标记
- 作用:为 ARM MDK 编译器建立初始化函数表,索引各个级别的起始和结束地址
第四步:GCC 编译器的条件分支
#elif defined(__GNUC__) /* for GCC Compiler */- 解释:GCC 编译器的条件分支
- 含义:
__GNUC__:GCC 编译器的宏定义
- 作用:判断是否为 GCC 编译器,选择对应的实现方式
第五步:GCC 编译器的数组初始化
static const init_call_entry_t *gs_init_call_table[] = {__init_call_pre_kernel_1_start,
__init_call_pre_kernel_2_start,
__init_call_post_kernel_start,
__init_call_pre_device_start,
__init_call_device_start,
__init_call_component_start,
__init_call_application_start,
#ifdef OS_USING_SMP
__init_call_smp_start,
#endif /*OS_USING_SMP*/
__os_init_end};- 解释:GCC 编译器下的初始化函数表数组初始化
- 含义:
__init_call_pre_kernel_1_start:直接使用链接器生成的段起始符号__os_init_end:链接器生成的结束符号
- 作用:为 GCC 编译器建立初始化函数表,使用链接器提供的段符号
第七步:SMP 级别可选支持
#ifdef OS_USING_SMP
&_os_init_call_smp_start + 1,
#endif /*OS_USING_SMP*/- 解释:SMP(对称多处理)级别的可选支持
- 含义:
- 仅当
OS_USING_SMP宏定义时才包含 SMP 级别
- 仅当
- 作用:根据配置选择性包含 SMP 初始化级别
第八步:结束标记
&_os_init_call_os_init_end};- 解释:初始化函数表的结束标记
- 含义:
- 作为最后一个元素,标记所有初始化级别的结束
- 作用:为遍历提供结束边界
第九步:编译器不支持的处理
#else
#error "not supported compiler"
#endif /*__CC_ARM*/- 解释:编译器不支持时的错误处理
- 含义:
#error:编译时错误指令,停止编译并输出错误信息
- 作用:确保在不支持的编译器上能及时发现问题
2.3 ARM MDK 编译器版本
static const init_call_entry_t *gs_init_call_table[] = {&_os_init_call_pre_kernel_1_start + 1,
&_os_init_call_pre_kernel_2_start + 1,
&_os_init_call_post_kernel_start + 1,
&_os_init_call_pre_device_start + 1,
&_os_init_call_device_start + 1,
&_os_init_call_component_start + 1,
&_os_init_call_application_start + 1,
#ifdef OS_USING_SMP
&_os_init_call_smp_start + 1,
#endif /*OS_USING_SMP*/
&_os_init_call_os_init_end};2.4 GCC 编译器版本
static const init_call_entry_t *gs_init_call_table[] = {__init_call_pre_kernel_1_start,
__init_call_pre_kernel_2_start,
__init_call_post_kernel_start,
__init_call_pre_device_start,
__init_call_device_start,
__init_call_component_start,
__init_call_application_start,
#ifdef OS_USING_SMP
__init_call_smp_start,
#endif /*OS_USING_SMP*/
__os_init_end};三、使用方式
3.1 索引方式
| 索引表达式 | 说明 |
|---|---|
gs_init_call_table[level - 1] | 该级别函数表的起始地址 |
gs_init_call_table[level] | 该级别函数表的结束地址 |
3.2 遍历方式
volatile const init_call_entry_t *entry;
for (entry = gs_init_call_table[level - 1];
entry < gs_init_call_table[level];
entry++)
{
/* 执行初始化函数 */
ret = (*entry->func)();
}四、实现原理
4.1 分段初始化函数注册(os_stddef.h 第 108-121 行)
初始化函数通过 OS_INIT_CALL 宏注册到指定的编译器段中:
#ifdef OS_INIT_CALL_DEBUG_EN
#define OS_INIT_CALL(fn, level, sublevel) \
OS_USED static const init_call_entry_t _os_init_call_##fn OS_SECTION( \
".init_call." OS_STRING(level) "." OS_STRING(sublevel)) = {#fn, fn}
#else
#define OS_INIT_CALL(fn, level, sublevel) \
OS_USED static const init_call_entry_t _os_init_call_##fn OS_SECTION( \
".init_call." OS_STRING(level) "." OS_STRING(sublevel)) = {fn}
#endif关键点解释:
OS_SECTION(".init_call." OS_STRING(level) "." OS_STRING(sublevel))- 将结构体放到特定段中OS_STRING()宏(第 93 行)将数字转换为字符串- 段名格式为
.init_call.<level>.<sublevel>,确保同一级别和子级别的函数连续排列 OS_USED宏确保编译器不会优化掉这些看似未使用的符号
4.2 编译器段属性宏(os_stddef.h 第 40-71 行)
段机制依赖于编译器的扩展属性,不同编译器的定义不同:
/* Compiler related definitions */
#if defined(__CC_ARM) || defined(__CLANG_ARM) /* For ARM compiler */
#define OS_SECTION(x) __attribute__((section(x)))
#define OS_USED __attribute__((used))
#elif defined(__IAR_SYSTEMS_ICC__) /* For IAR compiler */
#define OS_SECTION(x) @x
#define OS_USED __root
#elif defined(__GNUC__) /* For GNU GCC compiler */
#define OS_SECTION(x) __attribute__((section(x)))
#define OS_USED __attribute__((used))
#else
#error "Not supported the tool chain."
#endif4.3 初始化函数的遍历执行(os_startup.c 第 166-185 行)
_k_run_init_call() 函数负责遍历指定级别的所有初始化函数并执行:
static os_err_t _k_run_init_call(int32_t level)
{
os_err_t ret;
volatile const init_call_entry_t *entry;
for (entry = gs_init_call_table[level - 1]; entry < gs_init_call_table[level]; entry++)
{
ret = (*entry->func)();
if (ret != OS_SUCCESS)
{
#ifdef OS_INIT_CALL_DEBUG_EN
OS_ASSERT_EX(OS_FALSE, "level %d automatic initialization faild of %s", level, entry->name);
#else
OS_ASSERT_EX(OS_FALSE, "level %d automatic initialization faild", level);
#endif
}
}
return OS_SUCCESS;
}执行流程:
- 从
gs_init_call_table[level - 1]获取该级别的起始地址 - 遍历直到
gs_init_call_table[level]结束地址 - 依次调用每个
entry->func()函数 - 如果失败,根据调试模式打印函数名(第 177 行)或仅打印级别(第 179 行)
4.4 编译器特定的起始标记(os_startup.c 第 51-125 行)
不同编译器有不同的段起始标记定义方式:
ARM MDK 编译器(第 51-108 行):
static os_err_t pre_kernel_1_start(void)
{
return OS_SUCCESS;
}
OS_INIT_CALL(pre_kernel_1_start, OS_INIT_LEVEL_PRE_KERNEL_1, "");- 定义空函数并用
OS_INIT_CALL注册到每个级别作为段起始 - 通过
&_os_init_call_<fn> + 1获取用户注册函数的真实起始地址
GCC 编译器(第 109-121 行):
extern const init_call_entry_t __init_call_pre_kernel_1_start[];
extern const init_call_entry_t __init_call_pre_kernel_2_start[];
// ... 更多级别声明- 通过
extern const init_call_entry_t __init_call_<level>_start[];声明链接器生成的段起始符号
为什么 ARM MDK 需要 +1,而 GCC 不需要?
ARM MDK 编译器的情况:
- ARM MDK 编译器没有提供直接访问段起始/结束地址的机制
- 因此需要手动定义一个"标记函数",用
OS_INIT_CALL注册到每个级别的第一个位置 - 这个标记函数本身也是一个
init_call_entry_t结构体,占用段的第一个位置 - 用户真正的初始化函数会注册在这个标记函数后面
- 所以需要
+ 1来跳过标记函数,指向用户实际的第一个初始化函数
GCC 编译器的情况:
- GCC 编译器配合链接脚本(Linker Script)可以直接生成段的起始/结束符号
- 例如:
__init_call_pre_kernel_1_start直接指向.init_call.1.*段的起始地址 - 这个地址就是用户第一个初始化函数的位置,无需额外偏移
- 链接器会自动处理段的起始和结束位置,不需要手动定义标记函数
总结:
| 特性 | ARM MDK 编译器 | GCC 编译器 |
|---|---|---|
| 段标记方式 | 手动定义标记函数 | 链接器自动生成符号 |
| 访问方式 | &_os_init_call_xxx + 1 | __init_call_xxx_start |
| 偏移需求 | 需要 +1 跳过标记函数 | 不需要偏移 |
| 依赖 | 依赖代码中的标记函数 | 依赖链接脚本配置 |
4.5 优势
- 零开销: 不需要运行时构建函数表,所有工作在编译链接时完成
- 顺序保证: 链接时按注册顺序排列,子级别确保同一级别内的执行顺序
- 灵活扩展: 新增初始化级别无需修改核心代码,只需更新链接脚本
- 调试友好: 支持调试模式记录函数名,便于定位问题