美文网首页
RockPI 4A Linux内核模块

RockPI 4A Linux内核模块

作者: 小田BSP | 来源:发表于2021-03-11 20:48 被阅读0次

    Linux内核模块除代码实现部分外还需关注:模块定义、链接位置、模块加载和模块优先级。

    一、模块定义

    Linux内核使用xx_initcall_xx(fn)的宏来定义内核模块。

    宏定义文件:include/linux/init.h,定义如下:

    /*
     * Early initcalls run before initializing SMP.
     *
     * Only for built-in code, not modules.
     */
    #define early_initcall(fn)      __define_initcall(fn, early)
    
    /*
     * A "pure" initcall has no dependencies on anything else, and purely
     * initializes variables that couldn't be statically initialized.
     *
     * This only exists for built-in code, not for modules.
     * Keep main.c:initcall_level_names[] in sync.
     */
    #define pure_initcall(fn)       __define_initcall(fn, 0)
    
    #define core_initcall(fn)       __define_initcall(fn, 1)
    #define core_initcall_sync(fn)      __define_initcall(fn, 1s)
    #define postcore_initcall(fn)       __define_initcall(fn, 2)
    #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
    #define arch_initcall(fn)       __define_initcall(fn, 3)
    #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
    #define subsys_initcall(fn)     __define_initcall(fn, 4)
    #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
    #define fs_initcall(fn)         __define_initcall(fn, 5)
    #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
    #define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
    #define device_initcall(fn)     __define_initcall(fn, 6)
    #define device_initcall_sync(fn)    __define_initcall(fn, 6s)
    #define late_initcall(fn)       __define_initcall(fn, 7)
    #define late_initcall_sync(fn)      __define_initcall(fn, 7s)
    

    其中:early_initcall(fn)只针对内核的核心代码,不能描述模块。

    从上面代码可以看出,每个宏的实现都是__define_initcall(),其定义如下:

    #define __define_initcall(fn, id) \
        static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" #id ".init"))) = fn; \
        LTO_REFERENCE_INITCALL(__initcall_##fn##id)
        
    typedef int (*initcall_t)(void);
    

    对于上面定义,需要关注以下几点:

    1、initcall_t:是一个函数指针类型,定义__initcall_##fn##id,指向fn

    2、__used:在文件include/linux/compiler-gcc.h中定义为:# define __used __attribute__((__used__)),通知编译器在目标文件中保留一个静态函数,即使该函数未被使用。

    3、__attribute__((__section__(".initcall" #id ".init"))):定义的函数指针位于.initcall*.init段中。

    二、链接位置

    在文件include/asm-generic/vmlinux.lds.h中,定义了宏INIT_CALLS,定义如下:

    #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) = .;
    

    在文件arch/arm64/kernel/vmlinux.lds.S 中,设置了宏INIT_CALLS.init.data段中的分布位置,内容如下:

        .init.data : {
            INIT_DATA
            INIT_SETUP(16)
            INIT_CALLS
            CON_INITCALL
            SECURITY_INITCALL
            INIT_RAM_FS
        }
    

    在Linux内核编译链接后,会生成文件arch/arm64/kernel/vmlinux.lds。在该文件中将展开宏INIT_CALLS,并分配到.init.data段中,内容如下:

     .init.data : {
    ...
      __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
    ...
     }
    

    注:在__initcall_start__initcall_end之间是按照从0到7的顺序进行排列,对应pure_initcall(fn)late_initcall_sync(fn)

    三、模块加载

    Linux内核模块可以直接编译到内核映像,在内核启动时加载;也可以在系统启动后,通过insmod命令加载到内核。

    Linux内核启动时,kernel_init线程会实现静态编译的内核模块加载。

    1、程序调用流程

    ## kernel/init/main.c
    start_kernel()->
        rest_init()->
            kernel_thread(kernel_init, NULL, CLONE_FS) ## 1.创建内核线程
    
    kernel_init()->
        kernel_init_freeable()->
            do_basic_setup()->
                do_initcalls()->      ## 2.模块加载过程
                    do_initcall_level()->
                        do_one_initcall()
    

    2、do_initcalls()函数

    函数功能:按顺序扫描.init.data段中的每个等级,即:从__initcall0_start__initcall_end

    ## __initdata中的定义在
    static initcall_t *initcall_levels[] __initdata = {
        __initcall0_start, 
        __initcall1_start,
        __initcall2_start,
        __initcall3_start,
        __initcall4_start,
        __initcall5_start,
        __initcall6_start,
        __initcall7_start,
        __initcall_end,
    };
    
    static void __init do_initcalls(void)
    {
        int level;
    
        for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
            do_initcall_level(level);
    }
    

    3、do_initcall_level()函数

    函数功能:在同一个等级中,按顺序扫描.initcall*.init.initcall*s.init

    static void __init do_initcall_level(int level)
    {
        ...
        for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
            do_one_initcall(*fn);
    }
    

    4、do_one_initcall()函数

    函数功能:调用每个定义的模块函数。

    int __init_or_module do_one_initcall(initcall_t fn)
    {
        ...
        if (initcall_debug)
            ret = do_one_initcall_debug(fn);
        else
            ret = fn(); ##调用某个定义的initcall函数
        ...
        return ret;
    }
    

    注:每个fn()对应第一部分模块定义中的fn,例:device_initcall(fn)

    四、模块优先级

    从上面函数的执行流程可以看出内核模块加载优先级如下:

    early_initcall(fn)               ## 优先级最高,后续优先级依次降低
    pure_initcall(fn)
    core_initcall(fn)
    core_initcall_sync(fn)
    postcore_initcall(fn)
    postcore_initcall_sync(fn)
    arch_initcall(fn)
    arch_initcall_sync(fn)
    subsys_initcall(fn)
    subsys_initcall_sync(fn)
    fs_initcall(fn)
    fs_initcall_sync(fn)
    rootfs_initcall(fn)
    device_initcall(fn)
    device_initcall_sync(fn)
    late_initcall(fn)
    late_initcall_sync(fn)          ## 优先级最低
    

    Linux内核模块的优先级决定了模块的加载顺序,在驱动开发时,需要关注有启动顺序要求的模块定义。

    注:下一篇会通过一个例子对上面部分进行解释。

    相关文章

      网友评论

          本文标题:RockPI 4A Linux内核模块

          本文链接:https://www.haomeiwen.com/subject/vdboqltx.html