面试题
说一下你写Linux内核模块的完整流程。从编写代码到加载运行,中间有哪些步骤?遇到过什么问题,怎么解决的?
面试官考察点
是否实际做过内核模块开发,还是只背过概念
是否掌握模块的基本结构和编译方法
是否了解模块加载卸载的机制和工具
是否有调试驱动的实际经验
对字符设备驱动框架的理解程度
核心要点速览
内核模块是动态加载的,不需要重新编译内核
模块源码必须包含module_init、module_exit和MODULE_LICENSE
编译需要对应版本的内核源码树,用Kbuild/Makefile体系
insmod加载模块,rmmod卸载,lsmod查看已加载模块
dmesg和printk是最基础的调试手段
字符设备驱动:注册设备号→初始化cdev→添加到内核→创建设备节点
内核模块开发完整流程
最简单的内核模块需要包含几个基本要素:
头文件:linux/module.h、linux/init.h、linux/kernel.h等
初始化函数:用__init标记,模块加载时执行
退出函数:用__exit标记,模块卸载时执行
module_init和module_exit宏:注册初始化和退出函数
MODULE_LICENSE:声明许可证,不写的话内核会"被污染"(tainted)
内核模块的编译不是普通的gcc编译,而是利用内核的Kbuild编译体系。Makefile写法比较固定:
要点:obj-m指定要编译的模块目标;-C指定内核源码路径;M=指定模块源码所在目录。嵌入式开发中KDIR要指向目标平台的内核源码,还要指定交叉编译工具链。
对于嵌入式开发,需要用交叉编译工具链编译,不能用主机的gcc。通常设置环境变量:
ARCH:目标架构(如arm、arm64)
CROSS_COMPILE:交叉编译工具前缀(如arm-linux-gnueabihf-)
编译成功后会生成hello.ko文件,这就是可以加载到内核的模块文件。
注意:编译模块所用的内核源码版本必须与目标板上运行的内核版本一致,否则会出现版本魔术(vermagic)不匹配的问题,加载失败。
把.ko文件传到目标板上(通过scp、tftp、NFS等方式),然后用insmod加载:
也可以用modprobe加载,但modprobe要求模块已经放在/lib/modules/目录下,并且有modules.dep依赖文件。insmod更直接,适合开发调试阶段。
模块加载后,通过各种方式测试功能是否正常:
字符设备驱动:创建设备节点,用open/read/write/ioctl测试
平台驱动:看probe函数是否执行,设备是否正确初始化
用dmesg看printk输出,排查执行流程
用/proc和/sys查看设备状态
调试手段:printk是最基础也是最常用的;复杂问题可以用kgdb远程调试;还可以用ftrace跟踪内核函数调用。
卸载时要确保模块没有被使用(设备没有被打开、没有其他模块依赖等),否则rmmod会失败。
模块退出函数要做好清理工作:释放申请的内存、注销设备、释放中断、取消注册等。如果遗漏清理,再次加载时可能出问题。
回答思路
内核模块开发的完整流程,我一般分六步来讲:写源码、写Makefile、交叉编译、传到板子加载、测试调试、卸载清理。每一步都有需要注意的地方,比如编译必须用对应版本的内核源码树、加载后要用dmesg看日志、卸载前要做好资源清理。
字符设备驱动是最基础的驱动类型,开发步骤是:申请设备号、初始化cdev并关联file_operations、添加cdev到内核、创建设备类和设备节点、实现操作函数。特别要注意错误处理的回退顺序,按注册的逆序清理,哪一步失败都要回退前面已成功的操作。
字符设备驱动开发要点
字符设备驱动是最常见的内核模块类型,也是面试最常考的。基本开发步骤:
申请设备号:用alloc_chrdev_region动态分配或register_chrdev_region静态注册主设备号和次设备号
初始化cdev:用cdev_init初始化字符设备结构体,关联file_operations操作集
添加cdev:用cdev_add把字符设备添加到内核,使其可用
创建设备类和设备节点:用class_create创建设备类,device_create创建设备节点,让udev/mdev自动在/dev下生成设备文件
实现操作函数:open、release、read、write、ioctl等file_operations中的函数
很多人只说注册流程,不提错误处理。实际上驱动开发中"如果中间某一步失败了怎么回退"很重要。正确的做法是:按注册顺序的逆序做清理,每一步失败都要回退之前已成功的操作。比如cdev_add失败了,要先unregister_chrdev_region再返回错误。
常见问题与排查思路
问题一:insmod提示"Invalid module format"
这是最常见的错误,几乎都是版本不匹配导致的。
原因:编译模块的内核版本和目标板上的内核版本不一致,vermagic不匹配。内核会检查模块的版本魔术字符串(包含内核版本、架构、编译器版本等信息),不匹配就拒绝加载。
解决方法:用跟目标板完全相同的内核源码和编译配置来编译模块。可以用modinfo命令查看模块的vermagic,用uname -r查看目标板的内核版本,对比是否一致。
问题二:模块加载后没有任何反应
加载成功了但是printk没输出,可能的原因:
printk级别不够:printk有8个日志级别,控制台只显示级别高于console_loglevel的消息。KERN_DEBUG级别太低可能不会显示。用dmesg看完整日志
模块真的加载失败了:insmod没有报错但可能加载过程中出错了。检查dmesg有没有错误信息
初始化函数返回了非0值:module_init的函数返回非0的话,加载会失败,模块不会出现在lsmod中
问题三:rmmod提示"Device or resource busy"
模块正在被使用,不能卸载。常见原因:
设备文件还被某个进程打开着
有其他模块依赖这个模块(用lsmod可以看Used by列)
模块中有未完成的操作或未释放的资源
解决方法:先关闭所有打开的设备,再卸载依赖的模块,最后卸载目标模块。如果是自己写的驱动有bug导致引用计数不对,那就要检查代码。
问题四:加载后内核panic或死机
这是最严重的情况,通常是空指针访问、越界访问、竞争条件等严重bug导致的。
排查方法:
看panic信息,找到出错的函数和地址
用addr2line或gdb根据地址定位代码行
逐步注释代码,缩小问题范围
检查指针是否为NULL、数组是否越界、自旋锁是否死锁
内核态的bug比用户态严重得多,用户态程序崩溃只是进程死掉,内核态崩溃整个系统就挂了。所以内核开发要更加谨慎,做好边界检查和错误处理。
面试官可能的追问
追问一:insmod和modprobe有什么区别?
主要区别有几个方面:
依赖处理:insmod只加载指定的.ko文件,不处理依赖关系,如果模块依赖其他模块而那些模块没加载,加载就会失败。modprobe会自动检查并加载所有依赖的模块,使用更智能
模块位置:insmod需要指定.ko文件的完整路径。modprobe不需要路径,它会在/lib/modules/$(uname -r)/目录下查找模块
配置文件:modprobe会读取modprobe.conf等配置文件,可以设置别名、黑名单、参数等
适用场景:开发调试阶段用insmod更方便,可以直接加载编译出来的.ko;正式系统中用modprobe更稳妥
追问二:内核模块和应用程序有什么不同?
这是一个很基础但容易答不好的问题,可以从几个维度对比:
运行空间:应用程序运行在用户空间,模块运行在内核空间
权限:模块可以直接访问所有硬件和内核资源,权限最高。应用程序只能通过系统调用访问内核
出错后果:应用程序崩溃只是进程退出,影响有限。模块出问题(如空指针)可能导致整个内核panic,系统死机
编译方式:应用程序用普通gcc编译,模块要用内核的Kbuild体系编译
内存管理:应用程序有自己独立的虚拟地址空间。模块在内核地址空间运行,共享内核资源
API:应用程序用glibc等标准库,模块用内核提供的函数(如kmalloc、printk等)
追问三:什么是内核污染(Tainted Kernel)?
内核污染是Linux内核的一个机制,用来标记内核的状态是否"纯净"。如果加载了非GPL许可的模块,内核就会被标记为"tainted"。
污染的原因有很多种,常见的:
加载了非GPL许可的专有模块(最常见)
强制加载了版本不匹配的模块
内核出现过oops或panic
SMP系统上CPU不支持SMP
污染本身不会影响功能,但社区开发者一般不会为被污染的内核提供技术支持,因为你不知道专有模块里做了什么。所以写驱动时最好加上MODULE_LICENSE("GPL"),这样不仅不会污染内核,还能使用更多GPL-only的内核函数。
失分表达
扣分回答 1
"内核模块就是编译一下然后insmod加载嘛,跟应用程序差不多。"
问题:对内核模块和应用程序的区别理解不深,缺乏内核空间开发的安全意识。
扣分回答 2
问题:只知道版本不匹配这一个原因,对其他可能的问题和排查方法不了解。
扣分回答 3
问题:对驱动的完整生命周期理解不够,忽略了退出清理的重要性。实际项目中exit没写好会导致资源泄漏。
准备动作
在开发板上实际写一个最简单的字符设备驱动,走一遍完整的编译加载卸载流程
故意制造几个常见错误(版本不匹配、init返回错误、忘记释放资源),观察现象并排查
熟悉file_operations结构体中的主要成员函数
了解常用的内核调试手段:dmesg、proc文件系统、sysfs、kgdb、ftrace
核心关键词
内核模块、insmod、rmmod、modprobe、字符设备驱动、cdev、file_operations、module_init、module_exit、Makefile、交叉编译、dmesg、printk、内核污染

扫码关注





























