Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1379713
  • 博文数量: 860
  • 博客积分: 425
  • 博客等级: 下士
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-20 19:57
个人简介

对技术执着

文章分类

全部博文(860)

文章存档

2019年(16)

2018年(12)

2015年(732)

2013年(85)

2012年(15)

我的朋友

分类: Android平台

2018-03-30 14:36:18

. Linux Kernel Panic的产生的原因

     panic是英文中是惊慌的意思,Linux Kernel panic正如其名,linux kernel不知道如何走了,它会尽可能把它此时能获取的全部信息都打印出来。

有两种主要类型kernel panic,后面会对这两类panic做详细说明:

1.hard panic(也就是Aieee信息输出)
2.soft panic (也就是Oops信息输出)

2. 常见Linux Kernel Panic报错内容:

(1) Kernel panic-not syncing fatal exception in interrupt
(2) kernel panic – not syncing: Attempted to kill the idle task!
(3) kernel panic – not syncing: killing interrupt handler!
(4) Kernel Panic – not syncing:Attempted to kill init !

3. 什么会导致Linux Kernel Panic?

     只有加载到内核空间的驱动模块才能直接导致kernel panic,你可以在系统正常的情况下,使用lsmod查看当前系统加载了哪些模块。
除此之外,内建在内核里的组件(比如memory map等)也能导致panic。

     因为hard panic和soft panic本质上不同,因此我们分别讨论。

 

4. hard panic

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

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

4.1 原因

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

     本人就曾遇到过这样一个例子:在多核系统中,包括AP应用处理器、mcu微控制器和modem处理器等系统中,mcu控制器用于系统的低功耗控制,mcu微控制器由于某种原因超时向AP应用处理器发送一个超时中断,AP接受中断后调用中断处理函数读取mcu的状态寄存器,发现是mcu的超时中断,就在中断处理程序中主动引用一个空指针,迫使AP处理器打印堆栈信息然后重启linux系统。这就是一个典型的hard panic,这里不对mcu超时原因做深入的分析,只是用来说明hard panic产生的机理。

4. soft panic

4.1 症状:

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

4.2 原因

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

什么是fatal exception?

     “致命异常(fatal exception)表示一种例外情况,这种情况要求导致其发生的程序关闭。通常,异常(exception)可能是任何意想不到的情况(它不仅仅包括程序错误)。致命异常简单地说就是异常不能被妥善处理以至于程序不能继续运行。

     软件应用程序通过几个不同的代码层与操作系统及其他应用程序相联系。当异常(exception)在某个代码层发生时,为了查找所有异常处理的代码,各个代码层都会将这个异常发送给下一层,这样就能够处理这种异常。如果在所有层都没有这种异常处理的代码,致命异常(fatal exception)错误信息就会由操作系统显示出来。这个信息可能还包含一些关于该致命异常错误发生位置的秘密信息(比如在程序存储范围中的十六进制的位置)。这些额外的信息对用户而言没有什么价值,但是可以帮助技术支持人员或开发人员调试程序。

     当致命异常(fatal exception)发生时,操作系统没有其他的求助方式只能关闭应用程序,并且在有些情况下是关闭操作系统本身。当使用一种特殊的应用程序时,如果反复出现致命异常错误的话,应将这个问题报告给软件供应商。 ” 而且此时键盘无任何反应,必然使用reset键硬重启。

     panic.c源文件有个方法,当panic挂起后,指定超时时间,可以重新启动机器,这就是前面说的panic超时重启。如果你的机器事先配置好了魔法键的使用,就可以在超时之前通过魔法键使系统在重启前尽可能多的为你多做些事情,当然这些事情不是用来使系统恢复正常,而是尽量避免损失或导出一些有用信息来帮助后面的定位。

 

6. 一个kernel panic的解决之法

    相信使用linux kernel开发过驱动的兄弟都知道,kernel panic对系统带来的危害要比应用程序panic大的多,甚至可以用灾难来形容。对于应用程序的panic最多导致linux系统杀掉该用户进程,但对于kernel panic就没办法了,因为kernel是整个系统的管理者,自己出现问题了(当然是不可恢复的异常)就只能等待重启了。

    kernel panic的最大问题就是难于定位,对于一个开发者来说,有些kernel panic那简直就像是一场噩梦,上面主要说明了如何抓取kernel panic的方法和一些panic实例,当然,抓取panic的打印信息是解决panic的第一步也是关键一步,下面就根据自己曾碰到过的一个kernel panic做为实例来说明从出现panic到解决panic的一般方法。

6.1 抓取kernel panic信息

     没错,正如前面说的,这是第一步也是非常关键的一步,如果要解决一个kernel panic当然必须首先要知道它产生的地方,也就是说产生panic的内核函数调用栈,当前的内核调用栈记录了产生kernel panic时的函数调用关系链,这里我不在贴出相关的打印实例,这样的kernel panic网上也到处都是,而且还有很多的文章来说明如何确定是哪个源文件的哪一行导致的panic,因此感兴趣的同学可以搜索一些这样的文章看看,这里指说明一下解决kernel panic的一般步骤和注意事项。

     对于抓取kernel log的方法前面有介绍,这里不赘述,但想强调两点:

     (1) 不管是什么样的panic,首先要抓取足够的内核打印信息,当然必要的情况下还需要搜集产生kernel panic时的应用程序的打印信息,对于android系统来说就是logcat信息,在android嵌入式软件平台上其实有更好更全面的log搜集方法,那就是bugreport,它将产生此刻系统全方位的信息,对,没错,就是全方位的信息,包括内核、应用、内存、进程和处理器等所有相关信息,是一个非常好的调试工具,至于bugreport的工作原理感兴趣的同学自己查找下资料。

       注意:bugreport的使用需注意两点:第一,它只能在系统正常运行的情况下使用,第二,正因为第一点,你需要在系统产生kernel panic重启系统后的第一时间使用bugreport导出所有信息,因为这所有信息中包含了上次系统重启的原因的相关log信息。

     (2) 既然是抓取panic log信息,必然少不了复现panic这个过程,有的panic的产生时概率性随机的,就是说你不知道什么时候就可能会产生panic,因此请珍惜每一次复现panic的机会,起码要在复现panic之前准备好你要抓取的是那些信息,这些信息能否帮助你进一步定位panic,否则,不要在出现panic时手忙脚乱,不知道自己要什么,最好每次复现panic前计划好这次你要那些信息(可能每次抓取信息的重点不一样)。

      注意:在工作中经常碰到这样一个现象:测试部门的同学好不容易发现一个问题,请开发同学定位,开发同学基本上没怎么分析问题就嚷着信息抓的不够没法定位,结果让测试同学半天甚至一天来复现这个问题,等复现了问题开发同学还没搞清楚自己到底要什么信息来定位,有的问题复现时的环境只能保持几分钟甚至几十秒钟,这势必会浪费了测试同学的劳动成果。

6.2 分析kernel panic

     搜集了足够的panic信息,下面就是分析panic的时候了,对于一个panic问题,你要知道三点:

   (1) 首先要对汇编语言有一定的了解,定位panic产生的C代码位置

      其实就是根据当前内核线程的内核调用栈查找产生panic调用链,在panic log的前面几行已经显示了kernel panic的代码位置,但这个位置是相对于产生panic函数的偏移,你并不知道它到底是哪一行,这个时候你需要objdump反汇编器来对那个产生panic的镜像文件反汇编,然后根据panic信息的指示找到对应的汇编代码,对照C代码根据汇编上下文确定C代码行,其实,kernel panic的产生一般都是非法地址的引用,尤其是NULL指针的引用,这也比较容易定位出panic的C代码行。

   (2) 分析导致panic的C代码行上下文,确定panic引入点

      第一步应该会比较容易找到导致panic 的C代码行,根据产生panic的代码进一步找到panic的引入点,这一步可以搭配printk来定位(如果是大概率panic就更容易定位了),这一步相对第一步花费多一点的时间,如果是应用代码分析到这里已经差不多结束了,确定了panic引入点就可以修改代码进行回归测试了,但对于kernel来说要复杂的多。

      正如之前曾碰到的panic,复现虽然不容易但是基本上在固定时间点左右就可以复现,我是用的脚本循环加载卸载wifi模块,每次都是大约500次左右产生panic,要知道必现的panic就容易解决的多了,但当时因为这500次的循环就要花费2个小时左右,而且环境还经常出现问题,导致我花费很长时间才定位出问题所在:每次的加载和卸载wifi模块都导致devices kset节点引用计数多减一,当devices kset的引用计数变为0的时候被系统回收,linux系统随后可能会出现N种panic现象,之后发现是因为wifi模块每次加载下载时对应的设备节点的引用计数增减失衡导致devices kset被多减一,然后发现是linux 内核核心代码的问题。

    (3) 最好不要怀疑linux的核心代码,也不要试图去修改

      正因为这一点,让我迟迟不敢确定是不是真的核心代码问题,linux的核心代码那可是数以万计的大牛经过千锤百炼的代码,岂容你轻易修改,经过进一步的分析这个panic是因为我们用的wifi卡是非标准媒体卡,走的是非标准流程,在这个流程中对wifi设备初始化时少了一次wifi设备节点的引用,但在卸载模块时同标准卡一样被解引用了。

    (4)不要坚定的以为围绕着panic信息就能解决panic问题

      还是上面的panic,实际上,上面提到的panic问题其实应该是很多的panic,这也是在后期复现panic时发现的,在加载卸载500次左右时必现panic,但却不是同一个panic,如果按照正常思路:既然是panic,就应该从panic信息下手,顺藤摸瓜一直追下去。如果是这样,这个问题恐怕永远也解决不了,因为你在复现一个panic时总是会有其他的panic出现,这回让你无所适从的。

       通过对这些panic的log的分析,发现他们都有一个共性,在产生panic之前都有一段WARNING打印,也正是对这段打印的分析找出了问题的根源,对于这段WARNING的内容和分析过程不在这里说明,只为表达下面的观点:

       事实证明,在碰到panic问题定位时,如果想当一段时间内定位不出来,又没有什么更好的思路时,你应该回头看看在panic之前kernel是否产生了哪些不太正常的log,这也许就是导致kernel panic的前兆或推手。

     (5) 尽可能多的把握linux kernel的行为,对一些难啃的panic大胆猜测

       这里的大胆猜测是建立在想当了解linux kernel行为上的有理性的推理,尽管有些猜测并不是完全正确的,但在你证明它是正确的过程中或许会有意外收获。对于wifi模块加载卸载的panic问题来说,我曾有过两次错误的猜测:

       第一次,因为长时间的加载卸载都会出现panic,而且开始发现的panic是在kmem_cache_alloc函数中,因此猜测是内存泄露导致的内存耗尽,因此在后面的复现过程中我写了个脚本循环打印内存的使用情况,发现内存的占用一直稳定在一个正常的范围,证明了我的第一个猜测是错误的。

      第二次,在看到devices 的内核对象kobject的名字在panic之前出现乱码的情况下(printk打印了名字),我曾大胆地做过猜测:linux kernel发生了踩内存现象,导致devices kset对象被破坏。随后做了各方面的努力来证明我的想法,结果发现几乎都是在第493次加载wifi模块时出现的panic问题,这让我很迷惑,如果是踩内存怎么可能固定在493次发生,虽然不能完全证明这个猜想是错误的,但这足以说明我的方向有问题。

     如此反反复复,花费了我两个星期的时间才搞定这个panic,因此,kernel panic虽然难啃,但只要你愿意去尝试愿意去努力,就算最后拿不下这个panic,你也会学到很多很多的东西,包括linux kernel行为,这些会对你以后的学习产生很大的影响,在碰到这类问题一定是信心满满的。

7. 小结

     一直想总结一点kernel panic的解决之法,在网上也搜索了很多资料,基本上都一样,本文前面也引用了这些文章中的一篇,曾经做过的总结过的东西能记录下来给别人看和给以后自己复习都是很有意义的事情,以前kernel panic的问题总让我不敢靠的太近,现在我还是可以比较自信的面对他们,这里也只是给同学们一些解决panic的建议,个人觉得分析一个具体的实例的意义也不是太大,所以也没有对一个具体的实例做详细分析,希望可以找到更多的相关文章来拜读,夜已深沉,还有什么人...

什么是Oops?从语言学的角度说,Oops应该是一个拟声词。当出了点小事故,或者做了比较尴尬的事之后,你可以说"Oops",翻译成中国话就叫做“哎呦”。“哎呦,对不起,对不起,我真不是故意打碎您的杯子的”。看,Oops就是这个意思。

在Linux内核开发中的Oops是什么呢?其实,它和上面的解释也没什么本质的差别,只不过说话的主角变成了Linux。当某些比较致命的问题出现时,我们的Linux内核也会抱歉的对我们说:“哎呦(Oops),对不起,我把事情搞砸了”。Linux内核在发生kernel panic时会打印出Oops信息,把目前的寄存器状态、堆栈内容、以及完整的Call trace都show给我们看,这样就可以帮助我们定位错误。


这里面,0002表示Oops的错误代码(写错误,发生在内核空间),#1表示这个错误发生一次。

Oops的错误代码根据错误的原因会有不同的定义,本文中的例子可以参考下面的定义(如果发现自己遇到的Oops和下面无法对应的话,最好去内核代码里查找):

 

 * error_code:
 *      bit 0 == 0 means no page found, 1 means protection fault
 *      bit 1 == 0 means read, 1 means write
 *      bit 2 == 0 means kernel, 1 means user-mode
 *      bit 3 == 0 means data, 1 means instruction

有时候,Oops还会打印出Tainted信息。这个信息用来指出内核是因何种原因被tainted(直译为“玷污”)。具体的定义如下:

 

  1: 'G' if all modules loaded have a GPL or compatible license, 'P' if any proprietary module has been loaded.  Modules without a MODULE_LICENSE or with a MODULE_LICENSE that is not recognised by insmod as GPL compatible are assumed to be proprietary.
  2: 'F' if any module was force loaded by "insmod -f", ' ' if all modules were loaded normally.
  3: 'S' if the oops occurred on an SMP kernel running on hardware that hasn't been certified as safe to run multiprocessor. Currently this occurs only on various Athlons that are not SMP capable.
  4: 'R' if a module was force unloaded by "rmmod -f", ' ' if all modules were unloaded normally.
  5: 'M' if any processor has reported a Machine Check Exception, ' ' if no Machine Check Exceptions have occurred.
  6: 'B' if a page-release function has found a bad page reference or some unexpected page flags.
  7: 'U' if a user or user application specifically requested that the Tainted flag be set, ' ' otherwise.
  8: 'D' if the kernel has died recently, i.e. there was an OOPS or BUG.
  9: 'A' if the ACPI table has been overridden.
 10: 'W' if a warning has previously been issued by the kernel. (Though some warnings may set more specific taint flags.)
 11: 'C' if a staging driver has been loaded.
 12: 'I' if the kernel is working around a severe bug in the platform firmware (BIOS or similar).

基本上,这个Tainted信息是留给内核开发者看的。用户在使用Linux的过程中如果遇到Oops,可以把Oops的内容发送给内核开发者去debug,内核开发者根据这个Tainted信息大概可以判断出kernel panic时内核运行的环境。如果我们只是debug自己的驱动,这个信息就没什么意义了。

阅读(7204) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~