momo zone

调核人的blog

panic 流程

内核在发生严重错误的时候要“X屏”一下,以windows为例就是蓝屏,而linux却是黑屏,windows仅仅提供一个出错码和对应地址,十分难以 理解,而linux却可能提供整个寄存器和堆栈,它不隐瞒一切,代码公开,当然出错时的环境就没有必要隐瞒了,首先看一下panic:

NORET_TYPE void panic(const char * fmt, …)

{

long i;

static char buf[1024];

va_list args;

preempt_disable(); //关闭抢占,否则此cpu执行到后面的死循环时还是有可能被抢占的,被抢占意味着可以切换到别的进程,但是在panic里,这是不希望的,企图出去 panic函数的任何行为都是不希望的。注意,这里关闭了抢占就再也不打开了。

bust_spinlocks(1);

va_start(args, fmt);

vsnprintf(buf, sizeof(buf), fmt, args);

va_end(args);

printk(KERN_EMERG “Kernel panic – not syncing: %s/n”,buf); //非常熟悉的一句话,后面接panic原因的文字描述

bust_spinlocks(0);

crash_kexec(NULL); //如果事先设定了kexec的内核就开始启动另一个内核,panic流程到此结束

#ifdef CONFIG_SMP

smp_send_stop();//要求别的cpu执行halt,实际上就是停止了别的cpu,而本cpu即导致panic的cpu最终将死循环。

#endif

atomic_notifier_call_chain(&panic_notifier_list, 0, buf);//处理panic通知链,有些程序会在此时被触发比如kgdb

if (!panic_blink)

panic_blink = no_blink;

if (panic_timeout > 0) {  //重启延时如果不为0则倒计时重启。

printk(KERN_EMERG “Rebooting in %d seconds..”,panic_timeout);

for (i = 0; i < panic_timeout*1000; ) {  //只能用这种方式来倒计时了,进入panic意味着出现严重错误,因此最好呆在panic里面,不要触及外面的任何东西,timer这时可能已经不好用了,因此就用这种方式吧,否则将导致更严重的错误。

touch_nmi_watchdog();

i += panic_blink(i);

mdelay(1);

i++;

}

emergency_restart(); //重新启动

}

local_irq_enable();  // 打开了中断,因此在下面的死循环里还是可以响应中断的,但是如果是在中断上下文中引起panic,即in_interrupt()为真的情况引 起 panic的话,虽然硬件中断可以响应但是软中断就不可响应了,因为软中断执行时in_interrupt()必须为假才行。

for (i = 0;;) {

touch_softlockup_watchdog(); //复位一次watchdog,到了这个地步不要让狗再触发一次soft lockup的panic 了

i += panic_blink(i);

mdelay(1);

i++;

}

}

由 上述panic可以看出,panic并没有什么大不了的,就是禁抢占,善后,禁止了抢占实际上就禁止了调度,因为禁止抢占的情况下除非自己放弃cpu才能 调度,但是我们看看那个死循环根本没有放弃cpu的意思。发生panic无非就两种情形,一个是在中断上下文,另一个是在非中断上下文。在中断上下文中 panic的话,此时的in_interrupt()一定返回真,如果此硬件中断触发了一个软中断,那么这个软中断是永远不会被执行的,因为导致 panic的中断永远不会调用irq_exit来使得in_interrupt()返回假。这就解释了为何在中断中panic的系统ping不通,而在非 中断上下文中panic虽然内核死掉了但是还是可以从外部ping通的。想想ping的执行,ping依靠的是icmp协议,没有进程上下文,它是ip层 的内容,而ip层是不区分进程的,于是icmp的处理只在软中断上下文进行,既然硬件中断中panic了,根据上面的论述,软中断就没有机会执行了,于是 就ping不通了;而非中断的panic由于软中断可以执行,所以可以ping通,但是内核还是不能恢复了,因为已经关闭可抢占,无法调度了,于是执行流出去中断后便会一直执行那个死循环,没有机会执行别的。
这就是panic,从字面意义上就是“惊慌”的意思,如果一个人惊慌了,那么他的行为无非两种,一是失控地四处乱跑,而是在原地惊呆,内核是绝对不会选择 前者的,如果一个人四处乱窜的话,一定会有人帮他镇静,比如警察或者他的家人,要么就是医生,最终还是他要呆在一个地方静止,内核当然是一步到位了,直接不允许乱窜,人在乱窜的时候十有八九会导致事故,比如推翻了桌子,而桌子上有一只很贵的花瓶,内核也考虑到了,为了免得次生破坏,那么直接呆在原地不要动 了。因此你看不见任何出去panic的路子。当然会有一些提示,用来提示发生了严重错误,内核panic,类似于拨打了120电话,在一些平台上就是键盘 灯不停地闪烁。那么在代码中怎么体现呢?可以肯定的是,键盘闪烁一定是死循环中调用的函数的行为,而这个死循环如此之短使得把它揪出来很容易,看看 panic_blink函数,它是一个函数指针,不同的平台指向的函数不同,在大多数x86的pc上就是i8042_panic_blink:

static long i8042_panic_blink(long count)

{

long delay = 0;

static long last_blink;

static char led;

if (!i8042_blink_frequency)

return 0;

if (count – last_blink < i8042_blink_frequency)

return 0;

led ^= 0x01 | 0x04;

while (i8042_read_status() & I8042_STR_IBF)

DELAY;

i8042_write_data(0xed); /* set leds */

DELAY;

while (i8042_read_status() & I8042_STR_IBF)

DELAY;

DELAY;

i8042_write_data(led);

DELAY;

last_blink = count;

return delay;

}

这 个函数还用说吗?只要知道它的作用就完美了,硬件相关的东西如果你没有那个硬件的手册或者是你根本就不需要做那个硬件的相关的东西就没有必要浪费时间去研究,只要知道接口的意义就可以了。在那个死循环里还有一个重要的调用涉及到一个nmi的看门狗,nmi在内核挂起从而需要调试的时候很有用,nmi是 non maskable interrupt的意思,就是不可屏蔽中断,试想当内核挂起,键盘不再响应,甚至中断都不再执行或者无法输出堆栈信息的时候,nmi实际上就有用 了,nmi中断无论如何都要发生,从而nmi的中断处理函数无论如何都要执行,那么就可以在这个中断处理中输出调试信息或进行转储,它可能会调用die函 数,然后引发一个panic,最终输出信息或转储,至于具体代码就不说了。记住,如果发生oops,那么还有一招可以用,就是Alt+Sysrq+X组合 键,X为具体命令,请查手册,比如X为m就是导出内存信息,但是如果组合键都不响应了,那么请看看你的内核是否彻底关中断锁住了,还有就是看看组合键是否在软中断上下文被处理而导致panic的是在硬件中断上下文,起码在2.6内核引入input_dev后,这种情况成为了可能,人们为了追求和谐与统一可 以把一切键盘码的处理都放到软中断,但是却导致了组合键在oops中无法响应的问题
以上谈了panic,下面就看看BUG_ON,这也是一个panic点,但是却不是一定要panic,只有在平台不提供bug验证代码的时候才会调用 panic,因为无法处理这个哪怕小小的bug,那么就可能引发大大的崩溃,而何时引发以及危害如何是不确定的,因此还不如直接panic,停止内核,将以下的事情交给调试人员。看看代码:

#ifndef HAVE_ARCH_BUG_ON

#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0)

#endif

以上视为平台没有提供BUG_ON,以下为平台提供了BUG_ON

#ifndef HAVE_ARCH_BUG

#define BUG()

#endif

对于没有提供BUG_ON的平台,内核的定义为在满足条件的时候调用BUG,而BUG定义如下:

#ifndef HAVE_ARCH_BUG

#define BUG() do { /

printk(“BUG: failure at %s:%d/%s()!/n”, __FILE__, __LINE__, __FUNCTION__); /

panic(“BUG!”); /

} while (0)

#endif

可以看出最终调用了panic,内核就此game over,而对于平台的BUG_ON,这里以i386为例:(2.6.17内核)

#define BUG() __asm__ __volatile__(“ud2/n”)

ud2 在x86上是无效指令,执行之会引发错误从而输出堆栈或转储,但是看出问题了吗?仅仅一条无效指令导致堆栈回溯,然后呢?然后当然就恢复正常了,这难道是希望的行为吗?出错信息显然导到终端,但是是否所有人都会在意终端的输出呢?更好的做法应该是一旦BUG()被调用,立马挂起内核,像panic那样,但 是不善后。实际上内核中的任何哪怕非常小的bug都可能导致严重的后果,因此不要忽略任何bug,并且不要放过它。于是在2.6.20以及以后的内核的 BUG宏的定义就改变了:

#define BUG()                                                           /

do {                                                            /

asm volatile(“ud2”);                                    /

for(;;) ;                                               /

} while(0)

但是还是有问题,就是for循环在抢占打开的情况下可以被抢占,不过这个问题看似没有那么严重,毕竟严重错误的时候内核就直接panic了,而BUG只是一些不希望的事情确实发生了时候才调用的,最起码它停掉了一条执行流,该条执行流将永远堵在for循环里,万劫不复!

hard panic

一般出现下面的情况,就认为是发生了kernel panic:

  1. 机器彻底被锁定,不能使用
  2. 数字键(Num Lock),大写锁定键(Caps Lock),滚动锁定键(Scroll Lock)不停闪烁。
  3. 如果在终端下,应该可以看到内核dump出来的信息(包括一段”Aieee”信息或者”Oops”信息)
  4. 和Windows蓝屏相似

原因:

对于hard panic而言,最大的可能性是驱动模块的中断处理(interrupt handler)导致的,一般是因为驱动模块在中断处理程序中访问一个空指针(null pointre)。一旦发生这种情况,驱动模块就无法处理新的中断请求,最终导致系统崩溃。

soft panic

症状:

  1. 没有hard panic严重
  2. 通常导致段错误(segmentation fault)
  3. 可以看到一个oops信息,/var/log/messages里可以搜索到’Oops’
  4. 机器稍微还能用(但是收集信息后,应该重启系统)

原因:

凡是非中断处理引发的模块崩溃都将导致soft panic。在这种情况下,驱动本身会崩溃,但是还不至于让系统出现致命性失败,因为它没有锁定中断处理例程。导致hard panic的原因同样对soft panic也有用(比如在运行时访问一个空指针)

信息收集:
当soft panic发生时,内核将产生一个包含内核符号(kernel symbols)信息的dump数据,这个将记录在/var/log/messages里。为了开始排查故障,可以使用ksymoops工具来把内核符号信息转成有意义的数据。

为了生成ksymoops文件,需要:

  • 从/var/log/messages里找到的堆栈跟踪文本信息保存为一个新文件。确保删除了时间戳(timestamp),否则ksymoops会失败。
  • 运行ksymoops程序(如果没有,请安装)
  • 详细的ksymoops执行用法,可以参考ksymoops(8)手册。

hard lockup

意思是在关中断情况下发生死锁(大多数情况都是在硬中断中),不一定是真的死锁,实际是如果关中断时间太长 ,就会触发hard panic。比如下面的一段代码,在线程环境下:

local_irq_disable();

mdelay(40000);

local_irq_enable();

本地中断被关闭,导致超过30秒时钟中断没有发生,虽然不是在中断环境下,但长时间关闭中断相当于模拟了中断环境下的死循环。于是NMI中断程序(NMI是关不掉的)触发panic ,这个panic就是hard panic。

[ 1123.480906] Kernel panic – not syncing: Watchdog detected hard LOCKUP on cpu 1
[ 1134.945031] ttyS0: 1 input overrun(s)
[ 1123.480906] Pid: 2306, comm: insmod Not tainted 3.1.0-1.3-desktop-mod #1
[ 1123.480906] Call Trace:
[ 1123.480906] [<ffffffff8100441a>] dump_trace+0xaa/0x2b0
[ 1123.480906] [<ffffffff81587c90>] dump_stack+0x69/0x6f
[ 1123.480906] [<ffffffff8158a200>] panic+0xa4/0x1b8
[ 1123.480906] [<ffffffff810c3366>] watchdog_overflow_callback+0xb6/0xc0
[ 1123.480906] [<ffffffff810f05d6>] __perf_event_overflow+0x96/0x200
[ 1123.480906] [<ffffffff81015b34>] intel_pmu_handle_irq+0x194/0x250
[ 1123.480906] [<ffffffff815a3409>] perf_event_nmi_handler+0x29/0xb0
[ 1123.480906] [<ffffffff815a4f55>] notifier_call_chain+0x45/0x60
[ 1123.480906] [<ffffffff815a4fb5>] __atomic_notifier_call_chain+0x45/0x70
[ 1123.480906] [<ffffffff815a501d>] notify_die+0x2d/0x40
[ 1123.480906] [<ffffffff815a2577>] default_do_nmi+0x37/0x220
[ 1123.480906] [<ffffffff815a2948>] do_nmi+0x68/0x70
[ 1123.480906] [<ffffffff815a2330>] nmi+0x20/0x30
[ 1123.480906] [<ffffffff8100a020>] rtc_cmos_write+0x10/0x10
[ 1123.480906] [<ffffffff812c1016>] delay_tsc+0x76/0xf0
[ 1123.480906] [<ffffffffa0005019>] test_init+0x19/0x1000 [test]
[ 1123.480906] [<ffffffff810001ca>] do_one_initcall+0x3a/0x180
[ 1123.480906] [<ffffffff81093418>] sys_init_module+0xb8/0x230
[ 1123.480906] [<ffffffff815a8a52>] system_call_fastpath+0x16/0x1b
[ 1123.480906] [<00007f166d3aec8a>] 0x7f166d3aec89

[ 1123.480906] panic occurred, switching back to text console
[ 1135.142308] ————[ cut here ]————
[ 1135.142308] WARNING: at arch/x86/kernel/smp.c:118 try_to_wake_up+0x161/0x1f0()
[ 1135.142308] Hardware name: 7762WDX
[ 1135.142308] Modules linked in: test(+) af_packet rfcomm bnep cpufreq_conservative microcode cpufreq_userspace cpufreq_powersave acpi_cpufreq mperf dm_mod arc4 ath5k ath mac80211 snd_hda_codec_analog ecb pcmcia btusb joydev snd_hda_intel snd_hda_codec snd_hwdep ppdev thinkpad_acpi snd_pcm sdhci_pci snd_timer sr_mod bluetooth sdhci cdrom pcspkr i2c_i801 sg firewire_ohci ac battery parport_pc parport mmc_core yenta_socket firewire_core cfg80211 tpm_tis pcmcia_rsrc tpm pcmcia_core snd tpm_bios crc_itu_t e1000e iTCO_wdt soundcore snd_page_alloc iTCO_vendor_support rfkill hdaps tp_smapi thinkpad_ec autofs4 i915 drm_kms_helper drm i2c_algo_bit thermal processor video button thermal_sys ata_generic [last unloaded: test]
[ 1135.142308] Pid: 2306, comm: insmod Not tainted 3.1.0-1.3-desktop-mod #1
[ 1135.142308] Call Trace:
[ 1135.142308] [<ffffffff8100441a>] dump_trace+0xaa/0x2b0
[ 1135.142308] [<ffffffff81587c90>] dump_stack+0x69/0x6f
[ 1135.142308] [<ffffffff810533cb>] warn_slowpath_common+0x7b/0xc0       //试图唤醒之前被NMI中断的线程,发现该
[ 1135.142308] [<ffffffff8104b9f1>] try_to_wake_up+0x161/0x1f0                       //线程睡了很久,就再报一次kernel taint,之后
[ 1135.142308] [<ffffffff810761c9>] autoremove_wake_function+0x9/0x30     //继续执行panic流程中的LED闪灯代码,我明白,我要去按reset了~
[ 1135.142308] [<ffffffff81039128>] __wake_up_common+0x58/0x90
[ 1135.142308] [<ffffffff8103a963>] __wake_up+0x43/0x70
[ 1135.142308] [<ffffffff810649e4>] update_process_times+0x44/0x80
[ 1135.142308] [<ffffffff810870eb>] tick_sched_timer+0x5b/0xc0
[ 1135.142308] [<ffffffff81079c4e>] __run_hrtimer+0x6e/0x240
[ 1135.142308] [<ffffffff8107a675>] hrtimer_interrupt+0xe5/0x200
[ 1135.142308] [<ffffffff8101e8a3>] smp_apic_timer_interrupt+0x63/0xa0
[ 1135.142308] [<ffffffff815a959e>] apic_timer_interrupt+0x6e/0x80                  //panic里打开了中断,这里进入了中断程序
[ 1135.142308] [<ffffffff8158a2d1>] panic+0x175/0x1b8
[ 1135.142308] [<ffffffff810c3366>] watchdog_overflow_callback+0xb6/0xc0
[ 1135.142308] [<ffffffff810f05d6>] __perf_event_overflow+0x96/0x200
[ 1135.142308] [<ffffffff81015b34>] intel_pmu_handle_irq+0x194/0x250
[ 1135.142308] [<ffffffff815a3409>] perf_event_nmi_handler+0x29/0xb0
[ 1135.142308] [<ffffffff815a4f55>] notifier_call_chain+0x45/0x60
[ 1135.142308] [<ffffffff815a4fb5>] __atomic_notifier_call_chain+0x45/0x70
[ 1135.142308] [<ffffffff815a501d>] notify_die+0x2d/0x40
[ 1135.142308] [<ffffffff815a2577>] default_do_nmi+0x37/0x220
[ 1135.142308] [<ffffffff815a2948>] do_nmi+0x68/0x70
[ 1135.142308] [<ffffffff815a2330>] nmi+0x20/0x30
[ 1135.142308] [<ffffffff8100a020>] rtc_cmos_write+0x10/0x10
[ 1135.142308] [<ffffffff812c1016>] delay_tsc+0x76/0xf0
[ 1135.142308] [<ffffffffa0005019>] test_init+0x19/0x1000 [test]
[ 1135.142308] [<ffffffff810001ca>] do_one_initcall+0x3a/0x180
[ 1135.142308] [<ffffffff81093418>] sys_init_module+0xb8/0x230
[ 1135.142308] [<ffffffff815a8a52>] system_call_fastpath+0x16/0x1b
[ 1135.142308] [<00007f166d3aec8a>] 0x7f166d3aec89
[ 1135.142308] —[ end trace f606e083ac04fd4b ]—

hard lockup detect 机制依赖nmi中断,也就nmi中断响应函数中对kernel/watchdog.c 中watchdog_overflow_callback的调用。注意,这里也可以看出这段代码虽然是在watchdog.c 中,但实际喂狗的不是时钟中断,而是nmi中断。和下面说的soft lockup完全不同

soft lockup

其实就是在没有关中断的情况下发生了lockup,由于在没有关闭硬中断的情况下,正常的话时钟中断应该会被及时响应,时钟中断的处理函数会触发kernel watchdog更新时间戳。如果这种情况下系统非常繁忙以至时钟中断都响应不及时,那么将造成kernel watchdog时间戳间隔过大,那么他将发出侦测到soft lockup的情况。

http://www.diybl.com/course/6_system/linux/Linuxjs/20110503/554874.html

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: