Cover image for Linux Driver Loading Logic

Linux Driver Loading Logic

Words 6k
Views
Visitors

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 ContentsLinks
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#define __init		__section(".init.text") __cold  __latent_entropy __noinitretpoline#define __exit          __section(".exit.text") __exitused __cold notrace

__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
#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 the driver is compiled into the Linux kernel, module_exit is meaningless because statically compiled drivers cannot be unloaded.

MODULE Macro

ScenariomeaningMODULEStatus
Module (.ko)useobj-m += xxx.oCompile into a loadable module✅ Defined
Built-in driveruseobj-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 nameFunctionWhether to bring-DMODULE
KBUILD_CFLAGS_KERNELCFLAGS used when compiling built-in code❌ No
KBUILD_CFLAGS_MODULECFLAGS used when compiling module (.ko) code✅ Yes
KBUILD_AFLAGS_KERNELAFLAGS used by assembly files (built-in)❌ No
KBUILD_AFLAGS_MODULEAFLAGS used by assembly files (modules)✅ Yes

with usCompile the driver into the kernelas an example:

include/linux/module.h

12
// include/linux/module.h#define module_init(x)	__initcall(x);

include/linux/init.h

12345678910111213141516171819202122232425262728293031323334353637
// include/linux/init.h#define __initcall(fn) device_initcall(fn)#define pure_initcall(fn)		__define_initcall(fn, 0)#define core_initcall(fn)		__define_initcall(fn, 1)#define core_initcall_sync(fn)		__define_initcall(fn, 1s)#define postcore_initcall(fn)		__define_initcall(fn, 2)#define postcore_initcall_sync(fn)	__define_initcall(fn, 2s)#define arch_initcall(fn)		__define_initcall(fn, 3)#define arch_initcall_sync(fn)		__define_initcall(fn, 3s)#define subsys_initcall(fn)		__define_initcall(fn, 4)#define subsys_initcall_sync(fn)	__define_initcall(fn, 4s)#define fs_initcall(fn)			__define_initcall(fn, 5)#define fs_initcall_sync(fn)		__define_initcall(fn, 5s)#define rootfs_initcall(fn)		__define_initcall(fn, rootfs)#define device_initcall(fn)		__define_initcall(fn, 6)#define device_initcall_sync(fn)	__define_initcall(fn, 6s)#define late_initcall(fn)		__define_initcall(fn, 7)#define late_initcall_sync(fn)		__define_initcall(fn, 7s)#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)typedef int (*initcall_t)(void);#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS#define ___define_initcall(fn, id, __sec)			\	__ADDRESSABLE(fn)					\	asm(".section	\"" #__sec ".init\", \"a\"	\n"	\	"__initcall_" #fn #id ":			\n"	\	    ".long	" #fn " - .			\n"	\	    ".previous					\n");#else#define ___define_initcall(fn, id, __sec) \	static initcall_t __initcall_##fn##id __used \		__attribute__((__section__(#__sec ".init"))) = fn;#endif

__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.

MacroSection nameCall stage
early_initcall(fn).initcall0.initEarliest
core_initcall(fn).initcall1.initCore subsystem initialization
postcore_initcall(fn).initcall2.initInitialization after core completes
arch_initcall(fn).initcall3.initArchitecture-related parts
subsys_initcall(fn).initcall4.initSubsystems (e.g., driver core)
fs_initcall(fn).initcall5.initFilesystem initialization
device_initcall(fn).initcall6.initDevice driver initialization (default)
late_initcall(fn).initcall7.initLatest-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:

  1. Saves space: each entry takes only 4 bytes instead of 8 bytes (saves half on 64-bit).
  2. Supports kernel address randomization (KASLR): no need to perform relocation fixups on all pointer sections at boot time.
  3. Faster boot: eliminates a bunch of relocation fixup operations.

INIT_CALLS

include/asm-generic/vmlinux.lds.h

123456789101112131415161718
#define INIT_CALLS_LEVEL(level)						\		__initcall##level##_start = .;				\		KEEP(*(.initcall##level##.init))			\		KEEP(*(.initcall##level##s.init))			\#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 = .;

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
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONStypedef int initcall_entry_t;static inline initcall_t initcall_from_entry(initcall_entry_t *entry){	return offset_to_ptr(entry);}#elsetypedef initcall_t initcall_entry_t;static inline initcall_t initcall_from_entry(initcall_entry_t *entry){	return *entry;}#endif

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
#define SYSCALL_DEFINE_MAXARGS	6#define SYSCALL_DEFINEx(x, sname, ...)				\	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)/* * 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 */// ================SYSCALL_DEFINE=====================#ifndef SYSCALL_DEFINE0#define SYSCALL_DEFINE0(sname)					\	SYSCALL_METADATA(_##sname, 0);				\	asmlinkage long sys_##sname(void);			\	ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);		\	asmlinkage long sys_##sname(void)#endif /* SYSCALL_DEFINE0 */#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

The number represents the number of parameters.

Add a custom system call

helloworld.c

1234567
#include <linux/kernel.h>#include <linux/syscalls.h>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
// ...#define __NR_openat2 437__SYSCALL(__NR_openat2, sys_openat2)#define __NR_pidfd_getfd 438__SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)#define __NR_faccessat2 439__SYSCALL(__NR_faccessat2, sys_faccessat2)#define __NR_process_madvise 440__SYSCALL(__NR_process_madvise, sys_process_madvise)#define __NR_helloworld 441__SYSCALL(__NR_helloworld, sys_helloworld)#undef __NR_syscalls#define __NR_syscalls 442

Test

123456789
#include <sys/stat.h>#include <stdlib.h>#define __NR_helloworld 441int main(int argc, char **argv){    syscall(__NR_helloworld);    return 0;}

module_init

The initcall corresponding to kernel modules (loadable modules):

12345678910111213141516171819202122232425262728293031323334353637383940
#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

The module initialization is insmod/modprobecalled uniformly by the module loader at, and is not distinguished by the kernel’s boot stages. Therefore:

123
#define early_initcall(fn)       module_init(fn)#define device_initcall(fn)      module_init(fn)...

These macros all degenerate into the same thing:
When the module is loaded, the kernel directly callsinit_module(), and then executesfn()

123456
#define module_init(initfn)                                     \	static inline initcall_t __maybe_unused __inittest(void) {  \		return initfn;                                         \	}                                                          \	int init_module(void) __copy(initfn)                       \		__attribute__((alias(#initfn)));

After expansion, it is equivalent to generating two symbols:

  • an auxiliary__inittest()inline function (just returns initfn, not important);

  • a function alias namedinit_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 */#define __NR_init_module 105__SYSCALL(__NR_init_module, sys_init_module)#define __NR_delete_module 106__SYSCALL(__NR_delete_module, sys_delete_module)#define __NR_finit_module 273__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 CallsPrototypeIntroduction timeMain purpose
init_moduleinit_module(void *umod, unsigned long len, const char *uargs)Early Linux (since 1.x)Load modules from user-space memory
finit_modulefinit_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;#ifdef CONFIG_MODULE_SIG	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);	}#endif	/* 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);#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);	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. */#ifdef CONFIG_SYSFS	struct mutex param_lock;#endif	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;#ifdef CONFIG_UNUSED_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;#endif#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. */	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 */#ifdef CONFIG_GENERIC_BUG	/* Support for BUG */	unsigned num_bugs;	struct list_head bug_list;	struct bug_entry *bug_table;#endif#ifdef CONFIG_KALLSYMS	/* 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;#endif	/* The command line arguments (may be mangled).  People like	   keeping pointers to this stuff */	char *args;#ifdef CONFIG_SMP	/* Per-cpu data. */	void __percpu *percpu;	unsigned int percpu_size;#endif	void *noinstr_text_start;	unsigned int noinstr_text_size;#ifdef CONFIG_TRACEPOINTS	unsigned int num_tracepoints;	tracepoint_ptr_t *tracepoints_ptrs;#endif#ifdef CONFIG_TREE_SRCU	unsigned int num_srcu_structs;	struct srcu_struct **srcu_struct_ptrs;#endif#ifdef CONFIG_BPF_EVENTS	unsigned int num_bpf_raw_events;	struct bpf_raw_event_map *bpf_raw_events;#endif#ifdef CONFIG_JUMP_LABEL	struct jump_entry *jump_entries;	unsigned int num_jump_entries;#endif#ifdef CONFIG_TRACING	unsigned int num_trace_bprintk_fmt;	const char **trace_bprintk_fmt_start;#endif#ifdef CONFIG_EVENT_TRACING	struct trace_event_call**trace_events;	unsigned int num_trace_events;	struct trace_eval_map **trace_evals;	unsigned int num_trace_evals;#endif#ifdef CONFIG_FTRACE_MCOUNT_RECORD	unsigned int num_ftrace_callsites;	unsigned long *ftrace_callsites;#endif#ifdef CONFIG_KPROBES	void *kprobes_text_start;	unsigned int kprobes_text_size;	unsigned long *kprobe_blacklist;	unsigned int num_kprobe_blacklist;#endif#ifdef CONFIG_HAVE_STATIC_CALL_INLINE	int num_static_call_sites;	struct static_call_site *static_call_sites;#endif#ifdef CONFIG_LIVEPATCH	bool klp; /* Is this a livepatch module? */	bool klp_alive;	/* Elf information */	struct klp_modinfo *klp_info;#endif#ifdef CONFIG_MODULE_UNLOAD	/* 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;#endif#ifdef CONFIG_MITIGATION_ITS	int its_num_pages;	void **its_page_array;#endif#ifdef CONFIG_CONSTRUCTORS	/* Constructor functions. */	ctor_fn_t *ctors;	unsigned int num_ctors;#endif#ifdef CONFIG_FUNCTION_ERROR_INJECTION	struct error_injection_entry *ei_funcs;	unsigned int num_ei_funcs;#endif} ____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#include "libbb.h"#include "modutils.h"/* 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
#include <sys/syscall.h>#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)#if defined(__NR_finit_module)	#define finit_module(fd, uargs, flags) syscall(__NR_finit_module, fd, uargs, flags)	#ifndef MODULE_INIT_COMPRESSED_FILE		#define MODULE_INIT_COMPRESSED_FILE 4	#endif#endif#define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)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 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 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;		}	}# endif	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

Loading comments…