Linux内核模块怎么开发与调试?嵌入式Linux面试

浏览量:283
时间: 2026-09-14 19:50:06
一句话答案

内核模块是运行在内核态的可加载代码,开发用 module_init/module_exit 注册入口、MODULE_LICENSE 声明许可,通过 insmod/rmmod 加载卸载,调试靠 printk+dmesg,复杂问题用 gdb/kgdb;和用户态程序最大区别是不能直接调 libc、要小心并发和内存。

本文概述

内核模块是嵌入式 Linux 驱动开发的基础,面试常考:模块和应用的区别、入口出口、许可证声明、printk 调试、并发控制、常见崩溃原因。回答要写出一个最小模块骨架,并说清用户态和内核态的差异。

核心要点

  • 模块用 module_init()/module_exit() 注册加载卸载函数,MODULE_LICENSE("GPL") 声明许可

  • 内核态不能直接用 libc(printf/malloc),用 printk/kmalloc/vmalloc,头文件

  • 加载:insmod xxx.ko;卸载:rmmod xxx;查看:lsmod;日志:dmesg

  • 并发:多 CPU/多进程同时访问,要用 mutex/spinlock 保护共享资源

  • 常见崩溃:空指针、超时调度、内存越界,会 oops 或 panic,dmesg 看栈回溯

终端屏幕显示 make 编译内核模块、insmod/dmesg 调试输出,旁边开发板

面试官:写一个最简单的内核模块,加载打印 hello,卸载打印 bye,怎么写?怎么调试?

面试官考察点

这是手到擒来的题,写不出骨架基本没过。面试官看:①入口出口宏;②MODULE_LICENSE;③printk 级别;④怎么看 dmesg;⑤是否知道内核模块不能用 printf。

回答思路

先给最小代码骨架,再讲编译(Makefile 调内核构建系统),再讲加载调试流程,最后点出用户态和内核态的坑。

参考回答

最小模块代码。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int __init hello_init(void) {
  printk(KERN_INFO "hello from module\n");
  return 0;
}
static void __exit hello_exit(void) {
  printk(KERN_INFO "bye from module\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("hello demo");

几个关键点:

  • 必须 include <linux/module.h>、<linux/init.h>,不要 include stdio.h;

  • printk 不是 printf,第一个参数是日志级别(KERN_INFO/KERN_ERR),输出到内核 ring buffer,用 dmesg 看;

  • MODULE_LICENSE("GPL") 必须有,否则会有 tainted 警告,且无法使用 GPL 导出符号;

  • 加载函数返回 0 成功,返回负值(-ENOMEM 等)表示失败。

编译。模块不直接 gcc 编译,要调内核构建系统:

obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
  make -C $(KDIR) M=$(PWD) modules
clean:
  make -C $(KDIR) M=$(PWD) clean

嵌入式交叉编译时 KDIR 指向目标板内核源码树,ARCH/CROSS_COMPILE 指定。

加载与调试。

操作命令说明
加载insmod hello.ko手动加载,加载失败 dmesg 看错误
查看lsmod / lsmod | grep hello看已加载模块
卸载rmmod hello引用计数为 0 才能卸载
看日志dmesg | tailprintk 输出和 oops 栈
开机自动加载/etc/modules 或 modprobe.d生产环境用 modprobe

内核态的坑。

  • 不能用浮点(多数架构)、不能访问用户空间指针(要 copy_from_user);

  • 并发:模块可能被多 CPU 同时调用,共享资源用 mutex/spinlock 保护;

  • 崩溃代价大:内核 panic 直接死机,不能像用户态程序 segfault 了重启;

  • 复杂调试用 gdb 连 kgdb、或 crash 分析 vmcore。

面试官可能追问

追问 1:模块和应用程序的本质区别?
应用在用户态,崩溃只影响自己;模块在内核态,有最高权限,崩溃整个系统挂。模块没有 libc、有自己的内存分配、要处理并发和硬件。
追问 2:printk 的日志级别怎么用?
KERN_EMERG/ALERT/CRIT/ERR/WARNING/NOTICE/INFO/DEBUG,数字越小越紧急。dmesg 或 /proc/sys/kernel/printk 控制输出级别,生产环境别打 KERN_DEBUG。
追问 3:模块依赖另一个模块怎么办?
用 modprobe 自动按依赖加载,insmod 不处理依赖。模块导出符号用 EXPORT_SYMBOL(),其他模块用 EXPORT_SYMBOL_GPL()。
追问 4:写驱动时怎么访问用户空间的数据?
不能直接解引用用户指针,要用 copy_from_user()/copy_to_user(),否则可能崩溃或安全漏洞。
追问 5:oops 是什么?
内核态异常(空指针、非法指令等)时打印的错误信息,包含栈回溯、寄存器、PID,用来定位崩溃点。oops 后系统可能继续也可能 panic。

常见失分表达

✗ 内核模块就是普通 C 程序,用 gcc 编译 — 不知道要走内核构建系统,也不能用 libc。
✗ printf 打印调试 — 内核态没有 printf,要用 printk 并 dmesg 看。
✗ MODULE_LICENSE 随便写 — 不写 GPL 会 tainted,很多符号不能用,也影响调试。

准备动作

1
在本机写一个 hello 模块,insmod/rmmod/dmesg 跑通
2
写一个带参数(module_param)的模块,学习传参
3
准备一个 oops 栈回溯例子,能看懂 PC/栈地址
4
了解字符驱动框架(file_operations、cdev_init)的基本结构

核心关键词

内核模块module_initprintkinsmodMODULE_LICENSEoops


声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。仅供学习交流使用,不构成商业目的。版权归原作者所有,如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时删除。侵权投诉
相关推荐HOT
面试中怎样展示自己的学习能力?5种方法让面试官觉得你潜力大

分类:面试技巧与通用知识经验级别:基础难度:基础题型:软实力展示/学习能力📖 本文概述学习能力是面试官非常看重的一项软实力,特别是对经验不太多的候选人。但很多... 详情>>

2026-08-08 16:46:17
射频面试:链路预算怎么做?EIRP、自由空间损耗、接收灵敏度和余量要串起来

射频系统能不能通信,不是只看发射功率,也不是只看天线增益。链路预算就是把从发射机到接收机的所有增益和损耗用同一套单位串起来,最后判断接收功率距离灵敏度还有多少余... 详情>>

2026-10-20 21:54:20
EMC面试高频题:USB接口TVS二极管为什么要靠近连接器?PCB布局怎么做?

EMC电磁兼容 · 求职面试高频问答ESD测试时产品偶发死机,很多面试官会要求指出USB等外部接口的防护路径。TVS选型重要,但保护器件放得太远或接地太绕,同样... 详情>>

2026-11-01 10:05:11
射频工程师面试:dB和dBm有什么区别?如何计算放大器增益与链路功率?

射频电路 · 求职面试高频问答一个射频链路输入是−40 dBm,经过12 dB放大和3 dB线缆损耗,输出是多少?这是比复杂调制更常见的基础计算题。关键在于区分... 详情>>

2026-11-01 10:05:28
ARM指令集分为⼏类?

ARM指令集分为⼏类?2类,分别为Thumb指令集,ARM指令集。ARM指令⻓度为32位,Thumb指令⻓度为16位。这种特点使得ARM既能执⾏16位指令,⼜能... 详情>>

2023-11-21 11:22:30
开班信息