内核 Oops 是指 Linux 内核在执行过程中遇到了一个非致命性的错误。它表示内核代码中存在一个 bug,导致内核进入了一个不一致或不期望的状态,但这个错误不足以立即导致整个系统停止运行。
Oops 的名称由来:
"Oops" 这个词来源于英语口语中的感叹词,表示"哎呀,出错了"。这个命名生动地描述了内核遇到错误时的状态。


| 严重程度 | ||
| 系统响应 | ||
| 可恢复性 | ||
| 内核状态 | ||
| 触发条件 | ||
| 典型原因 | ||
| 日志输出 | ||
| 后续影响 |


最常见的 Oops 原因
错误代码示例:
// 错误示例 1: 直接解引用未检查的指针
struct device *dev = get_device();
dev->name = "example"; // ❌ 如果 get_device() 返回 NULL
// 错误示例 2: 函数参数未检查
void process_data(struct data *ptr) {
ptr->value = 100; // ❌ 调用者可能传入 NULL
}
// 错误示例 3: 内存分配失败后使用
struct buffer *buf = kmalloc(size, GFP_KERNEL);
buf->data[0] = 0x01; // ❌ kmalloc 可能返回 NULL
正确做法:
// ✅ 正确示例 1: 检查指针有效性
struct device *dev = get_device();
if (dev) {
dev->name = "example";
} else {
pr_err("Failed to get device\n");
return -ENODEV;
}
// ✅ 正确示例 2: 函数开始检查参数
void process_data(struct data *ptr) {
if (!ptr) {
pr_warn("Invalid pointer\n");
return;
}
ptr->value = 100;
}
// ✅ 正确示例 3: 检查分配结果
struct buffer *buf = kmalloc(size, GFP_KERNEL);
if (!buf) {
pr_err("Memory allocation failed\n");
return -ENOMEM;
}
buf->data[0] = 0x01;
kfree(buf);
内存管理中的经典错误

错误示例:
// ❌ Use-After-Free 错误
struct my_data {
int value;
char name[32];
};
void buggy_function(void) {
struct my_data *data = kmalloc(sizeof(*data), GFP_KERNEL);
data->value = 42;
strcpy(data->name, "test");
// 释放内存
kfree(data);
// ❌ 错误:继续使用已释放的内存
printk("Value: %d\n", data->value); // Oops!
}
正确做法:
// ✅ 正确的内存管理
void correct_function(void) {
struct my_data *data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return;
data->value = 42;
strcpy(data->name, "test");
// 使用数据
printk("Value: %d\n", data->value);
// 释放内存并清空指针
kfree(data);
data = NULL; // ✅ 防止后续误用
}
多核/多线程环境下的常见问题

错误示例:
// ❌ 竞态条件示例
static int shared_counter = 0;
void increment_counter(void) {
// ❌ 不安全:多个CPU可能同时执行
int temp = shared_counter;
temp++;
shared_counter = temp;
}
正确做法:
// ✅ 使用自旋锁保护
static int shared_counter = 0;
static DEFINE_SPINLOCK(counter_lock);
void increment_counter(void) {
spin_lock(&counter_lock);
shared_counter++;
spin_unlock(&counter_lock);
}
// ✅ 或使用原子操作
static atomic_t shared_counter = ATOMIC_INIT(0);
void increment_counter(void) {
atomic_inc(&shared_counter);
}
锁依赖循环

错误代码:
// ❌ 死锁示例
static DEFINE_SPINLOCK(lock_a);
static DEFINE_SPINLOCK(lock_b);
void function_1(void) {
spin_lock(&lock_a);
// ... do something ...
spin_lock(&lock_b); // 获取顺序: A -> B
// ... do something ...
spin_unlock(&lock_b);
spin_unlock(&lock_a);
}
void function_2(void) {
spin_lock(&lock_b);
// ... do something ...
spin_lock(&lock_a); // ❌ 获取顺序: B -> A (死锁!)
// ... do something ...
spin_unlock(&lock_a);
spin_unlock(&lock_b);
}
正确做法:
// ✅ 统一锁顺序
void function_1(void) {
spin_lock(&lock_a);
spin_lock(&lock_b); // 顺序: A -> B
// ... do something ...
spin_unlock(&lock_b);
spin_unlock(&lock_a);
}
void function_2(void) {
spin_lock(&lock_a);
spin_lock(&lock_b); // ✅ 相同顺序: A -> B
// ... do something ...
spin_unlock(&lock_b);
spin_unlock(&lock_a);
}
常见的上下文违规
// ❌ 错误:在持有自旋锁时睡眠
static DEFINE_SPINLOCK(my_lock);
void buggy_function(void) {
spin_lock(&my_lock);
// ❌ 错误:GFP_KERNEL 可能导致睡眠
void *buf = kmalloc(1024, GFP_KERNEL);
spin_unlock(&my_lock);
}
// ✅ 正确做法
void correct_function(void) {
spin_lock(&my_lock);
// ✅ 使用 GFP_ATOMIC,不会睡眠
void *buf = kmalloc(1024, GFP_ATOMIC);
spin_unlock(&my_lock);
}
当内核发生 Oops 时,会输出详细的调试信息。下面是一个典型的 Oops 输出示例:
BUG: unable to handle kernel NULL pointer dereference at 0000000000000008
IP: [<ffffffffa0123456>] my_driver_function+0x12/0x50 [my_module]
PGD 0
Oops: 0002 [#1] SMP
Modules linked in: my_module(O) ...
CPU: 2 PID: 1234 Comm: test_app Tainted: G O #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
RIP: 0010:my_driver_function+0x12/0x50 [my_module]
Code: 48 8b 47 08 48 c7 00 00 00 00 00 c3 0f 1f 44 00 00 ...
RSP: 0018:ffffc90001a3bd88 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff88007c123000 RCX: 0000000000000001
RDX: 0000000000000000 RSI: ffff88007c456000 RDI: 0000000000000000
RBP: ffffc90001a3bd98 R08: 0000000000000000 R09: 0000000000000001
R10: 0000000000000000 R11: 0000000000000000 R12: ffff88007c789000
R13: 0000000000000000 R14: ffff88007c012000 R15: 0000000000000000
FS: 00007f1234567000(0000) GS:ffff88007fc80000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000008 CR3: 000000007c123000 CR4: 00000000000006e0
Call Trace:
my_driver_ioctl+0x45/0x100 [my_module]
do_vfs_ioctl+0xa4/0x600
SyS_ioctl+0x79/0x90
do_syscall_64+0x73/0x130
entry_SYSCALL_64_after_hwframe+0x3d/0xa2

unable to handle kernel NULL pointer dereference | ||
general protection fault | ||
unable to handle kernel paging request | ||
kernel BUG at ... | ||
divide error |
Oops: 0002 [#1] SMP
^^^^ ^^ ^^^
| | |
| | +--- 系统类型 (SMP=多核, PREEMPT=可抢占)
| +-------- Oops 序号 (#1=第1次, #2=第2次...)
+-------------- 错误代码 (十六进制)
错误代码位含义:
示例:
- 0000 = 内核读取不存在的页
- 0002 = 内核写入不存在的页
- 0003 = 内核写入保护违规
RIP: 0010:my_driver_function+0x12/0x50 [my_module]
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^
| | | |
| | | +--- 模块名
| | +------- 函数大小 (80字节)
| +--------------------------------- 函数名+偏移 (偏移0x12字节)
+--------------------------------------- 代码段选择子
RSP: 0018:ffffc90001a3bd88 EFLAGS: 00010246
^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^
| | |
| | +--- CPU标志寄存器
| +----------------------------- 栈指针地址
+----------------------------------- 栈段选择子
CR2: 0000000000000008
^^^^^^^^^^^^^^^^
|
+--- 导致页错误的地址 (Page Fault Address)
关键寄存器:
Call Trace:
my_driver_ioctl+0x45/0x100 [my_module]
do_vfs_ioctl+0xa4/0x600
SyS_ioctl+0x79/0x90
do_syscall_64+0x73/0x130
entry_SYSCALL_64_after_hwframe+0x3d/0xa2
调用栈解读流程:


# 从 Oops 信息中提取地址
# IP: [<ffffffffa0123456>] my_driver_function+0x12/0x50 [my_module]
# 使用 addr2line 转换为源代码行
addr2line -e my_module.ko 0x123456
# 输出示例:
# /path/to/driver/my_driver.c:123
# 或者使用内核脚本
./scripts/decode_stacktrace.sh vmlinux /path/to/modules < oops.txt
# 反汇编模块
objdump -dS my_module.ko > my_module.asm
# 查找出错的函数
# 在 my_module.asm 中搜索 my_driver_function
# 找到 +0x12 偏移处的指令
# 示例输出:
# 0000000000000450 <my_driver_function>:
# 450: 48 8b 47 08 mov 0x8(%rdi),%rax ← 偏移 0x12
# 454: 48 c7 00 00 00 00 00 movq $0x0,(%rax) ← 这里解引用了空指针!
# 启动 GDB
gdb vmlinux
# 加载模块符号
(gdb) add-symbol-file /path/to/my_module.ko 0xffffffffa0123000
# 查看出错位置的源代码
(gdb) list *(my_driver_function+0x12)
# 输出:
# 123 void my_driver_function(struct my_device *dev)
# 124 {
# 125 struct data *ptr = dev->data; ← 假设 dev 是 NULL
# 126 ptr->value = 0; ← 这里触发 Oops!
# 127 }
# 反汇编函数
(gdb) disassemble my_driver_function
# 配置 kdump 捕获内核崩溃转储
# /etc/default/grub
GRUB_CMDLINE_LINUX="crashkernel=256M"
# 重启后,Oops/Panic 会生成 vmcore 文件在 /var/crash/
# 使用 crash 工具分析
crash vmlinux /var/crash/vmcore
# crash 命令示例:
crash> bt # 显示调用栈
crash> ps # 显示进程列表
crash> log # 显示内核日志
crash> struct my_device 0xffff88007c123000 # 查看数据结构
crash> dis my_driver_function # 反汇编函数
# 启用函数追踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo my_driver_function > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 触发问题后查看追踪
cat /sys/kernel/debug/tracing/trace
# 使用 perf 记录
perf record -e 'kmem:*' -ag
# 触发 Oops
perf report

// ✅ 总是检查指针有效性
if (ptr == NULL) {
pr_err("Invalid pointer\n");
return -EINVAL;
}
// ✅ 释放后清空指针
kfree(ptr);
ptr = NULL;
// ✅ 使用内核宏简化检查
if (IS_ERR_OR_NULL(ptr)) {
return PTR_ERR(ptr);
}
// ✅ 防御性编程
#define SAFE_DEREF(ptr, member, default) \
((ptr) ? (ptr)->member : (default))
int value = SAFE_DEREF(dev, status, -1);
// ✅ 分配后检查
void *buf = kmalloc(size, GFP_KERNEL);
if (!buf) {
pr_err("Memory allocation failed\n");
return -ENOMEM;
}
// ✅ 使用资源管理的自动清理
void *buf = devm_kmalloc(dev, size, GFP_KERNEL);
// 设备移除时自动释放
// ✅ 清零敏感内存
memzero_explicit(password, sizeof(password));
kfree(password);
// ✅ 使用 RAII 风格的锁保护
static DEFINE_SPINLOCK(my_lock);
void safe_function(void) {
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);
// 临界区代码
spin_unlock_irqrestore(&my_lock, flags);
}
// ✅ 使用锁验证工具
#ifdef CONFIG_PROVE_LOCKING
lockdep_assert_held(&my_lock);
#endif

使用示例:
# Sparse - 检查类型、地址空间、锁
make C=1 CF="-D__CHECK_ENDIAN__"
# Coccinelle - 查找模式
spatch --sp-file null_check.cocci my_driver.c
# Smatch - 静态分析
~/smatch/smatch_scripts/kchecker my_driver.c
# 内核测试工具
make W=1 # 启用额外警告
# KASAN - 检测内存错误
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
# UBSAN - 未定义行为检测
CONFIG_UBSAN=y
# LOCKDEP - 死锁检测
CONFIG_PROVE_LOCKING=y
CONFIG_LOCK_STAT=y
# KMEMLEAK - 内存泄漏检测
CONFIG_DEBUG_KMEMLEAK=y
echo scan > /sys/kernel/debug/kmemleak
# DEBUG_LIST - 链表调试
CONFIG_DEBUG_LIST=y
# DEBUG_ATOMIC_SLEEP - 检测原子上下文睡眠
CONFIG_DEBUG_ATOMIC_SLEEP=y
Oops 输出:
BUG: unable to handle kernel NULL pointer dereference at 0000000000000010
IP: [<ffffffffa0234567>] my_read+0x8/0x30 [faulty_module]
源代码:
struct my_device {
int status;
char *name; // 偏移 0x10
};
static ssize_t my_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct my_device *dev = file->private_data;
// ❌ 未检查 dev 是否为 NULL
return simple_read_from_buffer(buf, count, ppos,
dev->name, strlen(dev->name));
// 如果 dev == NULL,访问 dev->name (偏移 0x10) 会触发 Oops
}
修复:
static ssize_t my_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct my_device *dev = file->private_data;
// ✅ 添加检查
if (!dev || !dev->name) {
pr_err("Invalid device or device name\n");
return -EINVAL;
}
return simple_read_from_buffer(buf, count, ppos,
dev->name, strlen(dev->name));
}
场景:
struct work_item {
struct work_struct work;
char *data;
};
static void work_handler(struct work_struct *work)
{
struct work_item *item = container_of(work, struct work_item, work);
process_data(item->data);
// ❌ 释放内存
kfree(item->data);
kfree(item);
}
static int submit_work(void)
{
struct work_item *item = kmalloc(sizeof(*item), GFP_KERNEL);
item->data = kmalloc(1024, GFP_KERNEL);
INIT_WORK(&item->work, work_handler);
schedule_work(&item->work);
// ❌ 可能在 work_handler 执行期间被访问
return 0;
}
// 在模块卸载时
static void cleanup(void)
{
// ❌ 没有等待工作项完成
// 如果 work_handler 还在执行,访问已卸载的代码 -> Oops
}
修复:
static struct workqueue_struct *my_wq;
static void cleanup(void)
{
// ✅ 等待所有工作完成
flush_workqueue(my_wq);
destroy_workqueue(my_wq);
}

| 理解 | |
| 定位 | |
| 分析 | |
| 修复 | |
| 预防 | |
| 测试 |
永远不要忽略编译警告bash make W=1 # 启用额外警告
使用内核调试选项CONFIG_DEBUG_KERNEL=y CONFIG_KASAN=y CONFIG_UBSAN=y CONFIG_PROVE_LOCKING=y
编写防御性代码
- 检查所有指针
- 验证所有输入
- 正确处理错误
进行充分测试
- 单元测试
- 压力测试
- 边界条件测试
持续学习
- 阅读内核文档
- 分析其他开发者的补丁
- 参与社区讨论
希望这份技术指南能帮助您理解和调试 Linux 内核 Oops!如有任何疑问,欢迎继续探讨。🚀