Timeline
Timeline
2025-11-10
init
This article introduces the core mechanism of Linux driver loading logic, focusing on analyzing how the driver initialization function uses module._The registration of the init macro into the kernel, and the differences between the two ways of compiling into the kernel and as a loadable module (.ko). The article explains the role of linker scripts (LDS) and GNU C compiler attributes in placing symbols into specific sections (such as .init.text, .init.data), and details__The mechanism by which code marked with the init macro is freed after initialization. At the same time, the article compares the definition states of the MODULE macro in built-in driver and module scenarios, as well as the differences in usage of different CFLAGS/AFLAGS variables in the top-level Makefile. For drivers compiled into the kernel, module_After init is expanded, the function pointer is placed into the kernel-reserved initialization function pointer table (such as the .initcall section), and according to different priorities (such as fs_initcall、device_initcall, etc.) are called in stages. In addition, the article also discusses the meaning of the PREL32 relocation option on some architectures (such as ARM64, RISC-V), that is, stored in the section...
Linux Driver Notes
| Table of Contents | Links |
|---|---|
| 1. Linux Driver Framework | |
| 2. Linux Driver Loading Logic | |
| 3. Character Device Basics | |
| 4. Concurrency and Race Conditions | |
| 5. Advanced Character Device Topics | |
| 6. Interrupts | |
| 7. Platform Bus | |
| 8. Device Tree | |
| 9. Device Model | |
| 10. Hotplug | |
| 11. pinctrl Subsystem | |
| 12. GPIO subsystem | |
| 13. Input subsystem | |
| 14. 1-Wire | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. Network devices | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
Drivers generally need to call the module_init function to register the driver with the kernel.
__initand__exitproperty
The linker (ld on Linux systems) is part of binutils and is responsible for placing symbols (data, code, etc.) into appropriate sections of the generated binary file so that they can be processed by the loader when the program is executed.
These sections in binary files can be customized, their default locations changed, and even additional sections can be added by providing a linker script [called a linker definition file (LDF) or linker definition script (LDS)]. To achieve these operations, you only need to inform the linker of the symbol locations through compiler directives. The GNU C compiler provides some attributes for this purpose.
Sex.
The Linux kernel provides a custom LDS file, which is located atarch/<arch>/kernel/vmlinux.lds.SIn. For symbols to be placed in the dedicated section mapped by the kernel LDS file, use__initand__exitMark
1234 | //include/linux/init.h |
__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 it is freed after module loading and the 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 the boot process.
123456789101112131415161718192021222324252627282930313233343536373839404142 | $ objdump -h hello_world.kohello_world.ko: file format elf64-littleSections:Idx Name Size VMA LMA File off Algn 0 .text 00000000 0000000000000000 0000000000000000 00000040 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .init.text 00000034 0000000000000000 0000000000000000 00000040 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 2 .exit.text 00000024 0000000000000000 0000000000000000 00000074 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 3 .note.gnu.property 00000020 0000000000000000 0000000000000000 00000098 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .note.gnu.build-id 00000024 0000000000000000 0000000000000000 000000b8 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .note.Linux 00000018 0000000000000000 0000000000000000 000000dc 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .rodata.str1.8 0000002b 0000000000000000 0000000000000000 000000f8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .modinfo 000000b2 0000000000000000 0000000000000000 00000123 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 __versions 00000080 0000000000000000 0000000000000000 000001d8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 __patchable_function_entries 00000008 0000000000000080 0000000000000080 00000258 2**3 CONTENTS, ALLOC, LOAD, RELOC, DATA 10 .data 00000000 0000000000000000 0000000000000000 00000260 2**0 CONTENTS, ALLOC, LOAD, DATA 11 .gnu.linkonce.this_module 000003c0 0000000000000000 0000000000000000 00000260 2**6 CONTENTS, ALLOC, LOAD, RELOC, DATA, LINK_ONCE_DISCARD 12 .plt 00000001 0000000000000000 0000000000000000 00000620 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .init.plt 00000001 0000000000000000 0000000000000000 00000621 2**0 ALLOC, READONLY 14 .text.ftrace_trampoline 00000001 0000000000000000 0000000000000000 00000621 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 15 .bss 00000000 0000000000000000 0000000000000000 00000622 2**0 ALLOC 16 .comment 00000026 0000000000000000 0000000000000000 00000622 2**0 CONTENTS, READONLY 17 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000648 2**0 CONTENTS, READONLY |
The more important part is.modinfoand.init.text, the former stores information about the module, the latter stores with __code prefixed with the init macro
In short,__initand__exitAre Linux macros that use C compiler attributes to specify symbol locations. These macros instruct the compiler to place the code prefixed with them respectively in.init.textand.exit.textpart, so that the kernel can access these different object parts.
Drivers compiled into the kernel
module_init
Defined in include/linux/module.h
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 | /** * 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. *//** * 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. *//* * 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. *//* Each module must use one module_init(). *//* This is only required if you want to be unloadable. */ |
If the driver is compiled into the Linux kernel, module_exit is meaningless because statically compiled drivers cannot be unloaded.
MODULE Macro
| Scenario | meaning | MODULEStatus |
|---|---|---|
| Module (.ko) | useobj-m += xxx.oCompile into a loadable module | ✅ Defined |
| Built-in driver | useobj-y += xxx.oCompile into kernel image | ❌ Undefined |
In the top-level Makefile of the Linux source code
1234 | KBUILD_AFLAGS_KERNEL :=KBUILD_CFLAGS_KERNEL :=KBUILD_AFLAGS_MODULE := -DMODULEKBUILD_CFLAGS_MODULE := -DMODULE |
| Variable name | Function | Whether to bring-DMODULE |
|---|---|---|
KBUILD_CFLAGS_KERNEL | CFLAGS used when compiling built-in code | ❌ No |
KBUILD_CFLAGS_MODULE | CFLAGS used when compiling module (.ko) code | ✅ Yes |
KBUILD_AFLAGS_KERNEL | AFLAGS used by assembly files (built-in) | ❌ No |
KBUILD_AFLAGS_MODULE | AFLAGS used by assembly files (modules) | ✅ Yes |
with usCompile the driver into the kernelas an example:
include/linux/module.h
12 | // include/linux/module.h |
include/linux/init.h
12345678910111213141516171819202122232425262728293031323334353637 | // include/linux/init.htypedef int (*initcall_t)(void); |
__attribute__((__section__(".initcall6.init")))Tell the compiler: put this pointer variable into.initcall6.initIn the paragraph.
This section is an “initialization function pointer table” specifically reserved by the kernel, corresponding to the call order of different stages in the initialization phase.
Use fn as my_For example, driver: module_init ultimately expands to
1 | __initcall_my_driver_init6 = my_driver_init; |
That is, in this segment, a pointer is stored.my_driver_init()pointer. An 8-byte.
If you want to use a different priority, you can replace the 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 completes |
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 a Kconfig option, appears in some architectures (such as ARM64, RISC-V). Its meaning is:
- This architecture supports using PREL32(PC-relative 32-bit relocation) type of relocation, that is, stored in
.initcallthe addresses in the section are relative to the current pointer position (PC-relative) a 32-bit offset, not an absolute address.
On systems without this option (such as traditional x86),.initcallX.initthe section directly stores function pointers:
12 | static initcall_t __initcall_driver6 __used__attribute__((section(".initcall6.init"))) = driver_init; |
that is:
123 | .initcall6.init: 0xffffffff81001234 // absolute addresses 0xffffffff81004567 |
But on systems that support PREL32:
1234 | asm(".section \".initcall6.init\", \"a\"\n" "__initcall_driver6:\n" ".long driver_init - .\n" ".previous\n"); |
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; |
In this way, the kernel can, without needing relocation fixes, locate functions. This is for position-independent kernels (KASLR, RELATIVE linking), particularly important, because the absolute addresses of functions are determined only at boot time.
Advantages:
- Saves space: each entry takes only 4 bytes instead of 8 bytes (saves half on 64-bit).
- Supports kernel address randomization (KASLR): no need to perform relocation fixups on all pointer sections at boot time.
- Faster boot: eliminates a bunch of relocation fixup operations.
INIT_CALLS
include/asm-generic/vmlinux.lds.h
123456789101112131415161718 |
After final expansion, it is placed into the linker script:
1234567891011121314 | __initcall_start = .;KEEP(*(.initcallearly.init))__initcall0_start = .;KEEP(*(.initcall0.init))KEEP(*(.initcall0s.init))__initcall1_start = .;KEEP(*(.initcall1.init))KEEP(*(.initcall1s.init))...__initcall7_start = .;KEEP(*(.initcall7.init))KEEP(*(.initcall7s.init))__initcall_end = .; |
init/main.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 | extern initcall_entry_t __initcall_start[];extern initcall_entry_t __initcall0_start[];extern initcall_entry_t __initcall1_start[];extern initcall_entry_t __initcall2_start[];extern initcall_entry_t __initcall3_start[];extern initcall_entry_t __initcall4_start[];extern initcall_entry_t __initcall5_start[];extern initcall_entry_t __initcall6_start[];extern initcall_entry_t __initcall7_start[];extern initcall_entry_t __initcall_end[];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,};asmlinkage __visible void __init __no_sanitize_address start_kernel(void){ //Omitted above /* Do the rest non-__init'ed, we're now alive */ arch_call_rest_init(); prevent_tail_call_optimization();}void __init __weak arch_call_rest_init(void){ rest_init();}noinline void __ref rest_init(void){ struct task_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 below}static int __ref kernel_init(void *unused){ int ret; kernel_init_freeable(); // Omitted below}//static noinline void __init kernel_init_freeable(void){ // Omitted above do_basic_setup(); // Omitted below}static void __init do_basic_setup(void){ cpuset_init_smp(); driver_init(); init_irq_proc(); do_ctors(); usermodehelper_enable(); do_initcalls();}static void __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 shows that initialization starts from 0, so the smaller the number, the higher the priority. // In addition, for the same id, those with an 's' suffix (such as core_initcall_sync) execute after those without 's' (such as core_initcall). 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); } kfree(command_line);}static void __init do_initcall_level(int level, char *command_line){ initcall_entry_t *fn; parse_args(initcall_level_names[level], command_line, __start___param, __stop___param - __start___param, level, level, NULL, ignore_unknown_bootoption); trace_initcall_level(initcall_level_names[level]); 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){ int count = preempt_count(); char msgbuf[64]; int ret; if (initcall_blacklisted(fn)) return -EPERM; do_trace_initcall_start(fn); ret = fn();// Core code, execute this initialization function once. do_trace_initcall_finish(fn, ret); msgbuf[0] = 0; if (preempt_count() != count) { sprintf(msgbuf, "preemption imbalance "); preempt_count_set(count); } if (irqs_disabled()) { strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf)); local_irq_enable(); } WARN(msgbuf[0], "initcall %pS returned with %s\n", fn, msgbuf); add_latent_entropy(); return ret;} |
wheredo_initcall_levelused ininitcall_from_entrydefined in include/linux/init.h
123456789101112131415 | typedef int initcall_entry_t;static inline initcall_t initcall_from_entry(initcall_entry_t *entry){ return offset_to_ptr(entry);}typedef initcall_t initcall_entry_t;static inline initcall_t initcall_from_entry(initcall_entry_t *entry){ return *entry;} |
Summary
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 initcalls() function, based on the pointer array initcall_levels[6] finds __initcall6_start, which can be found in include/asm-generic/vmlinux.lds.h __The start address of the .initcall6.init section corresponding to initcall6_start, then sequentially take out the function pointers of this section and execute the functions.
Kernel module driver
System Calls
include/linux/syscalls.h
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 | /* * 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_*(). */// ================SYSCALL_DEFINE===================== |
The number represents the number of parameters.
Add a custom system call
helloworld.c
1234567 | SYSCALL_DEFINE0(helloworld){ printk("hello world syscall\n"); return 0;} |
Can be placed in any path of the Linux source code.
Add a system call number
Beforeinclude/uapi/asm-generic/unistd.hadd in
1234567891011121314 | // ...__SYSCALL(__NR_openat2, sys_openat2)__SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)__SYSCALL(__NR_faccessat2, sys_faccessat2)__SYSCALL(__NR_process_madvise, sys_process_madvise)__SYSCALL(__NR_helloworld, sys_helloworld) |
Test
123456789 | int main(int argc, char **argv){ syscall(__NR_helloworld); return 0;} |
module_init
The initcall corresponding to kernel modules (loadable modules):
12345678910111213141516171819202122232425262728293031323334353637383940 | /* * 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. *//* Each module must use one module_init(). *//* This is only required if you want to be unloadable. */ |
The module initialization is insmod/modprobecalled uniformly by the module loader at, and is not distinguished by the kernel’s boot stages. Therefore:
123 | ... |
These macros all degenerate into the same thing:
When the module is loaded, the kernel directly callsinit_module(), and then executesfn()
123456 |
After expansion, it is equivalent to generating two symbols:
an auxiliary
__inittest()inline function (just returns initfn, not important);a function alias named
init_modulefunction alias, directly pointing to the initialization function we defined.
Example
12 | static int mydriver_init(void) { ... }module_init(mydriver_init); |
After preprocessing, it is equivalent to:
1 | int init_module(void) __attribute__((alias("mydriver_init"))); |
That is to say: when the kernel loads the module, the actual entry function executed is init_module(), which is bound by the alias to the initialization function mydriver we defined._init()。
include/uapi/asm-generic/unistd.h
uapi belongs to Generic Kernel Interface (UAPI, User API), that is, header files that user space can include. It defines system call numbers common to most architectures(syscall numbers), for user programs to call.
12345678 | /* kernel/module.c */__SYSCALL(__NR_init_module, sys_init_module)__SYSCALL(__NR_delete_module, sys_delete_module)__SYSCALL(__NR_finit_module, sys_finit_module) |
include/linux/syscalls.h
1234567 | /* kernel/module.c */asmlinkage long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs);asmlinkage long sys_delete_module(const char __user *name_user, unsigned int flags);asmlinkage long sys_finit_module(int fd, const char __user *uargs, int flags); |
asmlinkageis a calling convention modifier, used to tell the compiler:All parameters of the system call entry function are passed on the stack (rather than in registers), because it is entered from the assembly entry (syscall trap).
kernel/module.c
12345678910111213141516171819202122232425262728293031323334353637383940414243444546 | SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs){ int err; struct load_info info = { }; err = may_init_module(); if (err) return err; pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n", umod, len, uargs); err = copy_module_from_user(umod, len, &info); if (err) return err; return load_module(&info, uargs, 0);}SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags){ struct load_info info = { }; void *hdr = NULL; int err; err = may_init_module(); if (err) return err; pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags); if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS |MODULE_INIT_IGNORE_VERMAGIC)) return -EINVAL; err = kernel_read_file_from_fd(fd, 0, &hdr, INT_MAX, NULL, READING_MODULE); if (err < 0) return err; info.hdr = hdr; info.len = err; return load_module(&info, uargs, flags);} |
| System Calls | Prototype | Introduction time | Main purpose |
|---|---|---|---|
init_module | init_module(void *umod, unsigned long len, const char *uargs) | Early Linux (since 1.x) | Load modules from user-space memory |
finit_module | finit_module(int fd, const char *uargs, int flags) | Linux 3.8 (2013) | Load modules from a file descriptor (fd) |
SYSCALL_DEFINE3is a macro used to generate system call wrapper functions. The function name it generates issys_init_module. Here, 3 means 3 parameters, SYSCALL_DEFINE supports up to 6 parameters (SYSCALL_DEFINE1 ~ SYSCALL_DEFINE6)。
will eventually expand to __se_sys_init_module。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 | /* Allocate and load the module: note that size of section 0 is always zero, and we rely on this for optional sections. */static int load_module(struct load_info *info, const char __user *uargs, int flags){ struct module *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; mod->sig_ok = info->sig_ok; if (!mod->sig_ok) { pr_notice_once("%s: module verification failed: signature " "and/or required key missing - tainting " "kernel\n", mod->name); add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK); } /* 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; } dynamic_debug_setup(mod, info->debug, info->num_debug); /* Ftrace init must be called in the MODULE_STATE_UNFORMED state */ ftrace_module_init(mod); /* Finally it's fully formed, ready to start executing. */ err = complete_formation(mod, info); if (err) goto ddebug_cleanup; err = prepare_coming_module(mod); if (err) goto bug_cleanup; /* Module is ready to execute: parsing args may do that. */ after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp, -32768, 32767, mod, unknown_module_param_cb); if (IS_ERR(after_dashes)) { err = PTR_ERR(after_dashes); goto coming_cleanup; } else if (after_dashes) { pr_warn("%s: parameters '%s' after `--' ignored\n", mod->name, after_dashes); } /* Link in to sysfs. */ err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp); if (err < 0) goto coming_cleanup; if (is_livepatch_module(mod)) { err = copy_module_elf(mod, info); if (err < 0) goto sysfs_cleanup; } /* Get rid of temporary copy. */ free_copy(info); /* Done! */ trace_module_load(mod);//-----> eventually returns do_init_module return do_init_module(mod); sysfs_cleanup: mod_sysfs_teardown(mod); coming_cleanup: mod->state = MODULE_STATE_GOING; destroy_params(mod->kp, mod->num_kp); blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod); klp_module_going(mod); bug_cleanup: mod->state = MODULE_STATE_GOING; /* module_bug_cleanup needs module_mutex protection */ mutex_lock(&module_mutex); module_bug_cleanup(mod); mutex_unlock(&module_mutex); ddebug_cleanup: ftrace_release_mod(mod); dynamic_debug_remove(mod, info->debug); synchronize_rcu(); kfree(mod->args); free_arch_cleanup: module_arch_cleanup(mod); free_modinfo: free_modinfo(mod); free_unload: module_unload_free(mod); unlink_mod: mutex_lock(&module_mutex); /* Unlink carefully: kallsyms could be walking list. */ list_del_rcu(&mod->list); mod_tree_remove(mod); wake_up_all(&module_wq); /* Wait for RCU-sched synchronizing before releasing mod->list. */ synchronize_rcu(); mutex_unlock(&module_mutex); free_module: /* Free lock-classes; relies on the preceding sync_rcu() */ lockdep_free_key_range(mod->core_layout.base, mod->core_layout.size); module_deallocate(mod, info); free_copy: free_copy(info); return err;} |
will eventually return do_init_module(mod);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 | /* * 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 int do_init_module(struct module *mod){ int ret = 0; struct mod_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 it 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); /* Switch to core kallsyms now init is done: kallsyms may be walking! */ rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms); 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); mutex_unlock(&module_mutex); wake_up_all(&module_wq); return 0;fail_free_freeinit: kfree(freeinit);fail: /* Try to protect us from buggy refcounters. */ mod->state = MODULE_STATE_GOING; synchronize_rcu(); module_put(mod); blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod); klp_module_going(mod); ftrace_release_mod(mod); free_module(mod); wake_up_all(&module_wq); return ret;} |
Call do_one_initcall(mod->init);
struct module
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 | struct module { enum module_state state; /* Member of list of modules */ struct list_head list; /* Unique handle for this module */ char name[MODULE_NAME_LEN]; /* Sysfs stuff. */ struct module_kobject mkobj; struct module_attribute *modinfo_attrs; const char *version; const char *srcversion; struct kobject *holders_dir; /* Exported symbols */ const struct kernel_symbol *syms; const s32 *crcs; unsigned int num_syms; /* Kernel parameters. */ struct mutex param_lock; struct kernel_param *kp; unsigned int num_kp; /* GPL-only exported symbols. */ unsigned int num_gpl_syms; const struct kernel_symbol *gpl_syms; const s32 *gpl_crcs; bool using_gplonly_symbols; /* unused exported symbols. */ const struct kernel_symbol *unused_syms; const s32 *unused_crcs; unsigned int num_unused_syms; /* GPL-only, unused exported symbols. */ unsigned int num_unused_gpl_syms; const struct kernel_symbol *unused_gpl_syms; const s32 *unused_gpl_crcs; /* Signature was verified. */ bool sig_ok; bool async_probe_requested; /* symbols that will be GPL-only in the near future. */ const struct kernel_symbol *gpl_future_syms; const s32 *gpl_future_crcs; unsigned int num_gpl_future_syms; /* Exception table */ unsigned int num_exentries; struct exception_table_entry *extable; /* Startup function. */ int (*init)(void); /* Core layout: rbtree is accessed frequently, so keep together. */ struct module_layout core_layout __module_layout_align; struct module_layout init_layout; /* Arch-specific module values */ struct mod_arch_specific arch; unsigned long taints; /* same bits as kernel:taint_flags */ /* Support for BUG */ unsigned num_bugs; struct list_head bug_list; struct bug_entry *bug_table; /* Protected by RCU and/or module_mutex: use rcu_dereference() */ struct mod_kallsyms __rcu *kallsyms; struct mod_kallsyms core_kallsyms; /* Section attributes */ struct module_sect_attrs *sect_attrs; /* Notes attributes */ struct module_notes_attrs *notes_attrs; /* The command line arguments (may be mangled). People like keeping pointers to this stuff */ char *args; /* Per-cpu data. */ void __percpu *percpu; unsigned int percpu_size; void *noinstr_text_start; unsigned int noinstr_text_size; unsigned int num_tracepoints; tracepoint_ptr_t *tracepoints_ptrs; unsigned int num_srcu_structs; struct srcu_struct **srcu_struct_ptrs; unsigned int num_bpf_raw_events; struct bpf_raw_event_map *bpf_raw_events; struct jump_entry *jump_entries; unsigned int num_jump_entries; unsigned int num_trace_bprintk_fmt; const char **trace_bprintk_fmt_start; struct trace_event_call**trace_events; unsigned int num_trace_events; struct trace_eval_map **trace_evals; unsigned int num_trace_evals; unsigned int num_ftrace_callsites; unsigned long *ftrace_callsites; void *kprobes_text_start; unsigned int kprobes_text_size; unsigned long *kprobe_blacklist; unsigned int num_kprobe_blacklist; int num_static_call_sites; struct static_call_site *static_call_sites; bool klp; /* Is this a livepatch module? */ bool klp_alive; /* Elf information */ struct klp_modinfo *klp_info; /* What modules depend on me? */ struct list_head source_list; /* What modules do I depend on? */ struct list_head target_list; /* Destruction function. */ void (*exit)(void); atomic_t refcnt; int its_num_pages; void **its_page_array; /* Constructor functions. */ ctor_fn_t *ctors; unsigned int num_ctors; struct error_injection_entry *ei_funcs; unsigned int num_ei_funcs;} ____cacheline_aligned __randomize_layout; |
How does the kernel run ko files?
insmod command
busybox1.37.0/modutils/insmod.c
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 | /* 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.//applet:IF_INSMOD(IF_NOT_MODPROBE_SMALL(APPLET_NOEXEC(insmod, insmod, BB_DIR_SBIN, BB_SUID_DROP, insmod)))//kbuild:ifneq ($(CONFIG_MODPROBE_SMALL),y)//kbuild:lib-$(CONFIG_INSMOD) += insmod.o modutils.o//kbuild:endif/* 2.6 style insmod has no options and required filename * (not module name - .ko can't be omitted) *///usage:#if !ENABLE_MODPROBE_SMALL//usage:#define insmod_trivial_usage//usage: IF_FEATURE_2_4_MODULES("[-fkvqLx] MODULE")//usage: IF_NOT_FEATURE_2_4_MODULES("FILE")//usage: IF_FEATURE_CMDLINE_MODULE_OPTIONS(" [SYMBOL=VALUE]...")//usage:#define insmod_full_usage "\n\n"//usage: "Load kernel module"//usage: IF_FEATURE_2_4_MODULES( "\n"//usage: "\n -f Force module to load into the wrong kernel version"//usage: "\n -k Make module autoclean-able"//usage: "\n -v Verbose"//usage: "\n -q Quiet"//usage: "\n -L Lock: prevent simultaneous loads"//usage: IF_FEATURE_INSMOD_LOAD_MAP(//usage: "\n -m Output load map to stdout"//usage: )//usage: "\n -x Don't export externs"//usage: )//usage:#endifint insmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;int insmod_main(int argc UNUSED_PARAM, char**argv){ char *filename; int rc; /* 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. */ IF_FEATURE_2_4_MODULES( getopt32(argv, INSMOD_OPTS INSMOD_ARGS); argv += optind - 1; ); filename = *++argv; if (!filename) bb_show_usage(); rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0)); if (rc) bb_error_msg("can't insert '%s': %s", filename, moderror(rc)); return rc;} |
Called bb_init_module
busybox1.37.0/modutils/modutils.c
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 | int FAST_FUNC bb_init_module(const char *filename, const char *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 (get_linux_version_code() < KERNEL_VERSION(2,6,0)) return bb_init_module_24(filename, options); /* * 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. */ { // Method 1: Open the file via file descriptor 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; } } image_size = INT_MAX - 4095; mmaped = 0; // Method 2: Map the ko file into 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; } errno = 0; // Finally call init_module init_module(image, image_size, options); rc = errno; if (mmaped) munmap(image, image_size); else free(image); return rc;} |
In the end, it will be called via a system call to __NR_init_module or __NR_finit_module

