利用__attribute__((section(“name”)))构建初始化函数表
之前在linux内核代码中经常看到函数导出的语句,在阅读rt-thread的代码时,也看到了,因为rt-thread的代码更小,且有keil项目进行参考,所以写了一下记录,望以后的项目中可以借鉴
typedef int (*init_fn_t)(void);
#define INIT_EXPORT(fn, level) \
__attribute__((used)) const init_fn_t __lig_init_##fn \
__attribute__((section(".lig_init_fn." level))) = fn
/*展开例子:*/
static int start(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(start,"0");
//==>
__attribute__((used)) const init_fn_t __lig_init_start __attribute__((section(".lig_init_fn.0"))) = start;
这个宏可以将函数注册到一个固定的段,在调用时,获取这个段的起始,然后通过指针偏移进行访问,常用在初始化,或者较为灵活的模块加入
这个宏的作用是定义一个函数类型变量__rt_init_xxx=xxx,然后指定__rt_init_xxx这个变量存放于(.rti_fn.level)段中,在keil中,编译器根据段的名称,自动处理了程序中的各个数据段的存储的排序,在此不用指定这个段的起始位置;
以下对这个宏的各个部分进行详细说明
__attribute__((used))
: 标记这个变量被使用,因为在程序中,这个变量可能以指针遍历索引的形式被访问,而不是显式的被调用,这时如果不标记为used,可能会被编译器认为没有使用此变量而优化掉const init_fn_t __rt_init_##fn
: 定义一个函数指针变量,这个变量的名称是__rt_init_##fn
,其中##
起到字符拼接的功能__attribute__((section(x)))
: 将变量放置到程序文件的x段
初始化函数表例子:
typedef int (*init_fn_t)(void);
#define INIT_EXPORT(fn, level) \
__attribute__((used)) const init_fn_t __lig_init_##fn \
__attribute__((section(".lig_init_fn." level))) = fn
static int start(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(start,"0");
static int end(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(end,"1.end");
static int init0(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(init0,"1");
static int init1(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(init1,"1");
static int init2(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(init2,"1");
static int init3(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(init3,"1");
static int init4(void)
{
printf("%s\n",__FUNCTION__);
return 0;
}
INIT_EXPORT(init4,"1");
int main(void)
{
volatile const init_fn_t *fn_ptr;
for (fn_ptr = &__lig_init_start; fn_ptr < &__lig_init_end; fn_ptr++)
{
(*fn_ptr)();
}
while(1);
}
在keil中编译后,在生成的map文件中,可以看到注册后的函数的存储地址
__lig_init_start 0x1a0011e0 Data 4 main.o(.lig_init_fn.0)
__lig_init_init0 0x1a0011e4 Data 4 main.o(.lig_init_fn.1)
__lig_init_init1 0x1a0011e8 Data 4 main.o(.lig_init_fn.1)
__lig_init_init2 0x1a0011ec Data 4 main.o(.lig_init_fn.1)
__lig_init_init3 0x1a0011f0 Data 4 main.o(.lig_init_fn.1)
__lig_init_init4 0x1a0011f4 Data 4 main.o(.lig_init_fn.1)
__lig_init_end 0x1a0011f8 Data 4 main.o(.lig_init_fn.1.end)
打印数据
start
init0
init1
init2
init3
init4/*没有end*/
注意: 利用段的名称使start和end排序正常,这样不管代码中的顺序是怎样的,编译后都能生成有序的排列;除了start和end外,注册的函数应该是顺序无关的,否则应该对段做更多的分级,以区分顺序,如.lig_init_fn.1
,.lig_init_fn.2
,.lig_init_fn.3
等
还有一种用法
typedef int (*lig_app_fn_t)(void);
#define APP_TAB_EXPORT(fn) \
__attribute__((used)) const lig_app_fn_t __lig_app_##fn \
__attribute__((section("Lig_app_tab"))) = fn
extern const int Lig_app_tab$$Base;
extern const int Lig_app_tab$$Limit;
static int app1(void)
{
printf("%s\r\n",__FUNCTION__);
return 0;
}
APP_TAB_EXPORT(app1);
static int app2(void)
{
printf("%s\r\n",__FUNCTION__);
return 0;
}
APP_TAB_EXPORT(app2);
static int app3(void)
{
printf("%s\r\n",__FUNCTION__);
return 0;
}
APP_TAB_EXPORT(app3);
int main(void)
{
volatile const lig_app_fn_t * appfn_ptr;
for (appfn_ptr = (lig_app_fn_t *)&Lig_app_tab$$Base; appfn_ptr < (lig_app_fn_t *)&Lig_app_tab$$Limit; appfn_ptr++)
{
(*appfn_ptr)();
}
while (1) {
lastime = SysTickCnt10Ms;
}
}
map:
Lig_app_tab$$Base 0x1a001230 Number 0 main.o(Lig_app_tab)
__lig_app_app1 0x1a001230 Data 4 main.o(Lig_app_tab)
__lig_app_app2 0x1a001234 Data 4 main.o(Lig_app_tab)
__lig_app_app3 0x1a001238 Data 4 main.o(Lig_app_tab)
Lig_app_tab$$Limit 0x1a00123c Number 0 main.o(Lig_app_tab)
打印数据:
app1
app2
app3
注意:从map中可以看出Lig_app_tab$$Base
的位置和__lig_app_app1
相同,是一个段的起始,而Lig_app_tab$$Limit
和__lig_app_app3
不同,是一个段的结束;
这种$$Base
,$$Limit
用法好像不能对.xxx
的段使用,编译不过去,不知道为什么,得找找相关文档看看