作用:
使用__attribute__((section()))构建初始化函数表后,由模块告知main:“我要初始化“,添加新模块再也不需要在main代码中显式调用模块初始化接口。以此实现main与模块之间的隔离,main不再关心有什么模块,模块的删减也不需要修改main。
"section"关键字会将被修饰的变量或函数编译到特定的一块位置,不是物理存储器上的特定位置,而是在可执行文件的特定段内。此处,可参考__attribute__之section详解
原理:
1. 模块通过__attribute__((section("name")))的实现,在编译时把初始化的接口放到name数据段中。
2. main在执行初始化时并不需要知道有什么模块需要初始化,只需要把name数据段中的所有初始化接口执行一遍即可。
练习代码:
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
typedef void (*init_call)(void);
/*
* These two variables are defined in link script.
*/
extern init_call _init_start;
extern init_call _init_end;
#define _init __attribute__((unused, section(".myinit")))
#define DECLARE_INIT(func) init_call _fn_##func _init = func
static void A_init(void)
{
write(1, "A_init\n", sizeof("A_init\n"));
}
DECLARE_INIT(A_init);
static void B_init(void)
{
printf("B_init\n");
}
DECLARE_INIT(B_init);
static void C_init(void)
{
printf("C_init\n");
}
DECLARE_INIT(C_init);
/*
* DECLARE_INIT like below:
* static init_call _fn_A_init __attribute__((unused, section(".myinit"))) = A_init;
* static init_call _fn_C_init __attribute__((unused, section(".myinit"))) = C_init;
* static init_call _fn_B_init __attribute__((unused, section(".myinit"))) = B_init;
*/
void do_initcalls(void)
{
init_call *init_ptr = &_init_start;
for (; init_ptr < &_init_end; init_ptr++) {
printf("init address: %p\n", init_ptr);
(*init_ptr)();
}
}
int main(void)
{
do_initcalls();
return 0;
}
在代码中,我们做了3件事:
使用__attribute__((section()))定义了宏:DECLARE_INIT,此宏把函数放置到初始化函数表
使用DELCARE_INIT的宏,声明了3个模块初始化函数:A_init/B_init/C_init
在main中通过调用do_initcalls函数,依次调用编译时构建的初始化函数。其中,“_init_start”和“_init_end”的变量在链接脚本中定义。
其他细节请参考原文:廖威雄: 利用__attribute__((section()))构建初始化函数表与Linux内核init的实现