内核模块 静态加载

静态加载模块分析

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内核编程