kernel/module 内核模块(Loadable Kernel Module)机制与原理详解
源码路径:rk3588/kernel-6.1/kernel/module/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)
该目录实现 Linux 可加载内核模块(LKM) 的核心运行时:模块的加载、重定位、符号解析、初始化/卸载、权限与安全校验,以及 sysfs/proc 管理接口。驱动、文件系统、网络协议栈等大量功能以 .ko 模块形式动态插入内核。
定位:连接用户态 insmod/modprobe/rmmod 与内核 ELF 加载器的桥梁;架构相关重定位在 arch/arm64/kernel/module.c,通用逻辑在本目录。
目录
一、源码目录结构
1.1 编译依赖(Makefile)
1 2 3 4 5 6 7 8 9 10 11 12 13
| obj-y += main.o strict_rwx.o
obj-$(CONFIG_MODULE_DECOMPRESS) += decompress.o obj-$(CONFIG_MODULE_SIG) += signing.o obj-$(CONFIG_LIVEPATCH) += livepatch.o obj-$(CONFIG_MODULES_TREE_LOOKUP) += tree_lookup.o obj-$(CONFIG_DEBUG_KMEMLEAK) += debug_kmemleak.o obj-$(CONFIG_KALLSYMS) += kallsyms.o obj-$(CONFIG_PROC_FS) += procfs.o obj-$(CONFIG_SYSFS) += sysfs.o obj-$(CONFIG_KGDB_KDB) += kdb.o obj-$(CONFIG_MODVERSIONS) += version.o obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING)+= tracking.o
|
| 配置项 |
说明 |
CONFIG_MODULES |
启用可加载模块支持(总开关) |
CONFIG_MODVERSIONS |
符号 CRC 版本校验 |
CONFIG_MODULE_SIG |
模块 PKCS#7 签名验证 |
CONFIG_MODULE_SIG_FORCE |
拒绝未签名模块 |
CONFIG_STRICT_MODULE_RWX |
模块 text/ro 只读、data 不可执行 |
CONFIG_MODULE_UNLOAD |
允许卸载模块 |
CONFIG_MODULE_DECOMPRESS |
内核内解压 .ko.gz/.ko.xz |
CONFIG_MODULES_TREE_LOOKUP |
latch tree 加速 __module_address() |
CONFIG_TRIM_UNUSED_KSYMS |
裁剪未使用导出符号(与 livepatch 互斥) |
Kconfig 位于 kernel/module/Kconfig。
1.2 源文件
| 文件 |
行数 |
功能 |
main.c |
3149 |
核心:加载/卸载/符号/系统调用/通知链 |
kallsyms.c |
532 |
模块符号表构建与查询 |
sysfs.c |
436 |
/sys/module/<name>/ 属性与参数 |
decompress.c |
276 |
GZIP/XZ 模块解压 |
internal.h |
305 |
内部 API、load_info、mod_tree |
strict_rwx.c |
143 |
模块内存 RWX 权限控制 |
procfs.c |
146 |
/proc/modules |
tracking.c |
132 |
已卸载 taint 模块追踪 |
signing.c |
125 |
签名验证 |
tree_lookup.c |
117 |
latch tree 地址→模块查找 |
version.c |
101 |
modversion CRC 校验 |
livepatch.c |
74 |
livepatch 模块 ELF 持久化 |
kdb.c |
62 |
KGDB/KDB 模块支持 |
debug_kmemleak.c |
30 |
kmemleak 模块区域注册 |
架构相关(目录外):
| 文件 |
功能 |
arch/arm64/kernel/module.c |
module_alloc、AArch64 ELF 重定位 |
arch/arm64/kernel/kaslr.c |
模块区域 KASLR 随机化 |
include/linux/module.h |
struct module、公共 API |
include/linux/moduleloader.h |
架构 loader 接口 |
kernel/module/ 外:kernel/kmod.c |
内核侧 request_module |
二、整体架构
2.1 用户态到内核态路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 用户态 内核态 ────── ────── insmod foo.ko args → init_module() ──────→ copy_module_from_user() modprobe foo load_module() → do_init_module() → mod->init()
modprobe -r foo → delete_module() ──────→ mod->exit() → free_module()
finit_module(fd, args, flags) → kernel_read_file_from_fd() → [module_decompress()] → load_module()
|
2.2 模块与子系统关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌─────────────────────────────────────────────────────────────┐ │ 用户态: insmod / modprobe / rmmod │ └──────────────────────────┬──────────────────────────────────┘ │ init_module / finit_module / delete_module ▼ ┌─────────────────────────────────────────────────────────────┐ │ kernel/module/main.c │ │ load_module() → ELF 解析 → 重定位 → 符号 → init │ └──────┬──────────────┬──────────────┬────────────────────────┘ │ │ │ ▼ ▼ ▼ signing.c strict_rwx.c kallsyms.c version.c decompress.c sysfs.c / procfs.c │ │ │ ▼ ▼ ▼ arch/arm64/ ftrace/klp/ /sys/module/ kernel/module.c jump_label /proc/modules
|
2.3 全局同步
1 2 3
| DEFINE_MUTEX(module_mutex); LIST_HEAD(modules); struct mod_tree_root mod_tree;
|
RCU 读路径:find_symbol()、__module_address() 可在 preempt_disable() 或持 module_mutex 下无锁遍历。
三、核心数据结构
3.1 struct module(include/linux/module.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct module { enum module_state state; struct list_head list; char name[MODULE_NAME_LEN];
struct module_layout core_layout; struct module_layout init_layout; struct module_layout data_layout;
struct module *init; void (*exit)(void);
const struct kernel_symbol *syms, *gpl_syms; unsigned int num_syms, num_gpl_syms; const s32 *crcs, *gpl_crcs;
atomic_t refcnt; struct list_head source_list; struct list_head target_list;
struct module_kobject mkobj; struct mod_tree_node mtn; };
|
3.2 struct module_layout
1 2 3 4 5 6 7 8
| struct module_layout { void *base; unsigned int size; unsigned int text_size; unsigned int ro_size; unsigned int ro_after_init_size; struct mod_tree_node mtn; };
|
内存分区(CONFIG_STRICT_MODULE_RWX):
1
| [ text (X) | ro-data (R) | ro-after-init (R) | writable data (RW, NX) ]
|
3.3 struct load_info(internal.h)
加载过程中的临时上下文:
1 2 3 4 5 6 7 8 9 10 11
| struct load_info { const char *name; struct module *mod; Elf_Ehdr *hdr; unsigned long len; Elf_Shdr *sechdrs; char *secstrings, *strtab; struct _ddebug_info dyndbg; bool sig_ok; struct { unsigned int sym, str, mod, vers, info, pcpu; } index; };
|
四、模块状态机
1 2 3 4 5 6
| enum module_state { MODULE_STATE_LIVE, MODULE_STATE_COMING, MODULE_STATE_GOING, MODULE_STATE_UNFORMED, };
|
| 状态 |
含义 |
可见性 |
UNFORMED |
ELF 解析/重定位中 |
仅在 modules 链表,kallsyms 不可见 |
COMING |
符号已就绪,init 未返回 |
kallsyms 可见,try_module_get 失败 |
LIVE |
init 成功 |
完全可用 |
GOING |
exit 已调用或加载失败 |
正在 tear down |
状态迁移:
1 2 3 4
| UNFORMED → complete_formation() → COMING COMING → do_init_module() 成功 → LIVE LIVE → delete_module() → GOING → free_module() 任意 → 加载失败路径 → GOING → free_module()
|
五、加载流程(load_module)
load_module() 是加载核心,由 init_module / finit_module 系统调用触发。
5.1 主要步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 1. module_sig_check() ← 签名验证,裁剪 sig 长度 2. elf_validity_check() ← ELF 头/段合法性 3. setup_load_info() ← 解析 section 索引 4. blacklist / rewrite_section_headers() 5. check_modstruct_version() ← module_layout CRC 6. layout_and_allocate() ← layout_sections + move_module 7. add_unformed_module() ← 加入 modules 链表 (UNFORMED) 8. percpu_modalloc() 9. find_module_sections() ← __ksymtab, __param, ftrace, jump_label... 10. check_module_license_and_versions() 11. setup_modinfo() 12. simplify_symbols() ← 解析未定义符号 13. apply_relocations() ← 架构重定位 14. post_relocation() ← extable排序, percpu, kallsyms, module_finalize 15. flush_module_icache() 16. complete_formation() → COMING 17. prepare_coming_module() ← ftrace, klp_module_coming, notifier 18. parse_args() ← 模块参数 19. mod_sysfs_setup() 20. do_init_module() → LIVE
|
5.2 layout_sections — 段布局
按优先级分配 core / init / data 区域:
| 轮次 |
段属性 |
目标 layout |
| 0 |
SHF_EXECINSTR | SHF_ALLOC |
core text |
| 1 |
SHF_ALLOC(非 WRITE) |
data ro |
| 2 |
SHF_RO_AFTER_INIT |
data ro-after-init |
| 3 |
SHF_WRITE | SHF_ALLOC |
data writable |
| init 段 |
.init*, __init* 等 |
init_layout(带 INIT_OFFSET_MASK) |
5.3 move_module — 内存拷贝
1 2 3 4
| mod->core_layout.base = module_alloc(core_size); mod->init_layout.base = module_alloc(init_size);
|
5.4 find_module_sections — 特殊段
解析并记录模块内各类元数据段:
| 段名 |
用途 |
__ksymtab / __ksymtab_gpl |
模块导出符号 |
__param |
模块参数 |
__ex_table |
异常表 |
__jump_table |
jump label |
__tracepoints_ptrs |
tracepoint |
__dyndbg |
动态 debug |
.kprobes.text |
kprobes |
.static_call_sites |
static call |
.klp.rela.* |
livepatch 重定位 |
六、初始化与卸载
6.1 do_init_module()
1 2 3 4 5 6 7 8 9 10 11 12
| do_mod_ctors(mod); ret = mod->init(); mod->state = MODULE_STATE_LIVE; blocking_notifier_call_chain(..., MODULE_STATE_LIVE, mod); kobject_uevent(..., KOBJ_ADD);
ftrace_free_mem(mod, init_base, init_end); rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms); module_enable_ro(mod, true); mod_tree_remove_init(mod); schedule_work(&init_free_wq);
|
init 段延迟释放:kallsyms 可能在 RCU 读路径遍历 init 区,故通过 workqueue + synchronize_rcu() 异步 module_memfree()。
6.2 delete_module 系统调用
1 2 3 4 5 6 7 8 9
| 1. CAP_SYS_MODULE + !modules_disabled 2. find_module(name) 3. 检查 source_list 非空 → -EWOULDBLOCK(有被依赖) 4. state == LIVE 5. 有 init 无 exit → 需 force(-f) 6. try_stop_module() → refcnt 归零 → GOING 7. mod->exit() 8. notifier GOING + klp_module_going + ftrace_release_mod 9. free_module()
|
6.3 引用计数
1 2 3 4
| atomic_t refcnt; try_module_get() module_put() ref_module()
|
module_use 链表:模块 A 使用模块 B 的符号 → A.source_list → B,卸载 B 前须先卸载所有依赖 A。
七、符号解析与版本校验
7.1 find_symbol()
1 2 3 4 5
| bool find_symbol(struct find_symbol_arg *fsa) { }
|
搜索顺序:内核普通导出 → 内核 GPL 导出 → 各已形成模块的普通 → GPL。
7.2 resolve_symbol()
1 2 3 4 5 6
| find_symbol(name) → GPL 许可检查(proprietary 模块不可用 GPL-only 符号) → inherit_taint(专有模块 taint 继承) → check_version(CRC) ← version.c → verify_namespace_is_imported ← EXPORT_SYMBOL_NS → ref_module(mod, owner) ← 建立依赖
|
7.3 modversion(version.c)
1 2 3 4 5 6
| int check_version(info, symname, mod, crc) { }
|
module_layout 符号 CRC 校验 struct module 布局兼容性。
7.4 vermagic
1 2
| static const char vermagic[] = VERMAGIC_STRING;
|
八、内存布局与 RWX 保护(strict_rwx.c)
CONFIG_STRICT_MODULE_RWX 启用时:
| 区域 |
权限 |
| text |
RO + X(via module_enable_x) |
| ro-data / ro-after-init |
RO + NX |
| writable data |
RW + NX |
1 2 3 4 5 6 7 8 9 10
| void module_enable_ro(mod, after_init) { frob_text(..., set_memory_ro); frob_rodata(..., set_memory_ro); if (after_init) frob_ro_after_init(..., set_memory_ro); }
void module_enable_nx(mod) void module_enable_x(mod)
|
module_enforce_rwx_sections() 拒绝同时具有 SHF_WRITE | SHF_EXECINSTR 的段。
九、模块签名(signing.c)
1
| 模块末尾: [ ELF 内容 | PKCS#7 签名 | module_signature 结构 | "~Module signature appended~\n" ]
|
流程:
1 2 3 4 5 6 7
| module_sig_check(info, flags) → 检测 MODULE_SIG_STRING 标记 → mod_verify_sig() → verify_pkcs7_signature() → sig_ok = true / taint TAINT_UNSIGNED_MODULE
is_module_sig_enforced() ← CONFIG_MODULE_SIG_FORCE 或 cmdline → 未签名/无效签名 → -EKEYREJECTED
|
与 Lockdown LSM 联动:lockdown 模式下未签名模块可能被拒绝。
十、模块解压(decompress.c)
finit_module(..., MODULE_INIT_COMPRESSED_FILE) 或内核内置解压:
| 格式 |
实现 |
GZIP (.ko.gz) |
zlib_inflateInit2 + 逐页输出 |
XZ (.ko.xz) |
xz_dec_run |
解压到 load_info.pages[] 页数组,供后续 ELF 解析。用于 load pinning 等需内核自行读模块的场景。
十一、Kallsyms 与地址查找
11.1 kallsyms.c
layout_symtab() — 将符号表移入 init 段布局
add_kallsyms() — 构建 mod->core_kallsyms(symtab + strtab)
- init 完成后切换:
rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms)
11.2 地址→模块查找
tree_lookup.c(CONFIG_MODULES_TREE_LOOKUP,PERF/TRACING 启用):
1 2 3 4 5
| struct module *mod_find(unsigned long addr, struct mod_tree_root *tree) { ltn = latch_tree_find(addr, &mod_tree.root, &mod_tree_ops); }
|
否则线性扫描 modules 链表 + within_module()。
1 2
| struct module *__module_address(unsigned long addr) struct module *__module_text_address(unsigned long addr) /* 仅 text 段 */
|
十二、Sysfs 与 Procfs 接口
12.1 Sysfs(sysfs.c)
1 2 3 4 5 6 7 8 9 10 11
| /sys/module/<name>/ ├── initstate (RO) Live/Loading/Unloading ├── coresize (RO) ├── initsize (RO) ├── refcnt (RO) [CONFIG_MODULE_UNLOAD] ├── taint (RO) ├── version (RO) ├── parameters/ (RW) 模块参数 ├── sections/ (RO) 各段地址 [CONFIG_KALLSYMS] ├── holders/ (RO) 依赖本模块的模块 └── notes/ (RO) ELF notes
|
12.2 Procfs(procfs.c)
/proc/modules 格式:
1
| name size refcnt deps address [taint_flags]
|
例:nvme 123456 0 - Live 0xffffffc008000000
十三、Livepatch 集成(livepatch.c)
livepatch 补丁模块加载时:
1 2 3
| copy_module_elf(mod, info) → 持久化 Elf header、section headers、secstrings → symtab 指向 core_kallsyms.symtab(init 释放后仍可用)
|
配合 kernel/livepatch/core.c 的 klp_apply_section_relocs() 解析 .klp.rela.* 段。
十四、RK3588/ARM64 平台说明
14.1 module_alloc(arch/arm64/kernel/module.c)
1 2 3 4 5 6 7 8 9
| void *module_alloc(unsigned long size) { p = __vmalloc_node_range(size, MODULE_ALIGN, module_alloc_base, module_alloc_base + MODULES_VSIZE, GFP_KERNEL, PAGE_KERNEL, ...); return kasan_reset_tag(p); }
|
- 模块加载于 vmalloc 模块区域(
_etext 下方 MODULES_VSIZE)
- KASLR:
kaslr.c 随机化 module_alloc_base(21-bit 偏移)
- MODULE PLT:静态区域耗尽时 fallback 到 2GB 窗口
- ARCH_HAS_STRICT_MODULE_RWX:ARM64 支持严格 RWX
14.2 AArch64 重定位
arch/arm64/kernel/module.c 处理 ELF 重定位类型:
R_AARCH64_ABS64 / PREL32 / CALL26 / JUMP26
R_AARCH64_ADR_PREL_PG_HI21 等页相对重定位
- alternatives、ftrace、static_call 模块段处理
- CFI 影子类型校验(
module_cfi_finalize)
14.3 RK3588 驱动开发注意点
- 内核与模块须 相同 vermagic 或启用
CONFIG_MODVERSIONS 且 CRC 匹配
- 启用
CONFIG_MODULE_SIG_FORCE 时须用 scripts/sign-file 签名
- 树外模块编译:
make modules + make modules_install;依赖 Kbuild 导出符号
EXPORT_SYMBOL_NS() 须对应 MODULE_IMPORT_NS(),否则加载失败
- 卸载驱动前确认
refcnt=0 且无 holders(lsmod / /sys/module/.../holders/)
14.4 架构 Kconfig
| 配置 |
ARM64 |
ARCH_USE_QUEUED_SPINLOCKS |
✅ |
ARCH_HAS_STRICT_MODULE_RWX |
✅ select |
ARCH_SUPPORTS_ATOMIC_RMW |
✅ |
MODULES_TREE_LOOKUP |
def_y(PERF/TRACING 时) |
十五、完整加载/卸载时序
15.1 加载时序
15.2 卸载时序
1 2 3 4 5 6
| delete_module("foo") → try_stop_module: refcnt==0 → GOING → mod->exit() → klp_module_going, ftrace_release_mod → free_module: sysfs teardown, module_memfree, 从 modules 移除 → wake_up(module_wq)
|
15.3 通知链
1 2
| register_module_notifier() blocking_notifier_call_chain(&module_notify_list, state, mod)
|
通知时机:COMING(prepare)、LIVE(init 成功)、GOING(卸载/失败)。
十六、总结
| 组件 |
职责 |
| main.c |
加载/卸载主流程、系统调用、符号解析、依赖管理 |
| strict_rwx.c |
模块内存 W^X 安全 |
| signing.c |
PKCS#7 签名验证 |
| version.c |
modversion CRC 兼容性 |
| kallsyms.c |
模块符号表 |
| tree_lookup.c |
O(log n) 地址→模块 |
| sysfs.c / procfs.c |
用户态管理接口 |
| decompress.c |
压缩模块解压 |
| livepatch.c |
热补丁 ELF 持久化 |
| tracking.c |
卸载 taint 模块审计 |
内核模块机制将 ELF 对象 安全地映射进运行中内核:通过 vermagic/modversion 保证 ABI 兼容,通过签名与 RWX 保护增强安全,通过 refcnt 与 module_use 保证卸载安全。RK3588 驱动开发几乎完全依赖此框架。
附录:源文件清单
| 文件 |
行数 |
kernel/module/main.c |
3149 |
kernel/module/kallsyms.c |
532 |
kernel/module/sysfs.c |
436 |
kernel/module/decompress.c |
276 |
kernel/module/internal.h |
305 |
kernel/module/strict_rwx.c |
143 |
kernel/module/procfs.c |
146 |
kernel/module/tracking.c |
132 |
kernel/module/signing.c |
125 |
kernel/module/tree_lookup.c |
117 |
kernel/module/version.c |
101 |
kernel/module/livepatch.c |
74 |
kernel/module/kdb.c |
62 |
kernel/module/debug_kmemleak.c |
30 |
kernel/module/Kconfig |
293 |
kernel/module/Makefile |
21 |
| 合计 |
~5628 |
关联源码(目录外):
| 文件 |
行数(约) |
说明 |
arch/arm64/kernel/module.c |
519 |
AArch64 重定位与 module_alloc |
arch/arm64/kernel/kaslr.c |
— |
模块区域 KASLR |
include/linux/module.h |
— |
struct module 定义 |
kernel/kmod.c |
— |
request_module 内核接口 |
scripts/mod/modpost.c |
— |
构建时 modpost 处理 |
正在加载留言…