module_init的调用顺序

本文详细探讨了Linux内核中module_init接口的调用顺序,从宏定义到函数组装,再到链接脚本vmlinux.lds.S中的存储空间组织。在内核启动过程中,init进程通过do_one_initcall逐个执行初始化函数,实现模块的注册和启动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

module_init()这个接口应该是驱动开发人员常用的一个接口了,在内核源码里也可以看到无数。
下午详细跟了一下内核代码,顺便分享一下,也算是自己做个记录。

源代码:MTK ALPS 4.4

首先是宏module_init的原始定义:

#define module_init(x) __initcall(x);

定义成了__initcall(),再看__initcall():

#define __initcall(fn) device_initcall(fn)

定义成了device__initcall(),再看device__initcall():

#define device_initcall(fn) __define_initcall("6",fn)

定义成了__define_initcall(“6”,fn),再看__define_initcall(“6”,fn):

#define __define_initcall(level,fn) \
    static initcall_t __initcall_##fn __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

可以看到module_init()宏最终把传进来的函数fn组装成了__initcall_fn,并且把函数fn定义到代码段.initcall6.init里面。

找到含有INIT_CALLS字符的文件vmlinux.lds.h:

#define INIT_CALLS_LEVEL(level)                     \
        VMLINUX_SYMBOL(__initcall##level##_start) = .;      \
        *(.initcall##level##.init)              \
        *(.initcall##level##s.init)             \

#define INIT_CALLS                          \
        VMLINUX_SYMBOL(__initcall_start) = .;           \
        *(.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)                 \
        VMLINUX_SYMBOL(__initcall_end) = .;

可以看到.initcall6.init是在这里定义的。打开vmlinux.lds.S,可以看到INITCALLS:

    .init.data : {
#ifndef CONFIG_XIP_KERNEL
        INIT_DATA
#endif
        INIT_SETUP(16)
        INIT_CALLS
        CON_INITCALL
        SECURITY_INITCALL
        INIT_RAM_FS
    }

vmlinux.lds.S是编译内核时使用的链接脚本,从这里可以看出,INIT_CALLS所定义的所有代码均被放到了.init.data这个节里面。即,我们使用module_init()来初始化模块注册函数fn,这个fn将会被放到.init.data节里面,这里就是代码在存储空间的组织位置。再回过头来看看其他init的定义:

#define __define_initcall(level,fn)   static initcall_t __initcall_##fn __attribute_used__   __attribute__((__section__(".initcall" level ".init"))) = fn
#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define late_initcall(fn) __define_initcall("7",fn)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn)   static exitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn)   static initcall_t __initcall_##fn   __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

可以看到,其他的init的也是通过这样的方法存放到存储空间的。内核在起动的时候,从存储空间里面按顺序获取代码段,并且执行。接下来我们来详细看看这块代码是怎么样跑起来的。

该部分的工作是在内核的init进程当中完成的,看函数kern_init():

static int __init kernel_init(void * unused)
{
    /*
     * Wait until kthreadd is all set-up.
     */
    wait_for_completion(&kthreadd_done);

    /* Now the scheduler is fully set up and can do blocking allocations */
    gfp_allowed_mask = __GFP_BITS_MASK;

    /*
     * init can allocate pages on any node
     */
    set_mems_allowed(node_states[N_HIGH_MEMORY]);
    /*
     * init can run on any cpu.
     */
    set_cpus_allowed_ptr(current, cpu_all_mask);

    cad_pid = task_pid(current);

    smp_prepare_cpus(setup_max_cpus);

    do_pre_smp_initcalls();
    lockup_detector_init();

    smp_init();
    sched_init_smp();

    **do_basic_setup();**

    /* Open the /dev/console on the rootfs, this should never fail */
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    (void) sys_dup(0);
    (void) sys_dup(0);

#ifdef CONFIG_MTK_HIBERNATION
    // IPO-H, move here for console ok after hibernaton resume
    software_resume();
#endif
    /*
     * check if there is an early userspace init.  If yes, let it do all
     * the work
     */

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }

    /*
     * Ok, we have completed the initial bootup, and
     * we're essentially up and running. Get rid of the
     * initmem segments and start the user-mode stuff..
     */

    init_post();
    return 0;
}

该部分工作在函数do_basic_setup()里面完成,该函数如下:

static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    usermodehelper_init();
    shmem_init();
    driver_init();
    init_irq_proc();
    do_ctors();
    usermodehelper_enable();
    **do_initcalls();**
    random_int_secret_init();
}

再看:

static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}

可以看出,这里是对每个level进行启动,再详细看do_initcalls的实现。

static void __init do_initcall_level(int level)
{
    extern const struct kernel_param __start___param[], __stop___param[];
    initcall_t *fn;

    strcpy(static_command_line, saved_command_line);
    parse_args(initcall_level_names[level],
           static_command_line, __start___param,
           __stop___param - __start___param,
           level, level,
           repair_env_string);

    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        **do_one_initcall(*fn);**
}

do_one_initcall(*fn)这个函数会继续往下,直接调用函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值