时间轴
时间轴
2025-11-10
init
本文介绍了Linux驱动加载逻辑的核心机制,重点分析了驱动初始化函数如何通过module_init宏注册到内核,以及编译进内核与可加载模块(.ko)两种方式下的差异。文章解释了链接器脚本(LDS)和GNU C编译器属性在将符号放置到特定段(如.init.text、.init.data)中的作用,并详细说明了__init宏标记的代码在初始化后被释放的机制。同时,文章对比了MODULE宏在内建驱动与模块场景下的定义状态,以及顶层Makefile中不同CFLAGS/AFLAGS变量的使用区别。针对编译进内核的驱动,module_init展开后会将函数指针放入内核保留的初始化函数指针表(如.initcall段),并根据不同优先级(如fs_initcall、device_initcall等)按阶段调用。此外,文章还讨论了PREL32重定位选项在部分架构(如ARM64、RISC-V)上的含义,即段中存
Linux 驱动笔记
| 目录 | 链接 |
|---|---|
| 1. Linux 驱动框架 | |
| 2. Linux 驱动加载逻辑 | |
| 3. 字符设备基础 | |
| 4. 并发与竞争 | |
| 5. 高级字符设备进阶 | |
| 6. 中断 | |
| 7. 平台总线 | |
| 8. 设备树 | |
| 9. 设备模型 | |
| 10. 热插拔 | |
| 11. pinctrl 子系统 | |
| 12. gpio 子系统 | |
| 13. 输入子系统 | |
| 14. 单总线 | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. 网络设备 | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
驱动一般都需要调用 module_init 函数来向内核注册驱动。
__init和__exit属性
链接器(Linux 系统上的 ld)是 binutils 的一部分,负责将符号(数据、代码等)放置到生成的二进制文件中的适当部分,以便在程序执行时可以被加载器处理。
二进制文件中的这些部分可以自定义、更改它们的默认位置,甚至可以通过提供链接器脚本[称为链接器定义文件(LDF)或链接器定义脚本(LDS)]来添加其他部分。要实现这些操作只需通过编译器指令把符号的位置告知链接器即可,GNU C 编译器为此提供了一些属性。
Linux 内核提供了一个自定义 LDS 文件,它位于arch/<arch>/kernel/vmlinux.lds.S中。对于要放置在内核 LDS 文件所映射的专用部分中的符号,使用__init和__exit进行标记
1234 | //include/linux/init.h |
__init关键字告诉链接器将该代码放在内核对象文件的专用部分。这部分事先为内核所知,它在模块加载和 init 函数执行后被释放。这仅适用于内置驱动程序,而不适用于可加载模块。内核在启动过程中第一次运行驱动程序的初始化函数。
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 |
较重要的部分是.modinfo和.init.text,前者存储有关模块的信息,后者存储以__init 宏为前缀的代码
总之,__init和__exit是 Linux 宏,它们使用 C 编译器属性指定符号的位置。这些宏指示编译器将以它们为前缀的代码分别放在.init.text和.exit.text部分,以便内核可以访问这些不同的对象部分。
编译进内核的驱动
module_init
定义在 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. */ |
如果把驱动编译进 Linux 内核,module_exit 是没有意义的,因为静态编译的驱动无法卸载
MODULE 宏
| 场景 | 含义 | MODULE状态 |
|---|---|---|
| 模块(.ko) | 使用obj-m += xxx.o编译为可加载模块 | ✅ 被定义 |
| 内建驱动(built-in) | 使用obj-y += xxx.o编进内核镜像 | ❌ 未定义 |
在 linux 源码顶层 Makefile 里
1234 | KBUILD_AFLAGS_KERNEL :=KBUILD_CFLAGS_KERNEL :=KBUILD_AFLAGS_MODULE := -DMODULEKBUILD_CFLAGS_MODULE := -DMODULE |
| 变量名 | 作用 | 是否带-DMODULE |
|---|---|---|
KBUILD_CFLAGS_KERNEL | 编译内建(built-in)代码时使用的 CFLAGS | ❌ 否 |
KBUILD_CFLAGS_MODULE | 编译模块(.ko)代码时使用的 CFLAGS | ✅ 是 |
KBUILD_AFLAGS_KERNEL | 汇编文件(built-in)使用的 AFLAGS | ❌ 否 |
KBUILD_AFLAGS_MODULE | 汇编文件(模块)使用的 AFLAGS | ✅ 是 |
以我们把驱动编译进内核为例:
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")))告诉编译器:把这个指针变量放到.initcall6.init段中。
这个段是内核专门保留的一个“初始化函数指针表”,对应初始化阶段的不同阶段调用顺序。
以 fn 为 my_driver 为例:module_init 最终展开为
1 | __initcall_my_driver_init6 = my_driver_init; |
也就是在这个段里存放了一个指向my_driver_init()的指针。一个 8 字节。
如果想要用其他优先级,可以把驱动中的 module_init 替换为 fs_initcall 等。
| 宏 | 段名 | 调用阶段 |
|---|---|---|
early_initcall(fn) | .initcall0.init | 最早 |
core_initcall(fn) | .initcall1.init | 核心子系统初始化 |
postcore_initcall(fn) | .initcall2.init | 核心完成后初始化 |
arch_initcall(fn) | .initcall3.init | 与架构相关部分 |
subsys_initcall(fn) | .initcall4.init | 子系统(例如 driver core) |
fs_initcall(fn) | .initcall5.init | 文件系统初始化 |
device_initcall(fn) | .initcall6.init | 设备驱动初始化(默认) |
late_initcall(fn) | .initcall7.init | 最晚阶段初始化 |
PREL32
CONFIG_HAVE_ARCH_PREL32_RELOCATIONS是一个 Kconfig 选项,出现在部分架构(如 ARM64、RISC-V)中。它的含义是:
- 该架构支持使用 PREL32(PC-relative 32-bit relocation) 类型的重定位方式,也就是说:存储在
.initcall段中的地址,是 相对当前指针位置(PC-relative) 的 32 位偏移量,而不是绝对地址。
在没有这个选项的系统上(如传统 x86),.initcallX.init段直接存放函数指针:
12 | static initcall_t __initcall_driver6 __used__attribute__((section(".initcall6.init"))) = driver_init; |
也就是:
123 | .initcall6.init: 0xffffffff81001234 // 绝对地址 0xffffffff81004567 |
但是在支持 PREL32 的系统上:
1234 | asm(".section \".initcall6.init\", \"a\"\n" "__initcall_driver6:\n" ".long driver_init - .\n" ".previous\n"); |
这里.long driver_init - .表示:把“函数地址减当前地址”的差值(一个 32 位有符号偏移)存入.initcall表。
在运行时,内核会这样取值:
1 | real_addr = (u64)&entry + (s32)*entry; |
这样,内核就能在 不需要重定位修正 的情况下定位函数。这对于 位置无关的内核(KASLR、RELATIVE linking) 特别重要,因为函数的绝对地址在启动时才确定。
优势:
- 节省空间:每个 entry 只占 4 字节,而不是 8 字节(在 64 位下节省一半)。
- 支持内核地址随机化(KASLR):不需要在启动时对所有指针段做重定位修正。
- 启动更快:省去一大堆 relocation fixup 操作。
INIT_CALLS
include/asm-generic/vmlinux.lds.h
123456789101112131415161718 |
最终展开后放入链接脚本中:
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){ //前面省略 /* 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); // 后面的省略}static int __ref kernel_init(void *unused){ int ret; kernel_init_freeable(); // 后面省略}//static noinline void __init kernel_init_freeable(void){ // 前面省略 do_basic_setup(); // 后面省略}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); // 这个for循环可以看到是从0开始初始化的,因此数字越小优先级越高 // 此外相同id的带s后缀的(如 core_initcall_sync)在不带s的(如 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();// 核心代码,执行一次这个初始化函数 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;} |
其中do_initcall_level中使用的initcall_from_entry定义在 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;} |
总结
在使用 module_init(hello_world)的时候,hello_world()这个函数会被放在.initcall6.init 段处。当内核启动的时候,会执行 do_initcalls()函数根据指针数组 initcall_levels[6]找到__initcall6_start,在 include/asm-generic/vmlinux.lds.h 中可以查到__initcall6_start 对应的.initcall6.init 段的起始地址,然后依次取出这个段的函数指针并执行函数。
内核模块驱动
系统调用
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===================== |
数字代表参数的个数
添加一个自定义系统调用
helloworld.c
1234567 | SYSCALL_DEFINE0(helloworld){ printk("hello world syscall\n"); return 0;} |
可以放在 linux 源码任意路径下
添加系统调用号
在include/uapi/asm-generic/unistd.h中添加
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) |
测试
123456789 | int main(int argc, char **argv){ syscall(__NR_helloworld); return 0;} |
module_init
内核模块对应的 initcall(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. */ |
模块的初始化是在 insmod/modprobe时由模块加载器统一调用 的,不会按内核的启动阶段区分。因此:
123 | ... |
这些宏都退化成同一个东西:
模块加载时,内核直接调用init_module(),进而执行fn()
123456 |
展开后等价于生成两个符号:
一个辅助的
__inittest()内联函数(只是返回 initfn,不重要);一个名为
init_module的函数别名,直接指向我们定义的初始化函数。
例子
12 | static int mydriver_init(void) { ... }module_init(mydriver_init); |
预处理后就等价于:
1 | int init_module(void) __attribute__((alias("mydriver_init"))); |
也就是说:内核加载模块时,真正执行的入口函数是 init_module(),它被别名绑定到我们定义的初始化函数 mydriver_init()。
include/uapi/asm-generic/unistd.h
uapi 属于 通用内核接口(UAPI, User API),也就是用户空间可以包含的头文件。定义了 大部分架构通用的系统调用号(syscall numbers),供用户程序调用。
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); |
asmlinkage是一个 调用约定修饰符,用于告诉编译器:系统调用入口函数的所有参数都从栈上传递(而不是寄存器),因为它是从汇编入口(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);} |
| 系统调用 | 原型 | 引入时间 | 主要用途 |
|---|---|---|---|
init_module | init_module(void *umod, unsigned long len, const char *uargs) | 早期 Linux(1.x 起) | 从用户空间内存加载模块 |
finit_module | finit_module(int fd, const char *uargs, int flags) | Linux 3.8(2013 年) | 从文件描述符(fd)加载模块 |
SYSCALL_DEFINE3是一个宏,用于生成系统调用封装函数。它生成的函数名就是sys_init_module。这里的 3 表示 3 个参数,SYSCALL_DEFINE 最多支持 6 个参数(SYSCALL_DEFINE1 ~ SYSCALL_DEFINE6)。
最终会展开为__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);//-----> 最终返回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;} |
最终会返回 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 *///-------->这里mod->init就是模块入口函数,然后调用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;} |
调用 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; |
内核是如何运行 ko 文件的?
insmod 命令
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;} |
调用了 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. */ { // 方法1:通过文件描述符打开文件 int fd = open(filename, O_RDONLY | O_CLOEXEC); if (fd >= 0) { int flags = is_suffixed_with(filename, ".ko") ? 0 : MODULE_INIT_COMPRESSED_FILE; for (;;) { // 然后调用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; // 方法2:将ko文件映射到内存 image = try_to_mmap_module(filename, &image_size); if (image) { mmaped = 1; } else { errno = ENOMEM; /* may be changed by e.g. open errors below */ // 如果映射失败,尝试将ko文件malloc到内存 image = xmalloc_open_zipped_read_close(filename, &image_size); if (!image) return -errno; } errno = 0; // 最后调用到init_module init_module(image, image_size, options); rc = errno; if (mmaped) munmap(image, image_size); else free(image); return rc;} |
最后都会通过系统调用调用到__NR_init_module 或 __NR_finit_module

