宋宝华: Kernel Oops和Panic是一回事吗?

很多童鞋误认为oops就是panic,实际上不是的,内核出错的时候,并不总是panic,有时候只是打印一个oops。当然,有时候打印oops的时候,也是要panic的,具体的区别是什么呢?本文用实例来说明。

Oops英文单词的中文含义是“哎呀”,表示“惊叹”;Panic英文单词的中文含义是“惊慌”。所以panic的程度显然是高于oops的,因为惊叹不一定会惊慌,而惊慌最容易失措,内核panic后,就死机了,俗称内核崩溃。但是内核报oops,这个时候不见得会panic,它可能只是报个oops,杀死进程而已。

下面我们用一个真实的例子来说明,我们在globalfifo.c这个简单的字符设备驱动中(详见宋宝华《Linux设备驱动开发详解》),在里面的file_operations的read成员函数,也即globalfifo_read()中,增加一个空指针的访问,代码如下:

static ssize_t globalfifo_read(struct file *filp, char __user *buf,

                               size_t count, loff_t *ppos)

{

        int ret;

        struct globalfifo_dev *dev = filp->private_data;

        DECLARE_WAITQUEUE(wait, current);

 

        /* do something bad */

        int *p = NULL;

        *p = 10;

              …

}

接下来我们用cat /dev/globalfifo命令来访问这个字符设备,促使globalfifo_read()被调用,我们看到的现象如下:

#

# cat /dev/globalfifo

[ 1951.693207] Unable to handle kernel NULL pointer dereference at virtual address 00000000

[ 1951.698797] pgd = 9ed00000

[ 1951.700266] [00000000] *pgd=7ed1e831, *pte=00000000, *ppte=00000000

[ 1951.704929] Internal error: Oops: 817 [#3] SMP ARM

[ 1951.707882] Modules linked in:

[ 1951.709298] CPU: 1 PID: 718 Comm: cat Tainted: G      D         4.0.0-rc1+ #49

[ 1951.714109] Hardware name: ARM-Versatile Express

[ 1951.719857] task: 9f699400 ti: 9f654000 task.ti: 9f654000

[ 1951.720861] PC is at globalfifo_read+0x50/0x20c

[ 1951.721373] LR is at 0x9f699400

[ 1951.721669] pc : [<802c4738>]    lr : [<9f699400>]    psr: a0000013

[ 1951.721669] sp : 9f655f18  ip : 80043e40  fp : 00000000

[ 1951.722614] r10: 7e83efa0  r9 : 00001000  r8 : 00001000

[ 1951.723108] r7 : 9f655f80  r6 : 9f4da3c0  r5 : 9f5da000  r4 : 9f5db040

[ 1951.723613] r3 : 00000000  r2 : 0000000a  r1 : 7e83efa0  r0 : 805aa514

[ 1951.724099] Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user

[ 1951.724772] Control: 10c5387d  Table: 7ed0006a  DAC: 00000015

[ 1951.725283] Process cat (pid: 718, stack limit = 0x9f654210)

[ 1951.725833] Stack: (0x9f655f18 to 0x9f656000)

[ 1951.726161] 5f00:                                                       00000004 00000000

[ 1951.734820] 5f20: 9f699400 80043e40 00000000 00000000 804d64ec 7e83efa0 00001000 9f655f80

[ 1951.738441] [<802c4738>] (globalfifo_read) from [<800e3864>] (__vfs_read+0x18/0x4c)

[ 1951.739014] [<800e3864>] (__vfs_read) from [<800e3914>] (vfs_read+0x7c/0x100)

[ 1951.739615] [<800e3914>] (vfs_read) from [<800e39d8>] (SyS_read+0x40/0x8c)

[ 1951.741263] [<800e39d8>] (SyS_read) from [<8000e720>] (ret_fast_syscall+0x0/0x40)

[ 1951.741888] Code: e58d3014 e58de008 e58dc00c e59f01a8 (e5832000)

[ 1951.754423] ---[ end trace be6bc9659b208c32 ]---

Segmentation fault

#

上面的oops里面打印了内核出错代码的原因是“Unable to handle kernelNULL pointer dereference at virtual address 00000000”,出错现场”globalfifo_read”和完整的backtrace(蓝色部分)。

但是我们看到结果只是应用程序收到了一个” Segmentation fault”,cat自己退出了,整个Linux系统并没有崩溃。

由此可见,oops并不都会panic,内核还可能继续运行。

我们研读一下内核的代码看一下原因是什么。首先*p=10这句话,访问了一个NULL地址,肯定会导致page fault,但是接下来page fault的处理过程如下,__do_kernel_fault()会调用到die()。

die()最终会调用到oops_end(),而猫腻就在oops_end()函数里面。

从代码逻辑可以看出,当这个oops发生的时候,如何in_interrupt()成立,或者panic_on_oops成立,都是直接panic(),否则只是以一个信号退出进程而已。

由此可见,如果我们在一个中断上下文,这个oops必须抛panic;否则如果只是一个进程上下文,打印个oops,进程退出即可。另外,如果/proc/sys/kernel/panic_on_oops设置的值是1,这个时候,不管你在什么上下文,都是要panic的。

In_interrupt()在如下三种情况下,都会成立:

  • 在硬中断;

  • 在软中断(soft irq);

  • 在NMI

下面我们来做一下修改panic_on_oops为1的实验,接着再访问/dev/globalfifo:

# echo 1 > /proc/sys/kernel/panic_on_oops

# cat /dev/globalfifo

[ 3119.083028] Unable to handle kernel NULL pointer dereference at virtual address 00000000

[ 3119.089507] pgd = 9edac000

[ 3119.090972] [00000000] *pgd=7ed1e831, *pte=00000000, *ppte=00000000

[ 3119.093989] Internal error: Oops: 817 [#4] SMP ARM

[ 3119.095296] Modules linked in:

[ 3119.095689] CPU: 1 PID: 720 Comm: cat Tainted: G      D         4.0.0-rc1+ #49

[ 3119.096355] Hardware name: ARM-Versatile Express

[ 3119.096802] task: 9f69e400 ti: 9f654000 task.ti: 9f654000

[ 3119.097338] PC is at globalfifo_read+0x50/0x20c

[ 3119.097772] LR is at 0x9f69e400

[ 3119.098104] pc : [<802c4738>]    lr : [<9f69e400>]    psr: a0000013

[ 3119.098104] sp : 9f655f18  ip : 80043e40  fp : 00000000

[ 3119.099352] r10: 7e985fa0  r9 : 00001000  r8 : 00001000

[ 3119.099820] r7 : 9f655f80  r6 : 9f4da900  r5 : 9f5da000  r4 : 9f5db040

[ 3119.101673] r3 : 00000000  r2 : 0000000a  r1 : 7e985fa0  r0 : 805aa514

[ 3119.102071] Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user

[ 3119.102471] Control: 10c5387d  Table: 7edac06a  DAC: 00000015

[ 3119.103785] Process cat (pid: 720, stack limit = 0x9f654210)

[ 3119.104227] Stack: (0x9f655f18 to 0x9f656000)

[ 3119.104611] 5f00:                                                       00000004 00000000

[ 3119.105346] 5f20: 9f69e400 80043e40 00000000 00000000 804d64ec 7e985fa0 00001000 9f655f80

[ 3119.111324] [<802c4738>] (globalfifo_read) from [<800e3864>] (__vfs_read+0x18/0x4c)

[ 3119.111840] [<800e3864>] (__vfs_read) from [<800e3914>] (vfs_read+0x7c/0x100)

[ 3119.112410] [<800e3914>] (vfs_read) from [<800e39d8>] (SyS_read+0x40/0x8c)

[ 3119.112996] [<800e39d8>] (SyS_read) from [<8000e720>] (ret_fast_syscall+0x0/0x40)

[ 3119.113468] Code: e58d3014 e58de008 e58dc00c e59f01a8 (e5832000)

[ 3119.159658] ---[ end trace be6bc9659b208c33 ]---

[ 3119.164336] Kernel panic - not syncing: Fatal exception


然后就没有然后了…


全文总结
非中断上下文的oops只是oops;
中断上下文oops要panic;
如果设置了panic_on_oops,任何oops都是panic。

内存管理是Linux里面最难最晦涩的部分,但是内存管理不清楚,Linux的很多现象又无法解释,很多环节都无法理清。这是宋宝华老师继《Linux的任督二脉:进程调度和内存管理》第一脉《进程、线程和调度》(http://edu.csdn.net/course/detail/5995)后的第二脉。


主要目的:

理解硬件访问内存的原理,MMU和页表;澄清Linux内核ZONE,buddy,slab管理;澄清用户空间malloc与内核关系,Lazy分配机制;澄清进程的内存消耗的vss,rss,pss,uss概念;澄清内存耗尽的OOM行为;澄清文件背景页面与匿名页,page cache与swap;澄清内存的回收、dirty page的写回,以及一些内存管理/proc/sys/vm sysctl配置的幕后原理;DMA和cache一致性,IOMMU等;给出一些内存相关的调试和优化方法;消除网上各种免费资料的各种误解。

最终形成一个Linux内存管理的全景视图。

直播时间: 2018.3.3-3.7,共五晚,9点-10点。


主要目的:

理解硬件访问内存的原理,MMU和页表;澄清Linux内核ZONE,buddy,slab管理;澄清用户空间malloc与内核关系,Lazy分配机制;澄清进程的内存消耗的vss,rss,pss,uss概念;澄清内存耗尽的OOM行为;澄清文件背景页面与匿名页,page cache与swap;澄清内存的回收、dirty page的写回,以及一些内存管理/proc/sys/vm sysctl配置的幕后原理;DMA和cache一致性,IOMMU等;给出一些内存相关的调试和优化方法;消除网上各种免费资料的各种误解。最终形成一个Linux内存管理的全景视图。

大纲与报名指南

报名:Linux的任督二脉之内存管理直播(2018.3.3-3.7五晚)