
Linux panic内核恐慌与kmsg_dump注册回调panic是Linux内核的终极异常处理函数。当内核检测到无法恢复的错误时调用panic()终止系统运行。panic()的实现位于kernel/panic.c中承载了信息转储、notifier回调、kmsg_dump输出和最终停机等一系列操作。__setup(panic, panic_setup)允许内核启动参数配置panic_timeout控制自动重启的延迟秒数void panic(const char *fmt, ...){static DEFINE_SPINLOCK(panic_lock);static char buf[1024];va_list args;long i;int state 0;int old_cpu;/** 防止多个CPU同时进入panic只有第一个进入panic的CPU* 继续执行其他CPU在panic_smp_self_stop中自旋*/old_cpu atomic_cmpxchg(panic_cpu, PANIC_CPU_INVALID,raw_smp_processor_id());if (old_cpu ! PANIC_CPU_INVALID)panic_smp_self_stop();/* 获取panic全局锁序列化输出 */if (spin_trylock(panic_lock)) {pr_emerg(Kernel panic - not syncing: );va_start(args, fmt);vprintk_emit(0, LOGLEVEL_EMERG, NULL, fmt, args);va_end(args);}/** 调用panic_notifier_list上的所有回调。* 这些回调由panic_notifier_chain_register注册* 典型的注册者包括kdump、netconsole、DRM等*/atomic_notifier_call_chain(panic_notifier_list, 0, buf);/* 触发kmsg_dump将内核日志转储到预先注册的dump设备 */kmsg_dump(KMSG_DUMP_PANIC);/** 输出附加信息堆栈、寄存器、已加载模块等*/bust_spinlocks(0);printk(---[ end Kernel panic - not syncing ]---\n);/* 等待panic_timeout秒后重启 */if (panic_timeout 0) {pr_emerg(Rebooting in %d seconds..\n, panic_timeout);for (i 0; i panic_timeout; i) {touch_nmi_watchdog();mdelay(1000);}emergency_restart();}/* 无限等待用户手动重启 */local_irq_enable();while (1)cpu_relax();}kmsg_dump机制是panic流程中最重要的输出通道之一。设备驱动或平台代码通过kmsg_dump_register注册dump器int kmsg_dump_register(struct kmsg_dumper *dumper){unsigned long flags;int err -EBUSY;spin_lock_irqsave(dump_list_lock, flags);if (!dumper-registered) {dumper-registered true;dumper-max_reason KMSG_DUMP_MAX;list_add_tail_rcu(dumper-list, dump_list);err 0;}spin_unlock_irqrestore(dump_list_lock, flags);return err;}struct kmsg_dumper结构包含核心的dump回调struct kmsg_dumper {struct list_head list;void (*dump)(struct kmsg_dumper *dumper,enum kmsg_dump_reason reason,const char *s1, unsigned long l1,const char *s2, unsigned long l2);enum kmsg_dump_reason max_reason;bool registered;bool active;};当panic调用kmsg_dump(KMSG_DUMP_PANIC)时遍历dump_list上的所有注册dump器void kmsg_dump(enum kmsg_dump_reason reason){struct kmsg_dumper *dumper;unsigned long flags;rcu_read_lock();list_for_each_entry_rcu(dumper, dump_list, list) {if (dumper-max_reason reason)continue;/* 防止dump回调中的死循环 */if (dumper-active)continue;dumper-active true;/* 从printk环形缓冲区迭代读取日志 */kmsg_dump_get_buffer(dumper, true,dumper-dump_buf,sizeof(dumper-dump_buf),len);if (len 0)dumper-dump(dumper, reason,dumper-dump_buf, 0,NULL, 0);dumper-active false;}rcu_read_unlock();}典型的kmsg_dump实现者包括1. MTD/Flash dump在嵌入式设备上将panic日志写入flash的最后一个分区2. pstore/ramoops将panic日志保存在保留的RAM区域中重启后通过/pstore文件系统读取3. netconsole将panic日志通过网络发送到远程日志服务器pstore的ramoops实现通过kmsg_dump_register注册回调其dump函数将日志写入预分配的物理内存区域static void ramoops_pstore_write(struct kmsg_dumper *dumper,enum kmsg_dump_reason reason,const char *s1, unsigned long l1,const char *s2, unsigned long l2){struct ramoops_context *cxt container_of(dumper,struct ramoops_context, dump);size_t hlen sizeof(struct persistent_ram_header);struct persistent_ram_zone *prz;if (reason ! KMSG_DUMP_PANIC reason ! KMSG_DUMP_OOPS)return;prz cxt-przs[cxt-dump_write_cnt % cxt-max_dump_cnt];persistent_ram_write(prz, s1, l1);if (s2)persistent_ram_write(prz, s2, l2);cxt-dump_write_cnt;}panic的另一个重要路径是crash_kexec。如果系统配置了kdumppanic_notifier的回调会调用crash_kexec来加载捕获内核static int kdump_panic_callback(struct notifier_block *self,unsigned long val, void *data){crash_kexec(NULL);return NOTIFY_DONE;}kdump机制在内核崩溃时启动一个预加载的capture kernel该内核在原内核预留的内存区域中运行收集崩溃现场的/proc/vmcore。