一、基本知识点
编译出来的程序(o so exe ko等等)都是以elf格式进行排列保存的
elf文件分析情况:https://blog.csdn.net/edonlii/article/details/8779075
二、系统连接器会自带许多section,当然也可以添加自定义的section
2.1、以下命令可以查看连接器默认的section名
ld --verbose > section.lds
2.2、如何实现自定义的section
2.2.1、使用__section__函数实现自定义
__attribute__((used, __section__("xxxx")))
自定义一个结构体
#define SECTION_START(_type,_name) \
static const struct mydyh_desc __mydyh_desc_##_type \
__attribute__((used, __section__(".dyh.info.init"))) = { \
.nr = MY_DYH_##_type, \
.name = _name,
#define SECTION_END \
};
2.2.2、在链接脚本中添加section名和起始结束位置
2.2.3、怎样才能获取到section里面的数据?
通过nm命令查看可执行程序的符号表,发现存在__dyh_info_begin,__dyh_info_end符号
nm exe_name | grep __dyh_info
因此,可以通过extern方式,获取符号地址
extern struct mydyh_desc __dyh_info_begin[], __dyh_info_end[];
2.3、编译代码
gcc section.c -T section.lds -o section
demo程序:https://github.com/dyh-git/section_custom
三、有什么用途?
假如在main函数中需要实现许多初始化功能,每当添加一个初始化功能,就需要把初始化函数添加到main函数中,然后重新编译。往往初始化函数的入参和返回值都一致。
如果使用自定义section模式,不需要把初始化函数添加到main函数中,遍历__dyh_info_begin到 __dyh_info_end地址就可以实现,详见demo程序。内核在module_init加载驱动时就是采用的这种方式。
四、module_init函数分析
4.1、module_init调用流程
#define module_init(x) __initcall(x);
#define __initcall(fn) __define_initcall("1", fn)
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
可以看到,最终会调用__section__函数创建.initcall的section
4.2、查看linux链接脚本vmlinux.lds可以找到上面定义的section
4.3、执行流程,do_initcall_level遍历的数组initcall_levels对应的就是section的起始位置
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
int __init_or_module do_one_initcall(initcall_t fn)
{
int ret;
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();
return ret;
}
static void __init do_initcall_level(int level)
{
initcall_t *fn;
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
参考书籍:《程序员的自我修养—链接、装载与库》