半年前在公众号看过section初始化列表,当时看得迷迷糊糊没有去深入学习,最近在项目中刚好需要用到,重新去学习了一下。在嵌入式学习工作中,初始化硬件时一般都是写一个初始化函数然后去到main函数去调用,当不需要用到某一个硬件模块时,如果没有去main函数删除对应的硬件初始化函数,程序就会报错,这样程序耦合性就变高了。使用section关键字初始化函数列表能够降低耦合性,下面介绍keil环境下section关键字初始化函数列表的两种方法。
一、section关键字
section主要作用是将函数或者变量放在指定段中,既可以是RAM内存和可以是ROM内存,这样就可在指定的位置取出。标记为attribute__((used))的变量被标记在目标文件中,以避免链接器优化删除。
#define SECTION(level) __attribute__((used, section("my_val."level)))
static int value1 SECTION("0");
static int value2 SECTION("0");
int SECTION("0") test0 (void)
{
return value1;
}
int SECTION("0") test1 (void)
{
return value2;
}
int main(void)
{
value1 = 1;
value2 = 2;
test0();
test1();
}
查看map文件
分析map文件可知,value1和value2被放在RAM内存的my_val.0段是连续的,test0和test1被放在ROM内存my_val.0段也是连续的。
二、初始化列表
方法一:
由上面可知,section字段中变量在内存中是连续的,所以我们将需要初始化函数以指针形式存在字段中,只需要记录字段的首地址和结束地址,在mian函数将字段中的函数指针遍历就可以调用对应的初始化函数,从事实现自动初始化函数列表。
(1)代码实现
typedef void (*initcall_t)(void);//函数指针类型
typedef struct
{
initcall_t fn_minit;
} initcall_type; //封装成结构体类型
extern initcall_type __initcall_start_;//记录开始地址
extern initcall_type __initcall_end_; //记录结束地址
#define INITCALL_LIST(ifn) \
static initcall_type __initcall_##ifn __attribute__((used, section(".init.1"))) = {ifn}
#define INITCALL_START \
initcall_type __initcall_start_ __attribute__((used, section(".init.0.start")))
#define INITCALL_END \
initcall_type __initcall_end_ __attribute__((used, section(".init.1.end")))
#define INITIALISE_ALL \
{ for (initcall_type *fn = &__initcall_start_+1; fn < &__initcall_end_; fn++) { \
if (fn->fn_minit) { \
(fn->fn_minit)(); } \
} \
}
static void test0 (void)
{
printf("test0 method1 initialize\r\n");
}
INITCALL_LIST(test0);
static void test1 (void)
{
printf("test1 method1 initialize\r\n");
}
INITCALL_LIST(test1);
INITCALL_START;
INITCALL_END;
int main(void)
{
SystemInitialize();
INITIALISE_ALL; //初始化函数列表
while(1)
{
}
}
(2)查看map文件,.init.1段存放2个函数指针所以占8个字节
(3)运行结果
方法二:
(1)keil中链接脚本的修改步骤
(2)打开.sct源文件
(3)修改.sct文件,添加.init.1字段,大小为0x100
(4)代码实现
typedef void (*initcall_t)(void);
typedef struct
{
initcall_t fn_minit;
} initcall_type;
#define INITCALL_LIST(ifn) \
static initcall_type __initcall_##ifn __attribute__((used, section(".init.1"))) = {ifn}
extern uint32_t Image$$MY_INITCALL$$Base;
extern uint32_t Image$$MY_INITCALL$$Limit;
initcall_type *__initcall_start_ = (initcall_type *)&Image$$MY_INITCALL$$Base;//获得段起始地址
initcall_type * __initcall_end_ = (initcall_type *)&Image$$MY_INITCALL$$Limit;//获得结束段地址
#define INITIALISE_ALL \
{ for (initcall_type *fn = __initcall_start_; fn < __initcall_end_; fn++) { \
if (fn->fn_minit) { \
(fn->fn_minit)(); } \
} \
}
static void test0 (void)
{
printf("test0 method2 initialize\r\n");
}
INITCALL_LIST(test0);
static void test1 (void)
{
printf("test1 method2 initialize\r\n");
}
INITCALL_LIST(test1);
int main(void)
{
SystemInitialize();
INITIALISE_ALL; //初始化函数列表
while(1)
{
}
}
(5)查看map文件
(6)运行结果
三、总结
虽然是学习section关键字的用法,但是在实现初始化列表的过程中还设计了许多知识点,比如#define宏定义的巧妙使用、单片机内存管理相关知识、以及ARM脚本知识等,后面会写一篇关于单片机内存管理的文章。
参考文章: