内核模块 静态加载
静态加载模块分析
module_init 宏定义
#define module_init(x) __initcall(x);
// include/linux/init.h
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
// 定义 CONFIG_HAVE_ARCH_PREL32_RELOCATIONS(相对寻址)时的分支。注释后面为示例,fn = hello_init
#define ___define_initcall(fn, id, __sec) \
__ADDRESSABLE(fn) \
asm(".section \"" #__sec ".init\", \"a\" \n" \ # .section ".initcall6.init","a"
# 即将模块入口地址保存到段 .initcall6.init
"__initcall_" #fn #id ": \n" \ # __initcall_hello_init6:
# 要保存的符号,即 module_init 修饰的入口地址
".long " #fn " - . \n" \ # .long hello_init - .(每个入口符号地址占 4 byte)
# 保存 hello_init 与 __initcall_hello_init6 的相对偏移。
".previous \n"); # 必须和 .section 配套使用。用于将两者之间的代码汇编到各自定义的段中。
# 然后跳回去,将这之后的的代码汇编到.text段中,也就是自定义段之前的段。
// 不走 CONFIG_HAVE_ARCH_PREL32_RELOCATIONS 宏
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \ # 定义函数指针,并放入指定的段
__attribute__((__section__(#__sec ".init"))) = fn; # 直接将符号放入初始化段。符号的地址不变
init/main.c:本质是使用链接脚本的地址,用于执行初始化函数和内存释放
声明链接脚本中的符号。每个符号代表一个地址。
extern initcall_entry_t __initcall_start[];
extern initcall_entry_t __initcall0_start[];
extern initcall_entry_t __initcall1_start[];
extern initcall_entry_t __initcall2_start[];
extern initcall_entry_t __initcall3_start[];
extern initcall_entry_t __initcall4_start[];
extern initcall_entry_t __initcall5_start[];
extern initcall_entry_t __initcall6_start[];
extern initcall_entry_t __initcall7_start[];
extern initcall_entry_t __initcall_end[];
在系统初始化函数 do_initcalls 中调用,初始化模块
#define __initdata __section(".init.data")
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
static const char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
include/asm-generic/vmlinux.lds.h:最终在 vmlinux.lds.S 中使用
#define INIT_DATA_SECTION(initsetup_align) \
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \
INIT_DATA \
INIT_SETUP(initsetup_align) \
INIT_CALLS \
CON_INITCALL \
INIT_RAM_FS \
}
#define INIT_CALLS \
__initcall_start = .; \
KEEP(*(.initcallearly.init)) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
__initcall_end = .;
#define INIT_CALLS_LEVEL(level) \
__initcall##level##_start = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
Linux内核模块编译、加载、运行机制分析、版本控制、许可声明、内核污染、模块传参、模块签名机制、out-of-tree动态模块编译及Makefile模板编写,尽在《Linux内核编程》,详情点击:王利涛老师个人淘宝店:Linux内核编程