This article introduces the loading logic of Linux drivers, discussing in detail the implementation differences of the module_init macro between built-in drivers and loadable modules, as well as how the linker places initialization code in specific sections through C compiler attributes and macros. Additionally, it summarizes the priority division and invocation stages of the built-in driver initialization function pointer table, and explores the application of the PREL32 relative offset relocation method in some architectures.
Drivers generally need to call the module_init function to register the driver with the kernel.
__initand__exitattribute
The linker (ld on Linux systems) is part of binutils and is responsible for placing symbols (data, code, etc.) into the appropriate sections of the generated binary file so that they can be processed by the loader during program execution.
These sections in the binary file can be customized, their default locations changed, or even additional sections added by providing a linker script [called a linker definition file (LDF) or linker definition script (LDS)]. To achieve this, simply inform the linker of the symbol’s location through compiler directives; the GNU C compiler provides some attributes for this purpose. .
The Linux kernel provides a custom LDS file, which is located atarch/<arch> /kernel/vmlinux.lds.SFor symbols to be placed in the special sections mapped by the kernel LDS file, use__initand__exitto mark them
__initThe keyword tells the linker to place the code in a special section of the kernel object file. This section is known to the kernel in advance and is freed after module loading and execution of the init function. This applies only to built-in drivers, not to loadable modules. The kernel runs the driver’s initialization function for the first time during boot.
The more important parts are.modeinfoand.init.text, the former stores information about the module, and the latter stores code prefixed with __the init macro
In summary,__initand__exitare Linux directives (actually macros) that use C compiler attributes to specify the placement of symbols. These directives instruct the compiler to place the code prefixed with them into the.init.textand.exit.textsections respectively, although the kernel can access different object sections.
#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 */
/* * In most cases loadable modules do not need custom * initcall levels. There are still some valid cases where * a driver may be needed early if built in, and does not * matter when built as a loadable module. Like bus * snooping debug drivers. */ #define early_initcall(fn) module_init(fn) #define core_initcall(fn) module_init(fn) #define core_initcall_sync(fn) module_init(fn) #define postcore_initcall(fn) module_init(fn) #define postcore_initcall_sync(fn) module_init(fn) #define arch_initcall(fn) module_init(fn) #define subsys_initcall(fn) module_init(fn) #define subsys_initcall_sync(fn) module_init(fn) #define fs_initcall(fn) module_init(fn) #define fs_initcall_sync(fn) module_init(fn) #define rootfs_initcall(fn) module_init(fn) #define device_initcall(fn) module_init(fn) #define device_initcall_sync(fn) module_init(fn) #define late_initcall(fn) module_init(fn) #define late_initcall_sync(fn) module_init(fn)
#define console_initcall(fn) module_init(fn)
/* Each module must use one module_init(). */ #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */ #define module_exit(exitfn) \ static inline exitcall_t __maybe_unused __exittest(void) \ { return exitfn; } \ void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));
#endif
If a driver is compiled into the Linux kernel, module_exit is meaningless because a statically compiled driver cannot be unloaded.
MODULE macro
Scenario
Meaning
MODULEStatus
Module (.ko)
Usedobj-m += xxx.oCompiled as a loadable module
✅ Defined
Built-in driver
Usedobj-y += xxx.oCompiled into the kernel image
❌ Not defined
In the top-level Makefile of the Linux source code
__attribute__((__section__(".initcall6.init")))Tell the compiler: Place this pointer variable into the.initcall6.initsection.
This section is a specially reserved “initialization function pointer table” in the kernel, corresponding to the call order of different stages in the initialization phase.
Taking fn as my_driver as an example: module_init ultimately expands to
1
__initcall_my_driver_init6 = my_driver_init;
That is, a pointer tomy_driver_init()is stored in this section. One 8-byte.
If you want to use other priorities, you can replace module_init with fs_initcall, etc.
Macro
Section Name
Call Stage
early_initcall(fn)
.initcall0.init
Earliest
core_initcall(fn)
.initcall1.init
Core Subsystem Initialization
postcore_initcall(fn)
.initcall2.init
Initialization After Core Completion
arch_initcall(fn)
.initcall3.init
Architecture-related parts
subsys_initcall(fn)
.initcall4.init
Subsystems (e.g., driver core)
fs_initcall(fn)
.initcall5.init
Filesystem initialization
device_initcall(fn)
.initcall6.init
Device driver initialization (default)
late_initcall(fn)
.initcall7.init
Latest stage initialization
PREL32
CONFIG_HAVE_ARCH_PREL32_RELOCATIONSis aKconfig option, present in some architectures (e.g., ARM64, RISC-V). Its meaning is:
This architecture supports usingPREL32(PC-relative 32-bit relocation)type of relocation, meaning: the address stored in the.initcallsection is aPC-relative32-bit offset, not an absolute address.
On systems without this option (e.g., traditional x86),.initcallX.initthe section directly stores function pointers:
Here.long driver_init - .means: store the difference (a 32-bit signed offset) of ‘function address minus current address’ into.initcalltable.
At runtime, the kernel retrieves the value like this:
1
real_addr = (u64)&entry + (s32)*entry;
Thus, the kernel can locate the functionwithout needing relocation fixupsunder the condition. This is especially important for**position-independent kernels (KASLR, RELATIVE linking)**because the absolute address of the function is determined only at boot time.
Advantages:
Space saving: Each entry occupies only 4 bytes instead of 8 bytes (saving half on 64-bit).
Support Kernel Address Space Layout Randomization (KASLR): No need to perform relocation fixups on all pointer segments at boot time.
Faster boot: Eliminates a large number of relocation fixup operations.
asmlinkage __visible void __init __no_sanitize_address start_kernel(void) { //Omitted before /* Do the rest non-__init'ed, we're now alive */ arch_call_rest_init();
noinline void __ref rest_init(void) { structtask_struct *tsk; int pid;
rcu_scheduler_starting(); /* * We need to spawn init first so that it obtains pid 1, however * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */ pid = kernel_thread(kernel_init, NULL, CLONE_FS); // Omitted after }
staticint __ref kernel_init(void *unused) { int ret;
kernel_init_freeable(); // Omitted after
}
// static noinline void __init kernel_init_freeable(void) { // Omitted before
staticvoid __init do_initcalls(void) { int level; size_t len = strlen(saved_command_line) + 1; char *command_line;
command_line = kzalloc(len, GFP_KERNEL); if (!command_line) panic("%s: Failed to allocate %zu bytes\n", __func__, len); // This for loop can be seen to initialize starting from 0, so the smaller the number, the higher the priority // Additionally, for the same ID, those with 's' have lower priority than those without for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) { /* Parser modifies command_line, restore it each time */ strcpy(command_line, saved_command_line); do_initcall_level(level, command_line); }
When using module_init(hello_world), hello_The world() function will be placed in the .initcall6.init section. When the kernel starts, it executes do_The initcall() function, based on the pointer array initcall_levels[6], finds __initcall6_start, which can be found in include/asm-generic/vmlinux.lds.h __The starting address of the .initcall6.init section corresponding to initcall6_start, then sequentially retrieves the function pointers from this section and executes the functions.
/* * The asmlinkage stub is aliased to a function named __se_sys_*() which * sign-extends 32-bit ints to longs whenever needed. The actual work is * done within __do_sys_*(). */ #ifndef __SYSCALL_DEFINEx #define __SYSCALL_DEFINEx(x, name, ...) \ __diag_push(); \ __diag_ignore(GCC, 8, "-Wattribute-alias", \ "Type aliasing is used to sanitize syscall arguments");\ asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \ __attribute__((alias(__stringify(__se_sys##name)))); \ ALLOW_ERROR_INJECTION(sys##name, ERRNO); \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ { \ long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\ __MAP(x,__SC_TEST,__VA_ARGS__); \ __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ return ret; \ } \ __diag_pop(); \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) #endif/* __SYSCALL_DEFINEx */
/* * In most cases loadable modules do not need custom * initcall levels. There are still some valid cases where * a driver may be needed early if built in, and does not * matter when built as a loadable module. Like bus * snooping debug drivers. */ #define early_initcall(fn) module_init(fn) #define core_initcall(fn) module_init(fn) #define core_initcall_sync(fn) module_init(fn) #define postcore_initcall(fn) module_init(fn) #define postcore_initcall_sync(fn) module_init(fn) #define arch_initcall(fn) module_init(fn) #define subsys_initcall(fn) module_init(fn) #define subsys_initcall_sync(fn) module_init(fn) #define fs_initcall(fn) module_init(fn) #define fs_initcall_sync(fn) module_init(fn) #define rootfs_initcall(fn) module_init(fn) #define device_initcall(fn) module_init(fn) #define device_initcall_sync(fn) module_init(fn) #define late_initcall(fn) module_init(fn) #define late_initcall_sync(fn) module_init(fn)
#define console_initcall(fn) module_init(fn)
/* Each module must use one module_init(). */ #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */ #define module_exit(exitfn) \ static inline exitcall_t __maybe_unused __exittest(void) \ { return exitfn; } \ void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));
#endif
Module initialization is **insmod/modprobeuniformly called by the module loader at **, and is not differentiated by kernel boot stages. Therefore:
That is to say: when the kernel loads a module, the actual entry function executed is init_module(), which is aliased to our defined initialization function mydriver_init()。
include/uapi/asm-generic/unistd.h
uapi belongs tothe Universal Kernel Interface (UAPI, User API), which are header files that user space can include. It definesthe system call numbers common to most architectures(syscall numbers) for user programs to invoke.
asmlinkage longsys_finit_module(int fd, constchar __user *uargs, int flags);
asmlinkageis acalling convention modifier, used to tell the compiler:
All parameters of the system call entry function are passed from the stack (rather than registers), because it is entered from the assembly entry (syscall trap).
init_module(void *umod, unsigned long len, const char *uargs)
Early Linux (since 1.x)
Load module from userspace memory
finit_module
finit_module(int fd, const char *uargs, int flags)
Linux 3.8 (2013)
Load module from file descriptor (fd)
SYSCALL_DEFINE3is a macro used to generate system call wrapper functions. The function name it generates issys_init_module. Here, 3 indicates 3 parameters, and SYSCALL_DEFINE supports up to 6 parameters.
/* Allocate and load the module: note that size of section 0 is always zero, and we rely on this for optional sections. */ staticintload_module(struct load_info *info, constchar __user *uargs, int flags) { structmodule *mod; long err = 0; char *after_dashes;
/* * Do the signature check (if any) first. All that * the signature check needs is info->len, it does * not need any of the section info. That can be * set up later. This will minimize the chances * of a corrupt module causing problems before * we even get to the signature check. * * The check will also adjust info->len by stripping * off the sig length at the end of the module, making * checks against info->len more correct. */ err = module_sig_check(info, flags); if (err) goto free_copy;
/* * Do basic sanity checks against the ELF header and * sections. */ err = elf_validity_check(info); if (err) { pr_err("Module has invalid ELF structures\n"); goto free_copy; }
/* * Everything checks out, so set up the section info * in the info structure. */ err = setup_load_info(info, flags); if (err) goto free_copy;
/* * Now that we know we have the correct module name, check * if it's blacklisted. */ if (blacklisted(info->name)) { err = -EPERM; pr_err("Module %s is blacklisted\n", info->name); goto free_copy; }
err = rewrite_section_headers(info, flags); if (err) goto free_copy;
/* Check module struct version now, before we try to use module. */ if (!check_modstruct_version(info, info->mod)) { err = -ENOEXEC; goto free_copy; }
/* Figure out module layout, and allocate all the memory. */ mod = layout_and_allocate(info, flags); if (IS_ERR(mod)) { err = PTR_ERR(mod); goto free_copy; }
audit_log_kern_module(mod->name);
/* Reserve our place in the list. */ err = add_unformed_module(mod); if (err) goto free_module;
/* To avoid stressing percpu allocator, do this once we're unique. */ err = percpu_modalloc(mod, info); if (err) goto unlink_mod;
/* Now module is in final location, initialize linked lists, etc. */ err = module_unload_init(mod); if (err) goto unlink_mod;
init_param_lock(mod);
/* Now we've got everything in the final locations, we can * find optional sections. */ err = find_module_sections(mod, info); if (err) goto free_unload;
err = check_module_license_and_versions(mod); if (err) goto free_unload;
/* Set up MODINFO_ATTR fields */ setup_modinfo(mod, info);
/* Fix up syms, so that st_value is a pointer to location. */ err = simplify_symbols(mod, info); if (err < 0) goto free_modinfo;
err = apply_relocations(mod, info); if (err < 0) goto free_modinfo;
err = post_relocation(mod, info); if (err < 0) goto free_modinfo;
flush_module_icache(mod);
/* Now copy in args */ mod->args = strndup_user(uargs, ~0UL >> 1); if (IS_ERR(mod->args)) { err = PTR_ERR(mod->args); goto free_arch_cleanup; }
/* * This is where the real work happens. * * Keep it uninlined to provide a reliable breakpoint target, e.g. for the gdb * helper command 'lx-symbols'. */ static noinline intdo_init_module(struct module *mod) { int ret = 0; structmod_initfree *freeinit;
freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL); if (!freeinit) { ret = -ENOMEM; goto fail; } freeinit->module_init = mod->init_layout.base;
do_mod_ctors(mod); /* Start the module */ //--------> Here mod->init is the module entry function, then calls do_one_initcall if (mod->init != NULL) ret = do_one_initcall(mod->init); if (ret < 0) { goto fail_free_freeinit; } if (ret > 0) { pr_warn("%s: '%s'->init suspiciously returned %d, it should " "follow 0/-E convention\n" "%s: loading module anyway...\n", __func__, mod->name, ret, __func__); dump_stack(); }
/* Now it's a first class citizen! */ mod->state = MODULE_STATE_LIVE; blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_LIVE, mod);
/* Delay uevent until module has finished its init routine */ kobject_uevent(&mod->mkobj.kobj, KOBJ_ADD);
/* * We need to finish all async code before the module init sequence * is done. This has potential to deadlock if synchronous module * loading is requested from async (which is not allowed!). * * See commit 0fdff3ec6d87 ("async, kmod: warn on synchronous * request_module() from async workers") for more details. */ if (!mod->async_probe_requested) async_synchronize_full();
ftrace_free_mem(mod, mod->init_layout.base, mod->init_layout.base + mod->init_layout.size); mutex_lock(&module_mutex); /* Drop initial reference. */ module_put(mod); trim_init_extable(mod); #ifdef CONFIG_KALLSYMS /* Switch to core kallsyms now init is done: kallsyms may be walking! */ rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms); #endif module_enable_ro(mod, true); mod_tree_remove_init(mod); module_arch_freeing_init(mod); mod->init_layout.base = NULL; mod->init_layout.size = 0; mod->init_layout.ro_size = 0; mod->init_layout.ro_after_init_size = 0; mod->init_layout.text_size = 0; /* * We want to free module_init, but be aware that kallsyms may be * walking this with preempt disabled. In all the failure paths, we * call synchronize_rcu(), but we don't want to slow down the success * path. module_memfree() cannot be called in an interrupt, so do the * work and call synchronize_rcu() in a work queue. * * Note that module_alloc() on most architectures creates W+X page * mappings which won't be cleaned up until do_free_init() runs. Any * code such as mark_rodata_ro() which depends on those mappings to * be cleaned up needs to sync with the queued work - ie * rcu_barrier() */ if (llist_add(&freeinit->node, &init_free_list)) schedule_work(&init_free_wq);
#ifdef CONFIG_MODULE_SIG /* Signature was verified. */ bool sig_ok; #endif
bool async_probe_requested;
/* symbols that will be GPL-only in the near future. */ conststructkernel_symbol *gpl_future_syms; const s32 *gpl_future_crcs; unsignedint num_gpl_future_syms;
#ifdef CONFIG_LIVEPATCH bool klp; /* Is this a livepatch module? */ bool klp_alive;
/* Elf information */ structklp_modinfo *klp_info; #endif
#ifdef CONFIG_MODULE_UNLOAD /* What modules depend on me? */ structlist_headsource_list; /* What modules do I depend on? */ structlist_headtarget_list;
/* Destruction function. */ void (*exit)(void);
atomic_t refcnt; #endif
#ifdef CONFIG_MITIGATION_ITS int its_num_pages; void **its_page_array; #endif
/* vi: set sw=4 ts=4: */ /* * Mini insmod implementation for busybox * * Copyright (C) 2008 Timo Teras <timo.teras@iki.fi> * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ //config:config INSMOD //config: bool "insmod (22 kb)" //config: default y //config: help //config: insmod is used to load specified modules in the running kernel.
/* Compat note: * 2.6 style insmod has no options and required filename * (not module name - .ko can't be omitted). * 2.4 style insmod can take module name without .o * and performs module search in default directories * or in $MODPATH. */
int FAST_FUNC bb_init_module(constchar *filename, constchar *options) { size_t image_size; char *image; int rc; bool mmaped;
if (!options) options = "";
//TODO: audit bb_init_module_24 to match error code convention #if ENABLE_FEATURE_2_4_MODULES if (get_linux_version_code() < KERNEL_VERSION(2,6,0)) return bb_init_module_24(filename, options); #endif
/* * First we try finit_module if available. Some kernels are configured * to only allow loading of modules off of secure storage (like a read- * only rootfs) which needs the finit_module call. If it fails, we fall * back to normal module loading to support compressed modules. */ # ifdef __NR_finit_module { // Method 1: Open the file via handle int fd = open(filename, O_RDONLY | O_CLOEXEC); if (fd >= 0) { int flags = is_suffixed_with(filename, ".ko") ? 0 : MODULE_INIT_COMPRESSED_FILE; for (;;) { // Then call finit_module rc = finit_module(fd, options, flags); if (rc == 0 || flags == 0) break; /* Loading non-.ko named uncompressed module? Not likely, but let's try it */ flags = 0; } close(fd); if (rc == 0) return rc; } } # endif
image_size = INT_MAX - 4095; mmaped = 0; // Method 2: Map the ko file to memory image = try_to_mmap_module(filename, &image_size); if (image) { mmaped = 1; } else { errno = ENOMEM; /* may be changed by e.g. open errors below */ // If mapping fails, try to malloc the ko file into memory image = xmalloc_open_zipped_read_close(filename, &image_size); if (!image) return -errno; }