当前位置:首页>Linux>Linux内核模块完整技术详解:原理、工具、选型(内置vs模块)

Linux内核模块完整技术详解:原理、工具、选型(内置vs模块)

  • 2026-10-11 06:38:47
Linux内核模块完整技术详解:原理、工具、选型(内置vs模块)
Hello,大家好,我是程序媛MM。

本文约4000字,这两天差不多移植完了AX615的SDK到新版本V3.0.0,上一版本SDK的驱动都是编译成模块加载的,这一版又变成内置了,内核变大了一些,本文结合《深入Linux内核架构》中文版第7章 模块以及《Linux设备驱动开发详解基于最新的Linux4.0内核》这本书第4章 Linux内核模块的内容,来详细整理下Linux内核模块技术,深入理解一下Linux内核模块的原理,工具以及选型。

关注公众号, 即可获得与Linux相关的电子书籍(含《深入Linux内核架构》中文版和Linux设备驱动开发详解基于最新的Linux4.0内核)以及常用开发工具,文末有文档清单。


一 什么是Linux内核模块

1. 核心定义

内核模块(LKM,Loadable Kernel Module)是运行在内核地址空间、可动态加载/卸载的独立代码单元,文件后缀为.ko,基于ELF可重定位目标文件格式,无需重新编译、重启内核即可扩展内核功能(驱动、文件系统、协议栈等)。 传统宏内核必须把所有功能静态编译进镜像,存在内核臃肿、新增功能必须重编重启的缺陷;模块机制完美解决动态扩展痛点,也是Linux生态普及的关键特性。

2. 模块核心优缺点

优势

  1. 内核轻量化:发行版可预编译海量驱动为模块,内核镜像只保留核心基础组件,减小内核体积、缩短启动时间,不用的模块不占用常驻内存。
  2. 动态迭代调试:开发新驱动/实验性功能时,仅单独编译模块,insmod/rmmod反复测试,无需全量编译内核、频繁重启机器;崩溃仅隔离模块,不易整机panic。
  3. 硬件即插即用基础:配合kmod/udev自动加载,U盘、显卡、网卡热插拔时自动匹配对应.ko,无需手动操作。
  4. 许可证兼容折中:硬件厂商不开放源码时,可发布二进制.ko驱动,解决专有硬件适配难题(但会标记内核tainted污染)。
  5. 灵活依赖管理:多模块可拆分公共逻辑(如vfat依赖fat),代码复用、降低内存占用、简化维护。

短板

  1. 模块加载需符号重定位、依赖解析,存在微小运行开销;
  2. 非GPL模块无法调用EXPORT_SYMBOL_GPL导出内核符号,且污染内核日志;
  3. 模块与内核版本、编译配置(SMP、抢占、架构)必须严格匹配,否则加载失败;
  4. 动态加载存在极小安全攻击面,高安全设备会禁用模块支持。

二 模块底层核心原理(结合两本教材内容)

2.1 标准模块代码结构

完整模块必须包含加载函数、卸载函数、许可证声明,参数、导出符号、设备表为可选扩展:

#include<linux/init.h>#include<linux/module.h>// 模块加载函数:__init标记,初始化完成后内存可释放staticint __init hello_init(void){    printk(KERN_INFO "Module load\n");return0;}module_init(hello_init); // 注册入口// 模块卸载函数:__exit标记,仅模块编译时生效staticvoid __exit hello_exit(void){    printk(KERN_INFO "Module unload\n");}module_exit(hello_exit); // 注册清理逻辑// 强制许可证,缺失会污染内核MODULE_LICENSE("GPL v2");MODULE_AUTHOR("Dev");MODULE_DESCRIPTION("Demo Module");MODULE_ALIAS("demo");

关键宏说明:

  1. __init / .init.text:初始化代码段,模块加载完成后内核自动回收内存;
  2. __exit / .exit.text:仅编译为模块时保留,内置内核会直接丢弃该段;
  3. MODULE_LICENSE:内核区分GPL兼容/专有模块的核心依据,专有模块触发TAINT_PROPRIETARY_MODULE标记;
  4. EXPORT_SYMBOL/EXPORT_SYMBOL_GPL:导出符号供其他模块调用,后者仅允许GPL模块使用。

2.2 模块ELF二进制特殊段

.ko是特殊ELF目标文件,除标准代码/数据段外,内核新增专属段存储元数据:

段名
作用
.gnu.linkonce.this_module
存储struct module实例,内核识别合法模块的唯一标识
.modinfo
存放许可证、作者、依赖、vermagic版本魔数、模块别名(modinfo读取来源)
__ksymtab / __ksymtab_gpl
普通/GPL导出符号表,记录函数名与内存地址
__kcrctab / __versions
符号CRC校验和,实现MODVERSIONS版本校验,防止接口不兼容模块加载
.init.text / .exit.text
初始化、卸载独立代码段
__param
模块启动参数元数据

2.3 内核侧数据管理:struct module

内核每加载一个模块,分配struct module结构体管理全部状态:

  1. 状态枚举:MODULE_STATE_COMING(加载中)、MODULE_STATE_LIVE(正常运行)、MODULE_STATE_GOING(卸载中);
  2. 符号管理:区分全局导出符号、仅GPL可用符号,配套CRC校验数组;
  3. 依赖链表:modules_which_use_me记录哪些模块依赖当前模块,配合struct module_use双向维护依赖关系;
  4. 引用计数:SMP多核独立计数,try_module_get/module_put增减引用,有依赖时禁止卸载;
  5. 版本魔数vermagic:记录内核编译配置(SMP、抢占、架构、编译器),模块与内核魔数不一致直接拒绝加载。

2.4 模块加载、卸载完整流程

1)手动加载:insmod / modprobe

  • insmod xxx.ko:仅加载单个模块,不会自动解析依赖,依赖未加载则失败,底层调用init_module系统调用;
  • modprobe xxx:自动读取/lib/modules/$(uname -r)/modules.dep依赖文件,递归加载所有前置依赖,底层仍调用insmod;
  • 加载核心步骤(sys_init_module):
    1. 用户态模块二进制拷贝至内核临时内存; . 解析ELF各段、校验vermagic版本;
    2. 分配核心/初始化两块内核内存,拷贝代码数据;
    3. 遍历符号表,通过resolve_symbol解析未定义引用(查找内核导出符号、已加载模块符号,校验CRC);
    4. 建立模块间依赖链表、更新内核全局符号表;
    5. 执行模块init初始化函数;
    6. 释放.init段内存,将模块加入全局模块链表。

2)卸载:rmmod / modprobe -r

底层调用delete_module系统调用:

  1. 根据模块名遍历链表找到struct module;
  2. 检查modules_which_use_me链表,若有其他模块依赖,返回-EWOULDBLOCK拒绝卸载;
  3. 调用模块exit清理函数;
  4. 释放模块核心、percpu、参数内存,移除符号、依赖链表节点。

3)自动加载(kmod机制)

内核检测缺失功能时(挂载未知文件系统、识别新USB设备)调用request_module:

  1. 内核创建用户态helper进程,执行/sbin/modprobe;
  2. 设备匹配依赖modules.alias(由depmod根据MODULE_DEVICE_TABLE生成);
  3. udev热插拔复用该机制,识别设备MODALIAS自动加载对应驱动模块,实现即插即用。

2.5 配套用户空间工具链

工具
功能
insmod
直接加载单模块,无依赖处理
rmmod
卸载指定模块
modprobe
自动处理依赖,支持加载/批量卸载
depmod
扫描所有.ko,生成modules.dep依赖、modules.alias别名,内核升级后必执行
modinfo
读取.modinfo段,打印许可证、参数、设备别名、vermagic
lsmod
读取/proc/modules,展示已加载模块、占用内存、引用计数
file /proc/kallsyms
内核全局符号表,存放所有内核+模块导出符号地址

三 两种编译模式:内置Built-in(Y) vs 模块Module(M)

在内核make menuconfig配置中,每个驱动/功能存在三种编译选项:

  • Y(*):内置,编译进vmlinux内核镜像,开机常驻内存;
  • M:模块,编译为独立.ko文件,运行时按需加载;
  • N:禁用,完全不编译该功能。

3.1 核心特性对比表

对比维度
内置编译(CONFIG=y)
模块编译(CONFIG=m)
存储位置
内核镜像vmlinux内
/lib/modules/内核版本/
加载时机
内核启动时自动载入
按需手动/自动加载
卸载能力
永久常驻,无法卸载
无引用时可动态卸载,释放内存
内核体积
增大,所有内置代码常驻
内核镜像精简,不用模块不占内存
迭代成本
修改功能必须全量重编内核、重启
单独编译模块,热更新无需重启
依赖处理
编译时静态链接,无运行时依赖问题
运行时解析依赖,依赖缺失加载失败
启动速度
基础功能无需动态加载,启动更快
冷启动更快(内核更小),首次加载模块有延迟
内存开销
全程占用内存,无法回收
闲置可卸载释放内存,适合多硬件场景
符号访问
直接调用内核内部符号无限制
非GPL模块无法使用GPL专属导出符号
版本校验
与内核一体,无版本不兼容问题
vermagic、符号CRC双重校验,不匹配拒绝加载
适用系统
嵌入式精简固件、安全专用系统
桌面、服务器、通用发行版

四 选型标准:什么时候编译进内核,什么时候做模块

4.2 必须编译为内置(Y)的场景

满足开机流程依赖、无模块无法正常启动的功能,强制内置:

  1. 根文件系统底层驱动SATA/AHCI、MMC、NAND、NVMe存储控制器,ext4/btrfs根文件系统驱动;若编译为模块,内核无程序加载.ko,无法挂载/直接启动失败。
  2. initramfs依赖组件内核镜像压缩算法(lz4/gzip)、内存磁盘ramfs、基础块设备层,用于解压初始根文件系统。
  3. 系统基础调试/控制台串口控制台、早期printk日志输出驱动,嵌入式无显示器时唯一调试通道。
  4. 内核核心基础子系统进程调度、内存管理、基础中断控制器、基础TCP/IP协议栈,内核运行根基,不支持模块化。
  5. 高安全隔离设备工控安全网关、加密终端、无文件系统极简固件:禁用模块加载(CONFIG_MODULES=n),所有驱动内置,消除动态加载攻击面。
  6. 极低资源无文件系统设备RAM<64MB微型单片机嵌入式,无/lib/modules分区,无法存放.ko,全部功能内置。
  7. 实时性硬实时系统动态加载模块存在内存分配、符号解析延迟,实时业务驱动内置消除抖动。

4.3 推荐编译为模块(M)的场景

非开机必需、硬件按需存在、需要频繁更新调试的功能,优先模块:

  1. 热插拔外设驱动USB网卡、摄像头、U盘、蓝牙、PCIe扩展显卡、声卡,设备即插即用,不用时卸载释放内存。
  2. 小众/可选文件系统vfat/NTFS/exFAT/ISO9660/加密文件系统,服务器极少使用,内置持续浪费内存。
  3. 第三方专有硬件驱动显卡、网卡厂商闭源二进制驱动,以.ko分发,无需重编内核即可升级驱动版本。
  4. 实验性、调试功能性能追踪、内核调试模块、新硬件测试驱动,开发阶段频繁加载卸载,崩溃不影响整机。
  5. 多硬件兼容通用发行版(Ubuntu/CentOS)发行版需兼容万千硬件,全部驱动内置会导致内核镜像数十MB,模块化仅加载本机硬件驱动。
  6. 低频使用功能RAID、IPVS、Netfilter额外匹配模块、小众网络协议,仅特定业务启用。
  7. 需要在线更新的组件服务器网卡、GPU驱动,业务7*24不能停机,替换.ko热更新无需重启服务器。

4.4 折中特殊场景

  1. 开机必需但希望模块化:放入initramfs临时文件系统,内核启动初期从内存加载模块,挂载根文件系统后可卸载;
  2. 嵌入式资源适中:核心存储内置,外设驱动模块存放于rootfs分区,兼顾启动速度与内存占用。

五 模块开发与编译实操要点

  1. 独立模块Makefile(out-of-tree)
KERNELDIR ?= /lib/modules/$(shell uname -r)/buildobj-m += hello.oall:    make -C $(KERNELDIR) M=$(PWD) modulesclean:    make -C $(KERNELDIR) M=$(PWD) clean
  1. 内核内模块(in-tree):目录Kbuild写入obj-$(CONFIG_HELLO) += hello.o,CONFIG_HELLO=m生成模块,=y内置;
  2. 许可证红线:使用EXPORT_SYMBOL_GPL符号的模块必须声明MODULE_LICENSE("GPL"),否则加载报错、内核污染;厂商封装GPL中间模块转发GPL符号给专有模块存在法律争议;
  3. 版本兼容:开启CONFIG_MODVERSIONS,通过CRC校验防止新旧内核接口不兼容模块加载崩溃。

六 总结

  1. 内核模块是Linux动态扩展核心机制,依托ELF特殊段、struct module管理、kmod自动加载实现驱动按需运行,大幅提升内核灵活性;
  2. 内置编译保障启动可靠性、消除动态开销,适合根存储、安全、极简嵌入式;模块编译轻量化、易迭代、节省常驻内存,适配通用桌面服务器、外设驱动开发;
  3. 工程选型核心原则:开机不可缺失的基础组件内置,按需、可变、外设类功能模块化,嵌入式可配合initramfs折中平衡启动与内存开销。
往期文章(欢迎订阅技术分享栏目全部文章):

【从零开始撸内核驱动源码】:以ttyserial(串口驱动)为例,串联字符设备驱动基础知识点的学习计划
Linux内核源码顶层 Makefile分析并单独编译调试内核自带的驱动
【从零开始撸内核驱动源码】:ttynull驱动
Linux内核驱动安装失败问题调试及解决方法
Linux内核驱动源码走读之编译内核及外部驱动实操指南
“谢谢你看到这里”

这里是女程序员的笔记本

 15年+嵌入式软件工程师兼二胎宝妈

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助

最新文章

随机文章