Linux内核模块怎么编写与加载?嵌入式Linux面试内核模块开发核心考点

浏览量:279
时间: 2026-08-27 12:14:07
嵌入式Linux中等难度技术面试中级经验

Linux内核模块开发与编译 - 电子工程师面试

本文概述

Linux内核模块(LKM)是可以动态加载和卸载的内核代码,无需重新编译内核即可扩展内核功能。基本结构包括模块初始化函数(module_init)、模块退出函数(module_exit)、模块许可证声明(MODULE_LICENSE)。加载方式有insmod(简单加载)和modprobe(自动解决依赖),编译需要对应内核源码树,生成.ko文件。

核心要点速览

  • 内核模块可以动态加载卸载,无需重新编译内核,驱动开发常用形式

  • 基本结构:module_init(入口)、module_exit(出口)、MODULE_LICENSE(许可证)

  • insmod直接加载指定.ko文件,modprobe自动加载依赖模块

  • 模块参数用module_param声明,可以在加载时传入参数

  • 编译需要内核源码树和正确的交叉编译工具链,生成.ko文件

一、面试题

请说明Linux内核模块的基本结构,以及你是怎么编写和加载一个内核模块的。

二、面试官考察点

1. 内核模块基础概念

考察候选人对内核模块作用、基本结构、与用户程序区别的理解程度,以及常用操作命令的掌握。

2. 实际开发经验

通过具体项目判断候选人是否有实际的内核模块开发经验,是否了解编译、加载、调试的完整流程。

3. 内核编程思维

是否理解内核态和用户态的区别,是否知道内核编程的注意事项(如不能使用C库、不能随意休眠等)。

三、回答思路

1. 什么是内核模块

Linux内核模块(Loadable Kernel Module,简称LKM)是一种可以在运行时动态加载到内核中的目标代码。加载后成为内核的一部分,可以直接调用内核函数和访问硬件资源。不需要时可以卸载,释放内存。

为什么需要内核模块:一是灵活性——驱动和功能可以按需加载,不需要把所有驱动都编译进内核;二是开发效率——修改驱动后只需重新编译模块,不用重新编译整个内核;三是体积——嵌入式系统内存有限,按需加载节省内存。

2. 内核模块的基本结构

一个最简单的内核模块至少包含以下几个部分:

  • 头文件:#include (模块基本定义)、#include (内核常用函数)、#include (初始化和退出宏定义)

  • 初始化函数:用module_init()宏注册,模块加载时执行一次,返回0表示成功,负数表示失败

  • 退出函数:用module_exit()宏注册,模块卸载时执行一次,负责清理资源

  • 许可证声明:MODULE_LICENSE("GPL"),声明模块遵循的许可证,影响能否使用GPL-only的内核函数

  • 作者/描述/版本(可选):MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION

3. 常用模块操作命令

命令功能说明
insmod加载模块直接加载指定.ko文件,不处理依赖关系
rmmod卸载模块卸载指定模块,模块被使用时不能卸载
modprobe智能加载/卸载自动处理依赖关系,从模块目录查找
lsmod列出已加载模块显示模块名、大小、被引用次数和依赖模块
modinfo查看模块信息显示模块的作者、描述、版本、依赖等
depmod生成模块依赖扫描模块目录生成modules.dep文件

4. 模块参数

内核模块可以接受加载时传入的参数,使用module_param宏声明:

  • 格式:module_param(变量名, 类型, 权限)

  • 常用类型:byte、short、ushort、int、uint、long、ulong、charp(字符串指针)、bool

  • 权限:0表示不在sysfs中显示,S_IRUGO表示所有用户可读,S_IWUSR表示root可写

  • 用法示例:insmod mymodule.ko irq_num=10 debug_enable=1

  • 模块参数数组:module_param_array(name, type, nump, perm),nump指向实际传入的元素数量

5. 模块编译方法

经验要点:内核模块必须和运行的内核版本一致,否则会因为vermagic不匹配而加载失败。嵌入式开发中,必须用目标板运行内核对应的源码树来编译模块,不能用主机的内核源码编译然后放到开发板上。编译前要确保内核源码树已经配置并编译过(至少做过make modules_prepare)。
  • 编译环境:需要安装对应内核版本的源码树或头文件,以及交叉编译工具链

  • Makefile写法:使用内核的Kbuild系统,obj-m += mymodule.o 指定要编译的模块

  • 编译命令:make -C /path/to/kernel M=$PWD modules

  • 生成文件:.ko文件是最终的可加载模块,还有.ko.cmd、.mod.c、.o等中间文件

  • 版本校验:modinfo可以查看模块的vermagic,和uname -r对比确认版本匹配

6. 内核模块编程注意事项

1

不能使用C标准库

内核有自己的函数库,如printk代替printf,kmalloc代替malloc,copy_to_user/copy_from_user代替memcpy用于用户空间数据拷贝

2

注意休眠上下文

中断上下文(中断处理函数、tasklet等)中不能休眠,也不能调用可能休眠的函数(如kmalloc with GFP_KERNEL),要用原子操作和GFP_ATOMIC标志

3

栈空间有限

内核栈只有4KB或8KB(取决于架构和配置),不能在函数中定义大数组,大内存分配要用kmalloc

!

出错后果严重

内核模块崩溃会导致整个系统panic(内核恐慌),不像用户程序只是段错误。开发中要做好边界检查和错误处理

4

模块卸载要清理干净

退出函数必须释放所有申请的资源:释放ioremap、注销设备号、销毁设备类、释放中断、停止定时器等,否则卸载后再加载可能出问题

四、追问(4个)

追问1:printk和printf有什么区别?

printk是内核提供的打印函数,printf是C标准库的用户态函数。区别主要有:printk运行在内核态,printf在用户态;printk有日志级别(8个级别,从KERN_EMERG到KERN_DEBUG),可以通过dmesg查看输出,printf输出到stdout;printk对浮点数支持不好(内核一般不用浮点);printk可以在中断上下文调用,printf不能。日志级别低于console_loglevel的消息才会打印到控制台。

追问2:模块加载时提示"disagrees about version of symbol"是什么意思?

这是因为编译模块用的内核源码树和运行的内核版本不一致,或者内核配置不同。每个内核符号(函数、变量)都有版本校验信息(crc值),模块加载时会校验这些值是否匹配。解决方法是用目标板运行的内核对应的源码树重新编译模块。如果只是小版本差异且确定不会有问题,也可以用modprobe --force强制加载,但不推荐。

追问3:内核模块怎么传递参数?有什么限制?

用module_param宏声明参数,加载时通过"参数名=值"的方式传入。限制有:参数类型只能是内核定义的那几种(int、long、charp等),不能传结构体;数组参数长度有限制;charp类型传入的字符串在内核中被拷贝一份,不是用户空间的指针;权限决定了参数在sysfs中是否可见、是否可写。加载后可以通过/sys/module/模块名/parameters/目录查看和修改参数。

追问4:什么是模块的依赖关系?modprobe怎么处理?

一个模块可能调用另一个模块导出的符号(EXPORT_SYMBOL),这就形成了依赖关系。depmod命令会扫描所有模块,分析符号引用关系,生成modules.dep文件。modprobe加载模块时,会先读取modules.dep,找出所有依赖的模块,按依赖顺序依次加载。卸载时则反向卸载。比如A依赖B,加载时先加载B再加载A,卸载时先卸载A再卸载B。

五、失分表达

  • "内核模块就是用户程序编译成.ko就行"——不理解内核态和用户态的本质区别

  • "随便找个内核源码编译就能加载"——忽略了内核版本匹配问题(vermagic)

  • "内核里可以用printf和malloc"——混淆用户态和内核态的函数库

  • "insmod和modprobe一样,随便用哪个都行"——不理解依赖处理的区别

六、准备动作

  • 能写出一个最简单的Hello World内核模块的完整代码

  • 掌握insmod、rmmod、modprobe、lsmod、modinfo等命令的用法和区别

  • 理解内核态和用户态编程的区别和注意事项

  • 知道内核模块的编译方法和Makefile写法

  • 了解模块参数的声明和使用方法

七、核心关键词

内核模块 LKM module_init insmod modprobe 内核编译 内核态 printk


声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。仅供学习交流使用,不构成商业目的。版权归原作者所有,如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时删除。侵权投诉
相关推荐HOT
传导骚扰CE怎么测试与整改?LISN与电源滤波怎么做?EMC工程师面试

一句话答案传导骚扰(CE)测的是产品通过电源线向外传导的噪声,用 LISN(线路阻抗稳定网络)把电源和产品隔开,再用接收机/频谱仪测 150kHz~30MHz;... 详情>>

2026-09-16 10:01:06
Buck-Boost升降压电路怎么设计?SEPIC与四开关拓扑怎么选?电源工程师面试

电源设计中高级技术基础题本文概述Buck-Boost(升降压)用在输入电压波动跨越输出电压的场景(如电池放电),是电源设计进阶题。面试官想听的不是“串个Buck... 详情>>

2026-09-08 09:00:17
PCB发板前DFM检查清单有哪些?PCB工程师面试必考题

分类:PCB设计工程师面试经验级别:中级难度:★★★☆题型:发板前设计检查PCB设计工程师面试:发板前DFM检查本文概述PCB发板前DFM检查是保证板子可制造、... 详情>>

2026-08-08 15:14:59
I²C地址发对了却没有ACK:逻辑分析仪里到底看哪一位?

总线抓波 / 排障主机发完设备地址后,程序显示NACK。有人马上换上拉电阻,也有人立刻怀疑芯片损坏。更专业的排查应先回到线上:第9个时钟周期的SDA究竟是谁在驱... 详情>>

2026-10-25 11:05:09
DDR信号怎么仿真?建立保持时间怎么算?DDR3设计仿真面试

一句话答案DDR 仿真分两部分:信号完整性(SDRAM 走线阻抗、串扰、眼图)和时序(建立/保持时间裕量);DDR3 数据对 DQS 中心对齐,地址命令对时钟;... 详情>>

2026-09-16 10:13:00
开班信息