Insert linux kernel module statically
Sure, you just need to do a bit of hacking to move the external module into the kernel source tree, tweak the Makefiles/Kconfig a bit so that the code is built-in, and then build your kernel image. For example, let's say you move the module source into drivers/blah
. Then you should add a line to then end of drivers/Makefile
like
obj-y += blah/
and you should make sure that drivers/blah/Makefile
is set up to build your module in, with something like
obj-y += mymodule.o
mymodule-objs := src.o other.o
and so on, where your Makefile is set up however it needs to be to build the particular module you're working on.
Note: You have to use the output file name for mymodule-objs and not the input filename!
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;
}
How to statically build kernel module with buildroot?
Supposing you have a driver in driver.c
, and a buildroot package called STATICDRVR
, you can use the following Config.in and STATICDRVR.mk files to add a static module to be built when the kernel is built:
Config.in
config BR2_PACKAGE_STATICDRVR
bool "Build & link static driver?"
help
This is a driver that blah blah greatness whatever
STATICDRVR.mk
STATICDRVR_VERSION = master
STATICDRVR_SITE = /location/to/STATICDRVR_containing_src
STATICDRVR_SITE_METHOD = local
STATICDRVR_MODULE_SUBDIRS = src
STATICDRVR_INSTALL_TARGET = YES
STATICDRVR_LICENSE = GPLv2
STATICDRVR_LICENSE_FILES = COPYING
STATICDRVR_NAME = STATICDRVR
STATICDRVR_DEPENDENCIES = linux
define STATICDRVR_BUILD_CMDS
#make sure that obj-y += STATICDRVR/ is only in the build makefile once
sed -i '/obj-y += STATICDRVR/d' $(BUILD_DIR)/linux-$(LINUX_VERSION)/drivers/Makefile
echo "obj-y += STATICDRVR/" >> $(BUILD_DIR)/linux-$(LINUX_VERSION)/drivers/Makefile
rm -rf $(BUILD_DIR)/linux-$(LINUX_VERSION)/drivers/STATICDRVR
cp -r $(@D)/src $(BUILD_DIR)/linux-$(LINUX_VERSION)/drivers/STATICDRVR
echo "obj-y += driver.o" > $(BUILD_DIR)/linux-$(LINUX_VERSION)/drivers/STATICDRVR/Makefile
endef
define STATICDRVR_INSTALL_STAGING_CMDS
endef
define STATICDRVR_INSTALL_TARGET_CMDS
endef
endif
define STATICDRVR_DEVICES
endef
define STATICDRVR_PERMISSIONS
endef
define STATICDRVR_USERS
endef
$(eval $(kernel-module))
$(eval $(generic-package))
Can't get multi files kernel module to work
Info which lead to solution was provided by @Tsyvarev. Original info may be found in comments to first post.
obj-m := multiFileKo.o
defines the name of module. It also uses multiFileKo.c as a source file by default. But this principle works for single-source-file modules only.
In case multiple source files are used to create a module, obj-m := multiFileKo.o
shall define module name, but not the sources. ALL object files (referencing actual sources) then shall be listed in multiFileKo-objs :=
list. Source files are not allowed to have the same name which module have.
From my experiments and make utility manual I can also say that it seems that listing sources in multiFileKo-y :=
also works. Maybe -obj
is still working due to compatibility, since make documents now advise to use -y
list.
To sum up, correct approach would be:
obj-m := multiFileKo.o
multiFileKo-y := multiFileKo_main.o helpers.o
Source files:
multiFileKo_main.c // contains init and exit functions
helpers.c
Output would be stored in multiFileKo.ko
Related Topics
On Linux, How to Make Sure to Unlock a Mutex Which Was Locked in a Thread That Dies/Terminates
Putting Two Consecutive Lines into One Line with Perl/Awk
How to Allocate a New Tls Area with Clone System Call
Linux X86-64 Assembly and Printf
How to Rename Files in Bash to Increase Number in Name
Differencebetween Module_Init and Init_Module in a Linux Kernel Module
Trying to Find All the Kernel Modules Needed for My MAChine Using Shell Script
Bash Script to Get All Ip Addresses
How to Check If Two Paths Are Equal in Bash
Running Shell Script Using .Env File
Error: Ld.So: Object 'Libgtk3-Nocsd.So.0' from Ld_Preload Cannot Be Preloaded
How to Get in Script Whether Valgrind Found Memory Leaks
How to Use Awk for a Compressed File
Can't Install Freetds via Yum Package Manager
Set Static Ip If Not Obtained from Dhcp (Script)
Get a Nanosecond-Precise Atime, Mtime, Ctime Fields for File (Stat)