查看init/main.c#start_kernel,不清楚静态链接的内核模块在哪里加载,也不清楚内核如何获得它们的列表。
那么,在哪里加载了静态链接的内核模块
?为了将一个模块编译为一个模块,将相应的Kconfig
设置为m
。如果设置为y
,则所有模块代码都像所有其他"内置"代码一样编译。内核源文件和生成的目标文件被添加到所有其余的"内置"文件中。内核对象文件,然后将它们链接在一起以创建vmlinux
映像。因此,当引导加载程序将这个整体映像加载到内存中以引导内核时,所有内置模块都将加载它。所需要的只是在初始化期间调用标记为module_init
的函数。
至于这是如何发生的,简短的回答是,除非模块源文件被"作为模块"编译,否则module_init
将给定的函数地址添加到函数地址数组中,然后在初始化期间调用该函数地址。
对于较长的答案,请考虑drivers/net/hamradio/mkiss.c
的mkiss
模块。
Kconfig和编译器标志
Kconfig
出现在drivers/net/hamradio/Kconfig
:
config MKISS
tristate "Serial port KISS driver"
depends on AX25 && TTY
select CRC16
help
...
To compile this driver as a module, choose M here: the module
will be called mkiss.
选择结果是.config
文件中的CONFIG_MKISS=y
或CONFIG_MKISS=m
行。然后在drivers/net/hamradio/Makefile
:
obj-$(CONFIG_MKISS) += mkiss.o
因此,如果"builtin",则mkiss.o
被添加到目标obj-y
的列表中。如果"模块"它被添加到obj-m
。经过一些路径处理后,obj-m
变成scripts/Makefile.lib
中的real-obj-m
,然后用于确定编译器标志:
part-of-module = $(if $(filter $(basename $@).o, $(real-obj-m)),y)
modkern_cflags =
$(if $(part-of-module),
$(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE),
$(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags))
c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)
-include $(srctree)/include/linux/compiler_types.h
$(_c_flags) $(modkern_cflags)
$(basename_flags) $(modname_flags)
这里的要点是,我们得到$(KBUILD_CFLAGS_MODULE)
模块,和$(KBUILD_CFLAGS_KERNEL)
的内置。这些在顶级Makefile
:
KBUILD_CFLAGS_KERNEL :=
KBUILD_CFLAGS_MODULE := -DMODULE
内置函数初始化函数
MODULE
预处理器宏定义的差异在include/linux/module.h
中被用来提供module_init
(和module_exit
)的替代定义:
#ifndef MODULE
/**
* 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);
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
#else /* MODULE */
...
跟踪initcall
所以,如果是内置的,module_init
就变成了__initcall
,和device_initcall
一样,就像你在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)
#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))
#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
或者如果你运行预处理器,你会得到这样的结果:
.section ".initcall6.init", "a"
__initcall__kmod_mkiss__502_979_mkiss_init_driver6:
.long mkiss_init_driver - .
.previous
为mkiss_init_driver
函数添加一个具有(相对)地址的唯一符号(对于给定函数的每次不同调用),位于.initcall6.init
节中。"6";这里表示device
的initcall
订单。是:
0: pure
1: core
2: postcore
3: arch
4: subsys
5: fs
6: device
7: late
当所有这些目标文件链接在一起时,.initcallX.init
节将被链接器合并,并且每个这样的节将是在初始化阶段调用的函数指针数组。这些数组的定义可以在include/asm-generic/vmlinux.lds.h
:
#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))
最后,您可以在init/main.c
中看到这些数组的迭代:
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 void __init do_initcalls(void)
{
...
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
...
do_initcall_level(level, command_line);
}
...
}
static void __init do_initcall_level(int level, char *command_line)
{
...
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
int __init_or_module do_one_initcall(initcall_t fn)
{
...
ret = fn();
...
return ret;
}