本文约3300字,上一篇我们学习了《Linux内核模块完整技术详解:原理、工具、选型(内置vs模块)》,本文整理了十道高频面试题,复习学过的内容。
关注公众号, 即可获得与Linux相关的电子书籍(含《深入Linux内核架构》中文版和Linux设备驱动开发详解基于最新的Linux4.0内核)以及常用开发工具,文末有文档清单。
内核模块是Linux驱动面试必考核心考点,覆盖基础概念、用户态工具、内核底层原理、编译选型、GPL许可证、故障排查、版本校验、自动加载等维度,兼顾校招基础题与社招深度底层题,适合面试前集中背诵复习。
vmlinux内核镜像,开机永久加载;模块生成独立.ko文件,运行时动态加载、无引用可卸载。vermagic魔数、符号CRC,内核配置不匹配直接加载失败。CONFIG_MODULES彻底禁用动态模块,消除攻击面;模块存在动态加载攻击风险。init_module系统调用,仅加载单个.ko文件;不会自动解析依赖,依赖模块未提前加载则直接报错,需要传入完整文件路径。delete_module系统调用卸载指定模块;若存在其他模块依赖、设备占用资源则卸载失败。modules.dep,递归加载目标模块所有前置依赖;支持按模块名加载(无需完整路径),modprobe -r可批量卸载依赖模块。.ko文件,生成两份关键文件:modules.dep:记录每个模块的依赖列表;modules.alias:根据MODULE_DEVICE_TABLE生成设备匹配别名,为udev自动加载提供匹配规则。自动加载核心依靠内核request_module函数 + 用户态modprobe协作,整体流程:
request_module(模块标识);call_usermodehelper创建用户态辅助进程,默认执行/sbin/modprobe;modules.alias匹配设备MODALIAS或功能别名,再读取modules.dep加载全部依赖模块;EXPORT_SYMBOL和EXPORT_SYMBOL_GPL区别?闭源模块调用GPL导出符号会有什么后果?规避方案与法律风险EXPORT_SYMBOL():导出的符号所有模块均可调用,无许可证限制;EXPORT_SYMBOL_GPL():仅声明GPL兼容许可证(MODULE_LICENSE="GPL")的模块能够访问。闭源专有模块调用GPL符号的后果 模块加载失败,同时内核打上TAINT_PROPRIETARY_MODULE污染标记;内核panic、oops日志会标注内核被闭源模块污染,内核社区不受理此类系统bug反馈。
行业规避方案与风险 方案:编写一层GPL开源中间wrapper模块,wrapper调用内核GPL符号,再用普通EXPORT_SYMBOL导出接口给闭源模块调用。 法律风险:Linux创始人Linus明确指出,该行为属于蓄意规避GPL协议约束,存在版权侵权、民事赔偿、禁令诉讼风险,企业不推荐使用。
load_module解析ELF符号表,识别SHN_UNDEF未定义符号;__find_symbol按优先级检索符号:内核全局符号表 → 已加载普通模块符号 → GPL专属符号;check_version对比符号CRC校验和,接口不一致直接拒绝加载;use_module建立双向依赖链表,增加被依赖模块引用计数。存放在模块.modinfo段,字符串包含内核版本、SMP开关、抢占配置、CPU架构、编译器版本;模块魔数和当前内核不匹配直接拦截加载,防止架构/配置不一致导致内核崩溃。
编译阶段genksyms工具为每个导出函数生成唯一CRC校验和,写入__versions段;加载时对比模块与内核符号CRC,只要函数参数/结构体定义发生变更,CRC就会改变,阻止不兼容模块加载。
__init、__exit宏作用必备4个核心组件,缺一不可: ① module_init(xxx_init):模块加载入口初始化函数; ② module_exit(xxx_exit):模块卸载资源清理函数; ③ MODULE_LICENSE():许可证声明,缺失会污染内核; ④ 头文件<linux/init.h>、<linux/module.h>。
__init作用 将函数放入.init.text段,模块初始化完成后内核自动回收该段内存,节省常驻空间;若代码内置进内核,系统启动后直接丢弃。
__exit作用 仅模块编译时生效,函数放入.exit.text;若代码静态内置,链接器直接丢弃该段,无需清理逻辑。
极简示例代码:
#include<linux/init.h>#include<linux/module.h>staticint __init hello_init(void){ printk(KERN_INFO "模块加载成功\n");return0;}module_init(hello_init);staticvoid __exit hello_exit(void){ printk(KERN_INFO "模块卸载成功\n");}module_exit(hello_exit);MODULE_LICENSE("GPL v2");MODULE_AUTHOR("求职者");MODULE_DESCRIPTION("面试Demo模块");module is in use模块无法卸载,列举3类高频原因与排查方法lsmod | grep 模块名查看Used by字段,显示依赖的模块列表;场景:挂载vfat分区、打开设备驱动文件、程序占用字符设备;
lsof /dev/xxx、查看挂载表mount;场景:代码遗漏module_put、未注销定时器/工作队列/内核线程、设备owner指针未释放;
/sys/module/xxx/refcnt引用计数值,打印dmesg内核日志;try_module_get/module_put。.ko是ELF文件,列举模块特有的ELF段及各自作用.gnu.linkonce.this_module:存储struct module结构体实例,内核识别合法内核模块的唯一标识,缺失则拒绝加载;.modinfo:存放模块元数据,包含许可证、vermagic、依赖列表、模块别名、参数,modinfo工具读取该段;__ksymtab / __ksymtab_gpl:普通/GPL导出符号表,记录函数名称与内存地址;__kcrctab / __versions:存储符号CRC校验和,用于MODVERSIONS版本校验;.init.text / .exit.text:初始化、卸载独立代码段,按需释放;__param:模块启动参数元数据,支持加载时传参。struct module每个加载的模块对应一个实例,核心成员:try_module_get/module_put维护;struct module_use建立双向关联:module_use节点;module_which_uses指向B,挂载到A的modules_which_use_me链表;module_param(变量名, 类型, 权限):基础单参数;module_param_array(数组名, 类型, 长度指针, 权限):数组参数; 支持类型:int、charp(字符串)、bool、ulong等。示例:
staticchar *dev_name = "default";module_param(dev_name, charp, S_IRUGO); // 只读权限两种加载传参方式 ① 手动加载:insmod demo.ko dev_name="usb0"; ② 内置模块:内核启动cmdline传入demo.dev_name="usb0"。
用户态查看入口 模块加载后在/sys/module/模块名/parameters/生成对应文件,cat文件即可读取当前参数值;权限为0则不生成sysfs节点。

这里是女程序员的笔记本
15年+嵌入式软件工程师兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助