Skip to content

Latest commit

 

History

History
325 lines (224 loc) · 8.22 KB

File metadata and controls

325 lines (224 loc) · 8.22 KB

fc_auto_init 设计与实现说明

本文档对应以下代码:

1. 模块定位

fc_auto_initcore/ 里负责“分阶段自动初始化注册”的模块。

它解决的问题不是“怎么初始化某个对象”,而是:

  • 如何把初始化函数注册到固定阶段
  • 如何给这些初始化函数设优先级
  • 如何在不同编译器下通过段机制把它们收集起来
  • 如何在运行时按顺序执行

它本质上提供的是一个“初始化调度框架”。

2. 初始化阶段模型

当前实现把初始化划分为四个阶段:

  1. ENV main() 之前,适合纯数据结构和基础环境
  2. CLOCK 时钟配置后
  3. DEVICE 外设初始化阶段
  4. APP 应用层或系统启动后阶段

整体关系如下:

上电 / 程序装载
        |
        v
fc_section_init_env()
        |
        v
main()
  |
  +--> 时钟配置完成 -> fc_section_init_clock()
  |
  +--> 外设基础初始化 -> fc_section_init_device()
  |
  +--> 应用任务启动后 -> fc_section_init_app()

其中只有 ENV 阶段可以在 USE_FC_AUTO_INIT=1 时自动通过 constructor 触发。

后面三个阶段都需要用户显式调用。

3. 总体分层

+--------------------------------------------------------------+
|                       各功能模块的 init 函数                  |
|  fc_default_port_init / fc_log_init / 自定义 board_init      |
+-------------------------------+------------------------------+
                                |
                                v
+--------------------------------------------------------------+
|                       INIT_EXPORT_* 宏层                      |
|  INIT_EXPORT_ENV / CLOCK / DEVICE / APP                      |
+-------------------------------+------------------------------+
                                |
                                v
+--------------------------------------------------------------+
|                    链接段 / section 收集层                    |
|  fc_section_0 / 1 / 2 / 3                                     |
+-------------------------------+------------------------------+
                                |
                                v
+--------------------------------------------------------------+
|                    fc_section_init_* 运行层                   |
|  按 order 从小到大依次执行                                    |
+--------------------------------------------------------------+

所以 fc_auto_init 的角色不是“初始化实现者”,而是“初始化注册器 + 调度器”。

4. 核心数据结构

真正被放进段里的不是裸函数指针,而是:

typedef struct
{
    fc_auto_init_func_t func;
    size_t              order;
} fc_auto_init_elem_t;

这意味着每个注册项有两部分信息:

  • func 实际执行的函数
  • order 当前阶段内的优先级

数字越小越早执行。

头文件里还提供了几组默认 order:

  • FC_PORT_INIT_ORDER = 100
  • FC_TRANS_INIT_ORDER = 110
  • FC_LOG_INIT_ORDER = 120

这些是给基础模块之间拉开依赖顺序用的。

5. 宏层设计

5.1 导出宏

最常用的是这四个:

  • INIT_EXPORT_ENV(func[, order])
  • INIT_EXPORT_CLOCK(func[, order])
  • INIT_EXPORT_DEVICE(func[, order])
  • INIT_EXPORT_APP(func[, order])

它们最终都会走到:

  • FC_INIT_EXPORT()

把一个 fc_auto_init_elem_t 放进对应段。

5.2 段收集宏

头文件里有三组关键底层宏:

  • SECTION_EXTERN(section_name)
  • ELEM_EXPORT(section_name, elem)
  • section_info(section_name, type_ptr, count)

它们的作用分别是:

  • 声明段起止符号
  • 往段里放元素
  • 在运行时取出段首地址和元素数量

6. 编译器适配方式

fc_auto_init 的一个重点是: 它不是只写给某一个编译器的。

头文件里按三类工具链做了适配:

  • ARMCC / armclang
  • IAR
  • GCC

适配思路都是一致的:

  1. 把元素放进命名 section
  2. 通过 section 起止符号拿到整段范围
  3. 运行时把这段解释成 fc_auto_init_elem_t[]

需要注意的是,源码里对:

  • IAR
  • GCC

都保留了 need test 的 warning,这说明当前作者对这些路径的意图是明确的,但并没有像 ARMCC 路径那样完全宣称已经长期验证。

因此在跨工具链移植时,建议优先先做一次段符号验证。

7. 运行时执行逻辑

运行时真正干活的是:

  • fc_section_init_env()
  • fc_section_init_clock()
  • fc_section_init_device()
  • fc_section_init_app()

每个函数内部都做了两件关键的事。

7.1 ONCE_RUNNING()

这个宏通过静态布尔变量保证:

  • 每个阶段只执行一次

所以即使外部重复调用 fc_section_init_device(),也不会重复跑设备初始化。

7.2 ORDER_SECTION_RUN()

这个宏的运行方式不是“先排序再执行”,而是:

  1. 先遍历一遍,找出本段元素数、最小 order、最大 order
  2. 从最小 order 开始执行
  3. 每轮再扫描一遍,找出比当前更大的最小 order
  4. 直到本段全部执行完

也就是说,它本质上是一个“小规模按 order 分层扫描”的实现。

这样做的特点是:

  • 不需要额外排序内存
  • 实现简单
  • 适合嵌入式里注册项本来就不多的场景

代价是:

  • 时间复杂度不是最优
  • 但初始化阶段通常只跑一次,这个代价完全可以接受

7.3 同优先级的顺序

同一个 order 下的多个元素会按段内枚举顺序执行。

这通常接近于:

  • 链接顺序
  • 对象文件顺序

但不应把它当作一个强保证。

如果两个初始化之间存在硬依赖,最好直接把 order 拉开。

8. 默认的空注册项

fc_auto_init.c 末尾放了一个空函数:

  • __void__func

并把它注册进四个阶段。

它的作用不是功能逻辑,而是:

  • 避免空 section 带来的链接器/编译器告警

所以它属于框架兜底,不是给业务层调用的。

9. 使用方式

最典型的用法如下:

static void my_board_init(void)
{
    /* board init */
}

static void my_uart_init(void)
{
    /* uart init */
}

INIT_EXPORT_ENV(my_board_init, 50);
INIT_EXPORT_DEVICE(my_uart_init, 200);

然后在程序启动流程里:

int main(void)
{
    /* ENV 阶段可由 constructor 自动执行 */

    board_clock_config();
    fc_section_init_clock();

    board_device_init();
    fc_section_init_device();

    app_start();
    fc_section_init_app();
}

10. 在整个框架中的位置

core/ 的依赖关系看,fc_auto_init 更像横向基础设施:

             +----------------------+
             |   fc_port_init       |
             +----------------------+
                        |
                        v
+--------------------------------------------------------------+
|                       fc_auto_init                            |
|  段注册 / 优先级 / 分阶段执行                                 |
+--------------------------------------------------------------+
                        ^
                        |
             +----------------------+
             |    fc_log_init       |
             +----------------------+

它不直接做业务,但很多模块会借它把自己的默认初始化挂到统一时序上。

11. 使用建议

适合放进自动初始化的内容:

  • 纯静态对象初始化
  • 默认端口、默认日志、协议表注册
  • 不依赖复杂运行时上下文的基础设施

不建议放进去的内容:

  • 依赖任务上下文的逻辑
  • 依赖外部对象已经动态创建的逻辑
  • 有明显重入副作用的大型业务流程

简单说:

  • fc_auto_init 适合“框架搭骨架”
  • 不适合“把整个业务流程塞进 constructor”

12. 关键注意点

最后有几条实际使用时最容易踩到的点:

  1. ENV 阶段自动执行并不等于所有阶段都自动执行
  2. 同优先级顺序不要过度依赖,最好显式错开 order
  3. 段机制依赖工具链,移植时先验证 section 起止符号
  4. 初始化函数必须是“无参、无返回值”
  5. 如果还想增加更多阶段,建议仿照现有头文件和 fc_auto_init.c 的方式扩展,而不是直接把业务逻辑硬塞进当前 4 段里