Linux panic内核恐慌与kmsg_dump注册回调
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。如果系统配置了kdump,panic_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。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)