当前位置:首页>Linux>Linux内核模块高频10道面试题(含答案)

Linux内核模块高频10道面试题(含答案)

  • 2026-10-11 05:37:37
Linux内核模块高频10道面试题(含答案)
Hello,大家好,我是程序媛MM。

本文约3300字,上一篇我们学习了《Linux内核模块完整技术详解:原理、工具、选型(内置vs模块)》,本文整理了十道高频面试题,复习学过的内容。

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


前言

内核模块是Linux驱动面试必考核心考点,覆盖基础概念、用户态工具、内核底层原理、编译选型、GPL许可证、故障排查、版本校验、自动加载等维度,兼顾校招基础题与社招深度底层题,适合面试前集中背诵复习。

题目1:简述LKM内核模块与静态内置(build-in)的区别,分别说明适用场景

参考答案

一、核心区别

  1. 加载方式:内置编译进vmlinux内核镜像,开机永久加载;模块生成独立.ko文件,运行时动态加载、无引用可卸载。
  2. 内存占用:内置全程常驻内存,无法释放;模块不用时可卸载回收内存,通用系统节省资源。
  3. 版本校验:内置和内核一体,无兼容校验;模块携带vermagic魔数、符号CRC,内核配置不匹配直接加载失败。
  4. 迭代成本:修改内置功能必须完整重编内核、重启机器;模块仅单独编译,热更新无需重启。
  5. 依赖机制:内置编译时静态链接,无运行时依赖报错;模块运行时解析依赖,缺少前置模块会加载失败。
  6. 安全特性:极简系统可关闭CONFIG_MODULES彻底禁用动态模块,消除攻击面;模块存在动态加载攻击风险。

二、适用场景

  1. 必须内置(Y)根文件系统存储驱动(NVMe/SATA/MMC)、基础文件系统ext4、串口控制台、内核基础调度/内存子系统、安全工控固件、无文件系统微型嵌入式、硬实时业务驱动。
  2. 推荐编译为模块(M)USB/声卡/显卡等热插拔外设、小众文件系统NTFS/VFAT、实验调试驱动、第三方闭源硬件驱动、服务器低频功能(RAID/Netfilter扩展)、通用发行版海量硬件兼容。

题目2:insmod、rmmod、modprobe三者作用与本质差异是什么?depmod工具的作用?

参考答案

  1. insmod:底层直接调用init_module系统调用,仅加载单个.ko文件;不会自动解析依赖,依赖模块未提前加载则直接报错,需要传入完整文件路径。
  2. rmmod:调用delete_module系统调用卸载指定模块;若存在其他模块依赖、设备占用资源则卸载失败。
  3. modprobe:上层封装工具,自动读取依赖文件modules.dep,递归加载目标模块所有前置依赖;支持按模块名加载(无需完整路径),modprobe -r可批量卸载依赖模块。
  4. depmod:内核升级/新增模块后必须执行,扫描所有.ko文件,生成两份关键文件:
    • modules.dep:记录每个模块的依赖列表;
    • modules.alias:根据MODULE_DEVICE_TABLE生成设备匹配别名,为udev自动加载提供匹配规则。

题目3:内核如何实现模块自动加载?简述kmod机制完整流程

参考答案

自动加载核心依靠内核request_module函数 + 用户态modprobe协作,整体流程:

  1. 内核运行时发现缺失功能:比如挂载vfat分区无对应驱动、热插拔USB设备无匹配驱动,调用request_module(模块标识);
  2. 内核通过call_usermodehelper创建用户态辅助进程,默认执行/sbin/modprobe;
  3. modprobe读取modules.alias匹配设备MODALIAS或功能别名,再读取modules.dep加载全部依赖模块;
  4. 模块加载完成后,内核重试对应操作(挂载文件系统、初始化设备);
  5. 拓展:udev热插拔复用该机制,内核上报uevent事件携带MODALIAS,udev调用modprobe完成驱动自动匹配。

题目4:EXPORT_SYMBOL和EXPORT_SYMBOL_GPL区别?闭源模块调用GPL导出符号会有什么后果?规避方案与法律风险

参考答案

  1. 两者核心区别
  • EXPORT_SYMBOL():导出的符号所有模块均可调用,无许可证限制;
  • EXPORT_SYMBOL_GPL():仅声明GPL兼容许可证(MODULE_LICENSE="GPL")的模块能够访问。
  1. 闭源专有模块调用GPL符号的后果 模块加载失败,同时内核打上TAINT_PROPRIETARY_MODULE污染标记;内核panic、oops日志会标注内核被闭源模块污染,内核社区不受理此类系统bug反馈。

  2. 行业规避方案与风险 方案:编写一层GPL开源中间wrapper模块,wrapper调用内核GPL符号,再用普通EXPORT_SYMBOL导出接口给闭源模块调用。 法律风险:Linux创始人Linus明确指出,该行为属于蓄意规避GPL协议约束,存在版权侵权、民事赔偿、禁令诉讼风险,企业不推荐使用。

题目5:加载模块时内核如何解析未定义符号?MODVERSIONS与vermagic分别是什么作用?

参考答案

1. 未定义符号解析流程

  1. load_module解析ELF符号表,识别SHN_UNDEF未定义符号;
  2. __find_symbol按优先级检索符号:内核全局符号表 → 已加载普通模块符号 → GPL专属符号;
  3. 开启MODVERSIONS时,check_version对比符号CRC校验和,接口不一致直接拒绝加载;
  4. 若符号来自其他模块,调用use_module建立双向依赖链表,增加被依赖模块引用计数。

2. vermagic版本魔数

存放在模块.modinfo段,字符串包含内核版本、SMP开关、抢占配置、CPU架构、编译器版本;模块魔数和当前内核不匹配直接拦截加载,防止架构/配置不一致导致内核崩溃。

3. MODVERSIONS符号版本校验

编译阶段genksyms工具为每个导出函数生成唯一CRC校验和,写入__versions段;加载时对比模块与内核符号CRC,只要函数参数/结构体定义发生变更,CRC就会改变,阻止不兼容模块加载。

题目6:写出极简可运行内核模块必备代码组件,说明__init、__exit宏作用

参考答案

  1. 必备4个核心组件,缺一不可: ① module_init(xxx_init):模块加载入口初始化函数; ② module_exit(xxx_exit):模块卸载资源清理函数; ③ MODULE_LICENSE():许可证声明,缺失会污染内核; ④ 头文件<linux/init.h>、<linux/module.h>。

  2. __init作用 将函数放入.init.text段,模块初始化完成后内核自动回收该段内存,节省常驻空间;若代码内置进内核,系统启动后直接丢弃。

  3. __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模块");

题目7:执行rmmod提示module is in use模块无法卸载,列举3类高频原因与排查方法

参考答案

原因1:存在其他模块依赖当前模块

  • 排查:lsmod | grep 模块名查看Used by字段,显示依赖的模块列表;
  • 解决:先卸载所有依赖该模块的子模块,再卸载目标模块。

原因2:用户态持有模块对应设备/文件资源

场景:挂载vfat分区、打开设备驱动文件、程序占用字符设备;

  • 排查:lsof /dev/xxx、查看挂载表mount;
  • 解决:umount分区、关闭占用设备的进程。

原因3:内核内部持有模块引用计数

场景:代码遗漏module_put、未注销定时器/工作队列/内核线程、设备owner指针未释放;

  • 排查:查看/sys/module/xxx/refcnt引用计数值,打印dmesg内核日志;
  • 解决:修复驱动,卸载前销毁所有内核异步资源、成对使用try_module_get/module_put。

题目8:模块.ko是ELF文件,列举模块特有的ELF段及各自作用

参考答案

  1. .gnu.linkonce.this_module:存储struct module结构体实例,内核识别合法内核模块的唯一标识,缺失则拒绝加载;
  2. .modinfo:存放模块元数据,包含许可证、vermagic、依赖列表、模块别名、参数,modinfo工具读取该段;
  3. __ksymtab / __ksymtab_gpl:普通/GPL导出符号表,记录函数名称与内存地址;
  4. __kcrctab / __versions:存储符号CRC校验和,用于MODVERSIONS版本校验;
  5. .init.text / .exit.text:初始化、卸载独立代码段,按需释放;
  6. __param:模块启动参数元数据,支持加载时传参。

题目9:内核用什么数据结构管理已加载模块?模块间依赖关系如何维护?

参考答案

  1. 核心数据结构struct module每个加载的模块对应一个实例,核心成员:
  • state:模块状态(加载中COMING/正常LIVE/卸载中GOING);
  • syms/gpl_syms:普通、GPL导出符号数组;
  • modules_which_use_me:链表头,记录所有依赖当前模块的其他模块;
  • ref:多核独立引用计数,由try_module_get/module_put维护;
  • init/core:初始化段、核心段内存地址。
  1. 模块依赖维护逻辑 辅助结构体struct module_use建立双向关联:
  • 若模块B依赖模块A,内核创建module_use节点;
  • 节点module_which_uses指向B,挂载到A的modules_which_use_me链表;
  • 卸载A前内核遍历该链表,若链表非空说明仍有模块依赖,直接拒绝卸载。

题目10:模块参数如何定义?加载时传参两种方式,参数在用户态的查看入口

参考答案

  1. 定义模块参数核心宏
  • module_param(变量名, 类型, 权限):基础单参数;
  • module_param_array(数组名, 类型, 长度指针, 权限):数组参数; 支持类型:int、charp(字符串)、bool、ulong等。

示例:

staticchar *dev_name = "default";module_param(dev_name, charp, S_IRUGO); // 只读权限
  1. 两种加载传参方式 ① 手动加载:insmod demo.ko dev_name="usb0"; ② 内置模块:内核启动cmdline传入demo.dev_name="usb0"。

  2. 用户态查看入口 模块加载后在/sys/module/模块名/parameters/生成对应文件,cat文件即可读取当前参数值;权限为0则不生成sysfs节点。

刷题小贴士

  1. 校招侧重1、2、6、10基础实操、手写代码;
  2. 社招驱动岗重点3、4、5、8、9底层原理、故障、法律GPL问题;
  3. 面试答题逻辑:先讲概念→再讲流程/结构→最后举工程场景,更容易拿高分。
往期文章(欢迎订阅技术分享栏目全部文章):

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

这里是女程序员的笔记本

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

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

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

最新文章

随机文章