kernel exception 流程

本文深入探讨Linux内核中的Kernel Panic与Oops现象,解析其触发机制与内核响应过程,包括关中断、打印错误信息、堆栈信息及内核通知链的调用流程。

Kernel Exception 根本原因有两类,

oops

  • oops是美国人比较常有的口语。就是有点意外,吃惊,或突然的意思。内核行为表现为通知感兴趣模块,打印各种信息,如寄存器值,堆栈信息
  • 当出现oops时,我们就可以根据寄存器等信息调试并解决问题。
  • /proc/sys/kernel/panic_on_oops为1时导致panic。我们默认设置为1,即oops会发生panic。

panic

  • Panic – 困惑,恐慌,它表示Linux kernel遇到了一个不知道该怎么继续的情况。内核行为表现为通知感兴趣模块,死机或者重启
  • 在kernel代码里,有些代码加了错误检查,发现错误可能直接调用了panic(),并输出信息提供调试。

在kernel代码里,一般会通过BUG(),BUG_ON(),panic()来拦截超出预期的行为,这是软件主动回报异常的功能。

例如:

	if (IS_ERR(clk_cs)) {
		pr_err("ERROR: timer input clock not found\n");
		BUG();
	}

由于BUG_ON调用BUG,原生得BUG直接调用panic

#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
#endif

下面来只看panic得流程

{
	static DEFINE_SPINLOCK(panic_lock);
	static char buf[1024];
	va_list args;
	long i, i_next = 0;
	int state = 0;

	/*
	 * Disable local interrupts. This will prevent panic_smp_self_stop
	 * from deadlocking the first cpu that invokes the panic, since
	 * there is nothing to prevent an interrupt handler (that runs
	 * after the panic_lock is acquired) from invoking panic again.
	 */
	local_irq_disable();  //1. 关中断

	/*
	 * It's possible to come here directly from a panic-assertion and
	 * not have preempt disabled. Some functions called from here want
	 * preempt to be disabled. No point enabling it later though...
	 *
	 * Only one CPU is allowed to execute the panic code from here. For
	 * multiple parallel invocations of panic, all other CPUs either
	 * stop themself or will wait until they are stopped by the 1st CPU
	 * with smp_send_stop().
	 */
	if (!spin_trylock(&panic_lock))
		panic_smp_self_stop();

	console_verbose();
	bust_spinlocks(1);
	va_start(args, fmt);
	vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);
	pr_emerg("Kernel panic - not syncing: %s\n", buf);    //打印panic得参数
#ifdef CONFIG_DEBUG_BUGVERBOSE
	/*
	 * Avoid nested stack-dumping if a panic occurs during oops processing
	 */
	if (!test_taint(TAINT_DIE) && oops_in_progress <= 1)
		dump_stack();    //打印堆栈,避免nested crash。
#endif

	/*
	 * If we have crashed and we have a crash kernel loaded let it handle
	 * everything else.
	 * If we want to run this after calling panic_notifiers, pass
	 * the "crash_kexec_post_notifiers" option to the kernel.
	 */
	if (!crash_kexec_post_notifiers)
		crash_kexec(NULL);

	/*
	 * Note smp_send_stop is the usual smp shutdown function, which
	 * unfortunately means it may not be hardened to work in a panic
	 * situation.
	 */
	smp_send_stop();

	/*
	 * Run any panic handlers, including those that might need to
	 * add information to the kmsg dump output.
	 */
	atomic_notifier_call_chain(&panic_notifier_list, 0, buf);    //内核通知链,通知关注panic得进程,并把panic得参数传递

	kmsg_dump(KMSG_DUMP_PANIC);    //KMSG dump

	/*
	 * If you doubt kdump always works fine in any situation,
	 * "crash_kexec_post_notifiers" offers you a chance to run
	 * panic_notifiers and dumping kmsg before kdump.
	 * Note: since some panic_notifiers can make crashed kernel
	 * more unstable, it can increase risks of the kdump failure too.
	 */
	if (crash_kexec_post_notifiers)
		crash_kexec(NULL);

	bust_spinlocks(0);

	/*
	 * We may have ended up stopping the CPU holding the lock (in
	 * smp_send_stop()) while still having some valuable data in the console
	 * buffer.  Try to acquire the lock then release it regardless of the
	 * result.  The release will also print the buffers out.  Locks debug
	 * should be disabled to avoid reporting bad unlock balance when
	 * panic() is not being callled from OOPS.
	 */
	debug_locks_off();
	console_flush_on_panic();

	if (!panic_blink)
		panic_blink = no_blink;

	if (panic_timeout > 0) {
		/*
		 * Delay timeout seconds before rebooting the machine.
		 * We can't use the "normal" timers since we just panicked.
		 */
		pr_emerg("Rebooting in %d seconds..\n", panic_timeout);

		for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
			touch_nmi_watchdog();
			if (i >= i_next) {
				i += panic_blink(state ^= 1);
				i_next = i + 3600 / PANIC_BLINK_SPD;
			}
			mdelay(PANIC_TIMER_STEP);
		}
	}
	if (panic_timeout != 0) {
		/*
		 * This will not be a clean reboot, with everything
		 * shutting down.  But if there is a chance of
		 * rebooting the system it will be rebooted.
		 */
		emergency_restart();
	}
#ifdef __sparc__
	{
		extern int stop_a_enabled;
		/* Make sure the user can actually press Stop-A (L1-A) */
		stop_a_enabled = 1;
		pr_emerg("Press Stop-A (L1-A) to return to the boot prom\n");
	}
#endif
#if defined(CONFIG_S390)
	{
		unsigned long caller;

		caller = (unsigned long)__builtin_return_address(0);
		disabled_wait(caller);
	}
#endif
	pr_emerg("---[ end Kernel panic - not syncing: %s\n", buf);
	local_irq_enable();
	for (i = 0; ; i += PANIC_TIMER_STEP) {
		touch_softlockup_watchdog();
		if (i >= i_next) {
			i += panic_blink(state ^= 1);
			i_next = i + 3600 / PANIC_BLINK_SPD;
		}
		mdelay(PANIC_TIMER_STEP);
	}
}

 

流程图:

<think>好的,用户问的是“kernel die流程是什么”。首先,我需要确认用户指的是操作系统内核中的“die”流程。通常,在Linux内核开发中,当遇到无法恢复的错误时,内核会触发panicoops,但“die”可能指的是更底层的处理流程。 接下来,我应该回顾一下内核错误处理的基本机制。当内核遇到严重错误,比如空指针解引用、内存访问越界等,会调用die()函数。这个函数负责处理当前的异常情况,打印相关的调试信息,比如寄存器状态、堆栈跟踪等,然后可能触发panic导致系统重启。 需要进一步分解die流程的具体步骤。首先,die()函数属于异常处理的一部分,通常在arch目录下的特定架构代码中实现,比如x86的die处理。它会先获取当前的异常信息,然后通过notifier链通知其他子系统,记录错误信息到控制台和日志,最后决定是否继续运行还是panic。 用户可能是在调试内核崩溃的问题,或者对内核的错误处理机制感兴趣。他们可能希望了解当系统发生严重错误时,内核是如何记录信息并采取行动的。因此,需要详细说明die流程的各个阶段,以及相关的函数调用,比如notifier_call_chain、oops_enter/exit等。 另外,要区分die()和panic()的区别。die()处理当前的异常,可能让内核继续运行,而panic()会导致系统停止。需要明确两者在流程中的关系,比如die()可能最终调用panic()。 还要考虑用户可能的背景,是否是内核开发者或系统管理员,他们可能需要了解如何分析内核日志中的die信息,或者如何配置内核以捕获更多调试信息。因此,在解释流程时,可以提到如何利用这些信息进行故障排查。 最后,确保使用正确的中文术语,避免直译造成的歧义。例如,将“stack trace”翻译为“堆栈跟踪”或“调用栈追踪”。同时,结构要清晰,分步骤解释流程,便于用户理解。</think>内核中的 "die" 流程是指当内核遇到无法处理的严重错误时,触发异常处理并生成崩溃信息的完整过程(通常对应 `die()` 函数)。以下是典型处理流程: --- ### **核心处理阶段** 1. **异常触发** - CPU 捕获非法操作(如空指针解引用、内存越界、除零错误) - 硬件生成异常信号(如x86的#GP、#PF异常) 2. **进入die()函数** - 内核调用 `die(const char *str, struct pt_regs *regs, long err)` - 参数包含错误描述字符串、寄存器状态和错误码 3. **关键信息收集** ```c /* 伪代码示例 */ void die(const char *str, struct pt_regs *regs, long err) { oops_enter(); // 进入oops处理 console_verbose(); // 强制控制台输出 __show_regs(regs); // 打印寄存器状态 add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE); // 标记内核污染状态 bust_spinlocks(1); // 解除自旋锁防止死锁 ``` - 打印堆栈跟踪(`dump_stack()`) - 输出内存映射信息(`show_mem()`) 4. **通知链处理** - 通过 `notify_die(DIE_OOPS, str, regs, err, 0, 0)` 调用注册的notifier函数 - 允许调试工具(如kdump)介入 5. **最终处理决策** ```c if (panic_on_oops) panic("Fatal exception"); // 触发内核恐慌 bust_spinlocks(0); oops_exit(); ``` - 根据 `panic_on_oops` 配置决定是否立即panic - 可能尝试杀死当前进程继续运行(非致命错误时) --- ### **关联机制** - **与panic()的关系** die() 可能最终调用 panic(),但更侧重于错误现场记录,而 panic() 是系统级崩溃处理 - **信息输出渠道** - 控制台输出(`printk` 缓冲区) - `dmesg` 日志记录 - 通过kmsg_dump保存到持久存储 - **架构相关实现** x86处理入口:`arch/x86/kernel/dumpstack.c` ARM处理入口:`arch/arm/kernel/traps.c` --- ### **典型日志特征** ``` [ 1234.567890] BUG: unable to handle kernel NULL pointer dereference at 0000000000000050 [ 1234.567893] IP: [<ffffffff81234567>] do_something+0x123/0x456 [ 1234.567896] PGD 1234567 PUD 1234567 [ 1234.567898] Oops: 0000 [#1] SMP [ 1234.567900] CPU: 3 PID: 4567 Comm: process_name Tainted: G W OE 4.19.0-xx-generic [ 1234.567902] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)... [ 1234.567905] RIP: 0010:[<ffffffff81234567>] [<ffffffff81234567>] do_something+0x123/0x456 [ 1234.567908] RSP: 0018:ffffc9000123ffd8 EFLAGS: 00010246 [ 1234.567910] RAX: 0000000000000000 RBX: ffff888123456789 RCX: 0000000000000001 ...(寄存器完整输出)... [ 1234.567923] Call Trace: [ 1234.567926] [<ffffffff8123abcd>] higher_function+0xef/0x234 [ 1234.567929] [<ffffffff8123def0>] syscall_handler+0x67/0x89 ...(调用栈追踪)... ``` --- ### **调试建议** 1. 检查 `dmesg` 输出完整日志 2. 使用 `objdump` 或 `addr2line` 解析地址信息 3. 配置 `CONFIG_DEBUG_INFO` 编译内核以获取符号详细信息 4. 通过 `crash` 工具分析vmcore文件(配合kdump使用) 该流程内核稳定性的最后防线,开发者需要通过其输出信息定位根本原因。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值