内核模块是运行在内核态的可加载代码,开发用 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 看栈回溯

面试官考察点
这是手到擒来的题,写不出骨架基本没过。面试官看:①入口出口宏;②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 | tail | printk 输出和 oops 栈 |
| 开机自动加载 | /etc/modules 或 modprobe.d | 生产环境用 modprobe |
内核态的坑。
不能用浮点(多数架构)、不能访问用户空间指针(要 copy_from_user);
并发:模块可能被多 CPU 同时调用,共享资源用 mutex/spinlock 保护;
崩溃代价大:内核 panic 直接死机,不能像用户态程序 segfault 了重启;
复杂调试用 gdb 连 kgdb、或 crash 分析 vmcore。

扫码关注





























