本文约4000字,这两天差不多移植完了AX615的SDK到新版本V3.0.0,上一版本SDK的驱动都是编译成模块加载的,这一版又变成内置了,内核变大了一些,本文结合《深入Linux内核架构》中文版第7章 模块以及《Linux设备驱动开发详解基于最新的Linux4.0内核》这本书第4章 Linux内核模块的内容,来详细整理下Linux内核模块技术,深入理解一下Linux内核模块的原理,工具以及选型。
关注公众号, 即可获得与Linux相关的电子书籍(含《深入Linux内核架构》中文版和Linux设备驱动开发详解基于最新的Linux4.0内核)以及常用开发工具,文末有文档清单。

内核模块(LKM,Loadable Kernel Module)是运行在内核地址空间、可动态加载/卸载的独立代码单元,文件后缀为.ko,基于ELF可重定位目标文件格式,无需重新编译、重启内核即可扩展内核功能(驱动、文件系统、协议栈等)。 传统宏内核必须把所有功能静态编译进镜像,存在内核臃肿、新增功能必须重编重启的缺陷;模块机制完美解决动态扩展痛点,也是Linux生态普及的关键特性。
insmod/rmmod反复测试,无需全量编译内核、频繁重启机器;崩溃仅隔离模块,不易整机panic。kmod/udev自动加载,U盘、显卡、网卡热插拔时自动匹配对应.ko,无需手动操作。.ko驱动,解决专有硬件适配难题(但会标记内核tainted污染)。vfat依赖fat),代码复用、降低内存占用、简化维护。EXPORT_SYMBOL_GPL导出内核符号,且污染内核日志;完整模块必须包含加载函数、卸载函数、许可证声明,参数、导出符号、设备表为可选扩展:
#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");关键宏说明:
__init / .init.text:初始化代码段,模块加载完成后内核自动回收内存;__exit / .exit.text:仅编译为模块时保留,内置内核会直接丢弃该段;MODULE_LICENSE:内核区分GPL兼容/专有模块的核心依据,专有模块触发TAINT_PROPRIETARY_MODULE标记;EXPORT_SYMBOL/EXPORT_SYMBOL_GPL:导出符号供其他模块调用,后者仅允许GPL模块使用。.ko是特殊ELF目标文件,除标准代码/数据段外,内核新增专属段存储元数据:
.gnu.linkonce.this_module | struct module实例,内核识别合法模块的唯一标识 |
.modinfo | modinfo读取来源) |
__ksymtab / __ksymtab_gpl | |
__kcrctab / __versions | |
.init.text / .exit.text | |
__param |
内核每加载一个模块,分配struct module结构体管理全部状态:
MODULE_STATE_COMING(加载中)、MODULE_STATE_LIVE(正常运行)、MODULE_STATE_GOING(卸载中);modules_which_use_me记录哪些模块依赖当前模块,配合struct module_use双向维护依赖关系;try_module_get/module_put增减引用,有依赖时禁止卸载;insmod xxx.ko:仅加载单个模块,不会自动解析依赖,依赖未加载则失败,底层调用init_module系统调用;modprobe xxx:自动读取/lib/modules/$(uname -r)/modules.dep依赖文件,递归加载所有前置依赖,底层仍调用insmod;sys_init_module):resolve_symbol解析未定义引用(查找内核导出符号、已加载模块符号,校验CRC);init初始化函数;.init段内存,将模块加入全局模块链表。底层调用delete_module系统调用:
struct module;modules_which_use_me链表,若有其他模块依赖,返回-EWOULDBLOCK拒绝卸载;exit清理函数;内核检测缺失功能时(挂载未知文件系统、识别新USB设备)调用request_module:
/sbin/modprobe;modules.alias(由depmod根据MODULE_DEVICE_TABLE生成);insmod | |
rmmod | |
modprobe | |
depmod | .ko,生成modules.dep依赖、modules.alias别名,内核升级后必执行 |
modinfo | .modinfo段,打印许可证、参数、设备别名、vermagic |
lsmod | /proc/modules,展示已加载模块、占用内存、引用计数 |
file /proc/kallsyms |
在内核make menuconfig配置中,每个驱动/功能存在三种编译选项:
Y(*):内置,编译进vmlinux内核镜像,开机常驻内存;M:模块,编译为独立.ko文件,运行时按需加载;N:禁用,完全不编译该功能。满足开机流程依赖、无模块无法正常启动的功能,强制内置:
.ko,无法挂载/直接启动失败。CONFIG_MODULES=n),所有驱动内置,消除动态加载攻击面。/lib/modules分区,无法存放.ko,全部功能内置。非开机必需、硬件按需存在、需要频繁更新调试的功能,优先模块:
.ko分发,无需重编内核即可升级驱动版本。.ko热更新无需重启服务器。initramfs临时文件系统,内核启动初期从内存加载模块,挂载根文件系统后可卸载;KERNELDIR ?= /lib/modules/$(shell uname -r)/buildobj-m += hello.oall: make -C $(KERNELDIR) M=$(PWD) modulesclean: make -C $(KERNELDIR) M=$(PWD) cleanobj-$(CONFIG_HELLO) += hello.o,CONFIG_HELLO=m生成模块,=y内置;EXPORT_SYMBOL_GPL符号的模块必须声明MODULE_LICENSE("GPL"),否则加载报错、内核污染;厂商封装GPL中间模块转发GPL符号给专有模块存在法律争议;CONFIG_MODVERSIONS,通过CRC校验防止新旧内核接口不兼容模块加载崩溃。struct module管理、kmod自动加载实现驱动按需运行,大幅提升内核灵活性;
这里是女程序员的笔记本
15年+嵌入式软件工程师兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助