module_init源码分析

简介: module_init源码分析

module_init源码分析


源码分析

       本章节我们一块来看一下module_init(x)这个函数,先分析一下它的源码,再梳理一下它的调用流程,参考代码:linux/include/linux/module.h。

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 *
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)  __initcall(x);

注释:如果是常驻的driver,那么会在do_initcalls的时候调到module_init添加的函数。do_initcalls是如何调用过来的我们后面再讲,继续看__initcall的定义(linux/include/linux/init.h):

#define __initcall(fn) device_initcall(fn)

此处继续看device_initcall(fn),还是在linux/include/linux/init.h中:

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

此处继续看__define_initcall(fn, id),还是在linux/include/linux/init.h中:

#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

此处继续看__define_initcall(fn, early),还是在linux/include/linux/init.h中:

#define ___define_initcall(fn, id, __sec)     \
  __unique_initcall(fn, id, __sec, __initcall_id(fn))

继续看___define_initcall(fn, id, .initcall##id),还是在linux/include/linux/init.h中:

#define __unique_initcall(fn, id, __sec, __iid)     \
  ____define_initcall(fn,         \
    __initcall_stub(fn, __iid, id),     \
    __initcall_name(initcall, __iid, id),   \
    __initcall_section(__sec, __iid))

在___define_initcall中,____define_initcall的实现:

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ____define_initcall(fn, __stub, __name, __sec)    \
  __define_initcall_stub(__stub, fn)      \
  asm(".section \"" __sec "\", \"a\"    \n" \
      __stringify(__name) ":      \n" \
      ".long  " __stringify(__stub) " - . \n" \
      ".previous          \n"); \
  static_assert(__same_type(initcall_t, &fn));
#else
#define ____define_initcall(fn, __unused, __name, __sec)  \
  static initcall_t __name __used       \
    __attribute__((__section__(__sec))) = fn;
#endif

从上述代码可知在定义了CONFIG_HAVE_ARCH_PREL32_RELOCATIONS的时候使用____define_initcall第一种实现方式,如果没有CONFIG_HAVE_ARCH_PREL32_RELOCATIONS定义,将采用第二种实现方式,由于未定义CONFIG_HAVE_ARCH_PREL32_RELOCATIONS我们暂时看第二种实现方式。

       GNU编译工具链支持用户自定义section,所以我们阅读Linux源码时,会发现大量使用如下一类用法:

__attribute__((__section__("section-name"))) 

__attribute__用来指定变量或结构位域的特殊属性,其后的双括弧中的内容是属性说明,它的语法格式为:__attribute__ ((attribute-list))。它有位置的约束,通常放于声明的尾部且“ ;” 之前。

       这里的attribute-list为__section__(“.initcall6.init”)。通常,编译器将生成的代码存放在.text段中。但有时可能需要其他的段,或者需要将某些函数、变量存放在特殊的段中,section属性就是用来指定将一个函数、变量存放在特定的段中。

 所以这里的意思就是:定义一个名为 __initcall_XXX_init6 的函数指针变量,并初始化为 XXX_init(指向XXX_init);并且该函数指针变量存放于 .initcall6.init 代码段中。

       通过查看链接脚本( arch/$(ARCH)/kernel/vmlinux.lds.S)来了解 .initcall6.init 段。

 可以看到,.init段中包含 INIT_CALLS,它定义在include/asm-generic/vmlinux.lds.h。INIT_CALLS 展开后可得:

#define INIT_CALLS_LEVEL(level)           \
    VMLINUX_SYMBOL(__initcall##level##_start) = .;    \
    KEEP(*(.initcall##level##.init))      \
    KEEP(*(.initcall##level##s.init))     \
#define INIT_CALLS              \
    VMLINUX_SYMBOL(__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)         \
    VMLINUX_SYMBOL(__initcall_end) = .;

综上所述,module_init的源码实现可以简化为:

#define module_init(x)  __initcall(x);
|
--> #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)
            |
            --> #define ___define_initcall(fn, id, __sec)     \
                  __unique_initcall(fn, id, __sec, __initcall_id(fn))
                |
                --> #define __unique_initcall(fn, id, __sec, __iid)     \
                      ____define_initcall(fn,         \
                        __initcall_stub(fn, __iid, id),     \
                        __initcall_name(initcall, __iid, id),   \
                        __initcall_section(__sec, __iid))                 

调用流程

       在#define module_init(x)    __initcall(x)的注释中说到do_initcalls调用module_init。实际上完整的调用流程如下:

asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
|
--> void __init __weak arch_call_rest_init(void)
    |
    --> noinline void __ref rest_init(void)
        |
        --> static int __ref kernel_init(void *unused)
            |
            --> static noinline void __init kernel_init_freeable(void)
                |
                --> static void __init do_basic_setup(void)
                    |
                    --> static void __init do_initcalls(void)

此处就不熟练调用流程的源码了,后续在系统启动流程再详细讲解。

相关文章
|
7月前
|
机器学习/深度学习 数据可视化 Python
No module named 'tensorboardX' 如何解决
【5月更文挑战第27天】No module named 'tensorboardX' 如何解决
471 0
|
JavaScript Cloud Native Go
Error: Cannot find module ‘webpack/bin/config-yargs‘ at Function.Module._resolveFilename (intern
Error: Cannot find module ‘webpack/bin/config-yargs‘ at Function.Module._resolveFilename (intern
84 0
|
Python
Python3 多线程问题:ModuleNotFoundError: No module named 'thread',原因及解决办法。
Python3 多线程问题:ModuleNotFoundError: No module named 'thread',原因及解决办法。
1152 0
[✔️]lua中的module函数
[✔️]lua中的module函数
237 0
AttributeError: cannot assign module before Module.__init__() call 怎么解决?
这个错误通常是由于在__init__方法之外对模块中的某些成员进行了初始化或赋值操作,导致模块还没有被完全初始化就已经被调用了。为了解决这个问题,你需要将所有的初始化或赋值操作移到__init__方法内部。 例如,如果你有一个自定义模块MyModule,并且想要在其中定义一个类变量my_var,则应该将其放在__init__方法中:
786 0
module_exit源码分析
module_exit源码分析
|
Linux
原因及解决办法:Failed to load module “canberra-gtk-module“
原因及解决办法:Failed to load module “canberra-gtk-module“
972 0
module_platform_driver源码分析
module_platform_driver源码分析
|
PyTorch Linux 算法框架/工具
AttributeError: cannot assign module before Module.__init__() call
AttributeError: cannot assign module before Module.__init__() call
|
JavaScript Java C语言
Modules 和 Packages 区别|学习笔记
快速学习 Modules 和 Packages 区别