What is the difference between module_init and init_module in a Linux kernel module?
If you look at the definition of the new functions:
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
You'll see it ensures that the right boilerplate is included so these special functions can be correctly treated by the compiler. It's what the internal API of Linux does, it evolves if there are better ways of solving the problem.
module_init() vs. core_initcall() vs. early_initcall()
They determine the initialization order of built-in modules. Drivers will use device_initcall
(or module_init
; see below) most of the time. Early initialization (early_initcall
) is normally used by architecture-specific code to initialize hardware subsystems (power management, DMAs, etc.) before any real driver gets initialized.
Technical stuff for understanding below
Look at init/main.c
. After a few architecture-specific initialization done by code in arch/<arch>/boot
and arch/<arch>/kernel
, the portable start_kernel
function will be called. Eventually, in the same file, do_basic_setup
is called:
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
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();
}
which ends with a call to do_initcalls
:
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
"early",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
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);
}
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
You can see the names above with their associated index: early
is 0, core
is 1, etc. Each of those __initcall*_start
entries point to an array of function pointers which get called one after the other. Those function pointers are the actual modules and built-in initialization functions, the ones you specify with module_init
, early_initcall
, etc.
What determines which function pointer gets into which __initcall*_start
array? The linker does this, using hints from the module_init
and *_initcall
macros. Those macros, for built-in modules, assign the function pointers to a specific ELF section.
Example with module_init
Considering a built-in module (configured with y
in .config
), module_init
simply expands like this (include/linux/init.h
):
#define module_init(x) __initcall(x);
and then we follow this:
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
So, now, module_init(my_func)
means __define_initcall(my_func, 6)
. This is _define_initcall
:
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
which means, so far, we have:
static initcall_t __initcall_my_func6 __used
__attribute__((__section__(".initcall6.init"))) = my_func;
Wow, lots of GCC stuff, but it only means that a new symbol is created, __initcall_my_func6
, that's put in the ELF section named .initcall6.init
, and as you can see, points to the specified function (my_func
). Adding all the functions to this section eventually creates the complete array of function pointers, all stored within the .initcall6.init
ELF section.
Initialization example
Look again at this chunk:
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
Let's take level 6, which represents all the built-in modules initialized with module_init
. It starts from __initcall6_start
, its value being the address of the first function pointer registered within the .initcall6.init
section, and ends at __initcall7_start
(excluded), incrementing each time with the size of *fn
(which is an initcall_t
, which is a void*
, which is 32-bit or 64-bit depending on the architecture).
do_one_initcall
will simply call the function pointed to by the current entry.
Within a specific initialization section, what determines why an initialization function is called before another is simply the order of the files within the Makefiles since the linker will concatenate the __initcall_*
symbols one after the other in their respective ELF init. sections.
This fact is actually used in the kernel, e.g. with device drivers (drivers/Makefile
):
# GPIO must come after pinctrl as gpios may need to mux pins etc
obj-y += pinctrl/
obj-y += gpio/
tl;dr: the Linux kernel initialization mechanism is really beautiful, albeit highlight GCC-dependent.
What is the difference for linux kernel :module or built-in?
If something is compiled in the linux kernel it will be part of the binary forever. So, it wont be loaded into the kernel on a "as-needed" basis. So, for example assume you have a realtek ethernet card on your machine why do you need drivers for intel ethernet cards?
One other thing, kernel modules in general can not reference a function in the kernel unless it is exported to modules using EXPORT_SYMBOL
and its variants. So, if your driver need to reference functions from the kernel sources (which should not be the case anyways) then you have to add your driver into the kernel source tree.
Where/how does the kernel loads statically linked modules?
For compiling a module "as a module", the corresponding Kconfig
is set to m
. If set to y
, all the module code is compiled just like all the other "builtin" kernel source files, and the generated object files are added to all the rest of the "builtin" kernel object files, which are then linked together to create the vmlinux
image. Therefore, as the bootloader loads this monolithic image into memory to boot the kernel, all the builtin modules are loaded with it. All that is required is for the functions marked with module_init
to be called during initialization.
As to how that happens, the short answer is, unless the module source file is compiled "as a module", the module_init
adds the given function address to an array of function addresses that are then called during initialization.
For the long answer, consider the mkiss
module at drivers/net/hamradio/mkiss.c
.
Kconfig and Compiler Flags
The Kconfig
is found in 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.
The selection results in a CONFIG_MKISS=y
or CONFIG_MKISS=m
line in the .config
file. This is then used in the drivers/net/hamradio/Makefile
:
obj-$(CONFIG_MKISS) += mkiss.o
So, if "builtin", mkiss.o
is added to the list of targets obj-y
. And if "module", it is added to obj-m
. After some path processing, obj-m
becomes real-obj-m
in scripts/Makefile.lib
, which is then used to determine the compiler flags:
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)
The takeaway here is that we get $(KBUILD_CFLAGS_MODULE)
for modules, and $(KBUILD_CFLAGS_KERNEL)
for builtin. These are defined in the top-level Makefile
:
KBUILD_CFLAGS_KERNEL :=
KBUILD_CFLAGS_MODULE := -DMODULE
Initialization Function for Builtins
This difference in MODULE
preprocessor macro definition is utilized in include/linux/module.h
to provide alternate definitions to module_init
(and 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 */
...
Tracking initcall
So, if builtin, module_init
becomes __initcall
, which is the same as device_initcall
, as you can see in include/linux/init.h
:
#define __initcall(fn) device_initcall(fn)
If you can follow all the macros:
#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
or if you run the preprocessor, you arrive at something like this:
.section ".initcall6.init", "a"
__initcall__kmod_mkiss__502_979_mkiss_init_driver6:
.long mkiss_init_driver - .
.previous
which is adding a unique (for every distinct invocation of a given function) symbol with an (relative) address to the mkiss_init_driver
function, in a section called .initcall6.init
. The "6" here represents the initcall
order for device
. It goes:
0: pure
1: core
2: postcore
3: arch
4: subsys
5: fs
6: device
7: late
When all these object files are linked together, the .initcallX.init
sections will be merged by the linker, and each such section will be an array of function pointers to be called in that stage during initialization. The definitions of these arrays can be found in 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))
Finally, you can see the iteration over these arrays in 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;
}
Creating simple kernel module
Is this the source tree for the running kernel? If not, it should fail.
Install the kernel-devel (or similarly named) package for your distribution, it adds enough machinery to build modules against it.
kernel module in not loaded (but insmod returns 0)
readelf -a
shows that the relocation entry for your init function is different than in the native module case:
xt_mark.ko
Relocation section '.rel.gnu.linkonce.this_module' at offset 0x958 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
000000bc 00001502 R_MIPS_32 00000000 init_module
00000130 00001402 R_MIPS_32 00000000 cleanup_module
khelloworld.ko
Relocation section '.rel.gnu.linkonce.this_module' at offset 0xafc contains 2 entries:
Offset Info Type Sym.Value Sym. Name
000000ac 00002502 R_MIPS_32 00000000 init_module
0000010c 00002402 R_MIPS_32 00000000 cleanup_module
Note how for the native modules, the init_module
pointer is in offset 0xbc of the module
struct, whereas in your module it is in offset 0xac. As a result, the loader does not find your init function and does not call it.
As explained here, this is likely the result of a kernel configuration difference between your build environment and the native build environment. It's very likely that CONFIG_UNUSED_SYMBOLS
is the culprit (see the module
definition here).
Alternatively, you can (at your own risk!) apply a binary patch to your resulting module to change the 0xac to 0xbc.
Related Topics
Go Http Server Testing Ab VS Wrk So Much Difference in Result
Creating a Raw Printer Queue in Cups (Host) and Adding Them Through Cups (Client)
Which Stack Is Used by Interrupt Handler - Linux
Changing the PHPmyadmin Default Url
Calculating Rounded Percentage in Shell Script Without Using "Bc"
Extract Date from a File Name in Unix Using Shell Scripting
How to Make a Bash Script Portable Between Linux and Freebsd
Where Are Gdb Symbols Coming From
Linux Udp Max Size of Receive Buffer
Redirecting Man Page Output to File Results in Double Letters in Words
How to Run Linux Docker Images on Windows Server 2016
How to Run Elasticsearch 2.1.1 as Root User in Linux MAChine
Bash Scripting - Read Single Keystroke Including Special Keys Enter and Space