代码自动初始化
概述
在嵌入式开发过程中,可能会遇到初始化代码自动初始化,比如RTT中就运用到这项技术。那么初始化代码是如何做到自动化调用的呢?
在嵌入式实际开发过程中,往往需要对 bsp 部分进行外设配置,以及一些模块、参数进行初始化,常见的方法如下:
- 将各个部分的初始化代码分别封装成一个单独的函数,然后再main函数刚开始的地方进行调用已实现初始化;
该方法简单粗暴,但是此方法存在一些不足: - 所有初始化均需要在main函数内调用,不能做到彻底解耦
- 框架设计不友好,部分非业务部分初始化,如软件框架类的初始化,往往不希望还需要业务在main函数内调用初始化
那有没有一种方法能解决上述的问题呢?
答案是肯定的,本文将向大家介绍一种自动初始化的实现方式。注意,此方案在不同的平台,由于链接器使用的链接脚本可能不一致,容易出现问题,需要大家重点注意,细节在下文中将详细介绍。
案例讲解
为了更好的阐述代码自动初始化的技术,本文通过一个demo程序进行讲解如何实现该技术,以下是该demo程序:
typedef void (*init_func)(void);
#define INIT_EXPORT(fn, level) const init_func init_##fn __attribute__((used, section(".init."level))) = fn
void start(void)
{
return;
}
INIT_EXPORT(start, "1");
void end(void)
{
return;
}
INIT_EXPORT(end, "4");
接下来分析上述代码:
关于宏定义:#define INIT_EXPORT(fn, level) const init_func init_##fn attribute((used, section(".init."level))) = fn的代码分析如下:
- 首先定义一个函数指针typedef void (*init_func)(void);用于所有初始化函数的指针类型;
- 定义宏INIT_EXPORT(fn, level) 作为一个函数接口,后续的初始化函数可通过此接口加入自动初始化列表内
- const init_func init_##fn,其中##为连接符,假设参数fn为test,则init_##fn为init_test;init_func 为指令类型
- const init_func init_##fn与const int *p是同一个概念,只不过数据的类型不一样
- const init_func init_##fn定义了一个init_func类型的指针变量,此指针还没有赋值初始化,因此将参数fn赋值给此变量,宏定义可以简化为#define INIT_PORT(fn, level) const init_func init_##fn = fn(__attribute__先忽略)
- attribute((used, section(".init."level))) 这是一个关键字进行修饰,可以做如下拆解:
- attribute((used))用来告诉编译器,此函数如果没有被调用,也不要被优化
- attribute((section(".init."level)))用来修饰的内容放入指定的段“.init.”level中(编译器在你编程的时候自动完成),注意level为宏的第二个传入参数,传入来的时候是一个字符串,所以不用加#加进行拼接,C语言中两个字符串会自动拼接。
综述分析:宏#define INIT_EXPORT(fn, level) const init_func init_##fn attribute((used, section(".init.“level))) = fn,其实定义了一个init_func类型的函数指针,并将此函数指针通过__attribute__((used, section(”.init."level)))指定存放到特定的段内。
demo程序中定义了两个函数:
- void start(void)
- void end(void)
并通过宏INIT_EXPORT(fn,level)添加到自动初始化段内 - INIT_EXPORT(start, “1”);
- INIT_EXPORT(end, “6”);
注意事项:
- level参数是一个字符串,而不是数字
- 用户自定义初始化函数编译器会自动添加到start和end的level值中间,是由于编译器采用__attribute__((section(".init."level))) 指定放入到指定段的时候会针对其进行排序,keil默认采用根据名称的方式排序,因此需要放置在用户自定义的,放置在用 start 和 end 中间,方便后续遍历 start 和 end 中间部分进行完整初始化
最后的vhdl_board_init初始化函数:
void vhdl_board_init(void)
{
const init_func *fn_ptr = NULL;
for (fn_ptr = &init_start; fn_ptr < &init_end; fn_ptr++)
{
(*fn_ptr)();
}
}
此函数在main函数中调用,通过for循环遍历start和end中间的段,并调用进行初始化。
通过上述一系列的操作,用户初始化函数时只需要调用宏INIT_EXPORT(fn,level)即可完成自动初始化。
为了针对不同类型的初始化,可以将start和end之间距离拉大点,用作区别不同类型的初始化,如下所示
INIT_EXPORT(start, "1");
INIT_EXPORT(end, "6");
并且对外开放相应的接口宏
#define INIT_BSP_PORT(func) INIT_EXPORT(func, "2");
#define INIT_DATA_PORT(func) INIT_EXPORT(func, "3");
用户通过调用 BSP_INIT_PORT 和 DATA_INIT_PORT 进行注册
检测初始是否成功??
检测MAP文件
在上述方法中,我们使用 attribute((section(".init."level))) 将数据存入指定的段内,那么是否成功了呢,以及排序方式是否符合我们的预期呢?接下来我们需要通过检查map文件进行确认。
keil / MDK 环境检查
keil/mdk编译完程序之后,双击工程栏处的工程,可以查看map文件,还不知道的可以网上搜下
以下是上述demo的map文件中关键字段,我们可以看到我们需要的数据存放在正确的段内,并且排序也是正常的,注意一定要检查排序和字段是否完整!
gcc 环境
- 在链接器脚本ld文件中建立sections区域块.text中定义新的section information for inital ,比如rtt如下:
.ALIGN(4)
__rt_init_start=.;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end=.;
.ALIGN(4)
- 编写auto_init文件
typedef void (*init_fn_t)(void);
#define INIT_EXPORT(fn,level) __attribute__((used)) const init_fn_t __rt_init_##fn __attribute__((section(".rti_fn." level))) = fn
static int rti_start(void)
{
return 0;
}
INIT_EXPORT(rti_start,"0");
static int rti_board_start(void)
{
return 0;
}
// 注册rti_board_start到内存中
INIT_EXPORT(rti_board_start,"0.end");
static int rti_board_end(void)
{
return 0;
}
// 注册rti_board_end到内存中
INIT_EXPORT(rti_board_end,"1.end");
static int rti_end(void)
{
return 0;
}
INIT_EXPORT(rti_end,"6.end");
void rt_components_board_init(void)
{
volatile const init_fn_t *fn_ptr;
// 遍历内存中注册的地址段
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
}
// 遍历剩下的地址内存空间,防止函数指针访问出错
void rt_components_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
rt_kprintf("do components initialization.\n");
for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
#else
volatile const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
(*fn_ptr)();
}
#endif /* RT_DEBUG_INIT */
}
#endif /* RT_USING_COMPONENTS_INIT */
//最后生成的map文件如下:
*(SORT(.rti_fn*))
.rti_fn.0 0x0800cf30 0x4 ./rt-thread/src/components.o
0x0800cf30 __rt_init_rti_start
.rti_fn.0.end 0x0800cf34 0x4 ./rt-thread/src/components.o
0x0800cf34 __rt_init_rti_board_start
.rti_fn.1 0x0800cf38 0x4 ./drivers/drv_clk.o
0x0800cf38 __rt_init_clock_information
.rti_fn.1.end 0x0800cf3c 0x4 ./rt-thread/src/components.o
0x0800cf3c __rt_init_rti_board_end
.rti_fn.6 0x0800cf40 0x4 ./rt-thread/components/finsh/shell.o
0x0800cf40 __rt_init_finsh_system_init
.rti_fn.6.end 0x0800cf44 0x4 ./rt-thread/src/components.o
0x0800cf44 __rt_init_rti_end
0x0800cf48 __rt_init_end = .
0x0800cf48 . = ALIGN (0x4)