Kernel Exception基本流程

Kernel Exception基本流程

框架:

在这里插入图片描述

一张网络上的图通俗易懂。

空间布局:

32 bit 为例,先说概括一下用户空间吧;

在这里插入图片描述

对于32位的RAM来说,最大的访问空间是4G,其中用户空间占0-3G,每个进程独享的;内核空间占3G-4G,所有进程共享。

接下里是那1G大小的内核空间:

  • vmlinux代码/数据段:任何程序都有TEXT(可执行代码),RW(数据段),ZI段(未初始化数据段),用户空间也有,对应的是.text,.data,.bss
  • module区域:kernel可以支持ko(模块),因此需要一段空间用于存储代码和数据段。
  • vmalloc区域:kernel除了可以申请连续物理地址的内存外,还可以申请不连续的内存(虚拟地址是连续的),可以避免内存碎片化而申请不到内存。
  • io map区域:留给io寄存器映射的区域,有些版本没有io map区域而是直接用vmalloc区域了。
  • memmap:kernel是通过page结构体描述内存的,每一个页框都有对应的page结构体,而memmap就是page结构体数组。

随着Linux版本的更替,布局有稍许的变化,此处不深究。

对于32bit的来说,有高低内存之分:

hight memory:

ZONE_DMA 内存开始的16MB (直接存储器存取)

ZONE_NORMAL 16MB~896MB (正常的存取)

ZONE_HIGHMEM 896MB ~ 结束(1G) (高内存)

借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。

low memory:

由于kernel没办法将所有内存都映射进来,毕竟kernel自己只占1G,如果RAM超过1G,就无法全部映射。怎么办呢?只能先映射一部分了,这部分叫low memory。其他的就按需映射,VMALLOC区域就是用于按需映射的。

那如果是64bit的ARM:

可以讲整个RAM映射进来,就没有所谓的 high memory了,vmalloc区域功能除了外设寄存器也直接映射到vmalloc了,因此也就没有32bit布局里的 io map 区域了。

>= kernel-4.6/N0.MP8 kernel-4.4(patch back)

Start            End              Size   Use
-----------------------------------------------------------------------
0000000000000000 0000007fffffffff  512GB user
ffffff8000000000 ffffff8008000000  128MB modules
ffffff8008080000 ................  ..... vmlinux
ffffff8008000000 ffffffbdbfff0000  246GB vmalloc
ffffffbdbfff0000 ffffffbdc0000000   64KB [guard page]
ffffffbdc0000000 ffffffbfc0000000    8GB vmemmap
ffffffbfc0000000 ffffffbffe7fd000    ~1G [guard]
ffffffbffe7fd000 ffffffbffec00000 4108KB fixed mappings
ffffffbffec00000 ffffffbffee00000    2MB [guard]
ffffffbffee00000 ffffffbfffe00000   16MB PCI I/O
ffffffbfffe00000 ffffffc000000000    2MB [guard]
ffffffc000000000 ffffffffffffffff  256GB kernel logical memory map

KE的类别:

  1. oops(类似于assert,有机会恢复)
    • 内核行为表现为通知感兴趣模块,打印各种信息,如寄存器值,堆栈信息
    • 当出现oops时,我们就可以根据寄存器等信息调试并解决问题。
    • /proc/sys/kernel/panic_on_oops为1导致panic
  2. panic(死机、重启)
    • 表示Linux kernel遇到了一个不知道该怎么继续的情况。内核行为表现为通知感兴趣模块,死机或者重启
    • 在kernel代码里,有些代码加了错误检查,发现错误可能直接调用了panic(),并输出信息提供调试。

调试方法:

  • 在线调试, Online debug, 指的是在程序的运行过程中监视程序的行为,分析是否符合预期。通常会借助一些工具,如GDB和Trace32等。有时候也会借助一些硬件设备的协助,如仿真器/JTAG,但是准备环境非常困难,而且用起来也很麻烦,除非一些runtime问题需要外很少使用。
  • 离线调试, Offline debug, 指的是在程序的运行中收集需要的信息,在Bug发生后根据收集到的信息来分析的一种手段。通常也分为两种方式,一种是Logging,一种是Memory Dump。

前期异常处理:

在内核调用可以用来方便标记bug,提供断言并输出信息。最常用的两个是BUG()和BUG_ON()。当被调用的时候,它们会引发oops,导致栈的回溯和错误信息的打印。这就是软件主动回报异常的功能。

die()函数流程:

在这里插入图片描述

简单来说,触发oops之后会走到debug_locks_off()方法里打印一些log,如果这个异常是代码里调用BUG()/BUG_ON()引起的,那么会通过report_bug()方法进行额外的log说明。

再往下就是__die()方法,因为绝大部分关键信息都是由它输出的,因此它的流程如下:

在这里插入图片描述

__die()输出的内容大致有:

  • 基本错误信息,看一份kernel log有没有oops,直接搜索关键字Internal error就可以了。

  • CPU寄存器信息

  • 寄存器附近的内存

  • 调用栈

  • PC附近指令

panic()函数流程:

流程走到panic()就离异常重启不远了,panic()有标志性的log输出,大致如下:

在这里插入图片描述

关键字 Kernel panic

通知链:

panic()会调用栈上的回调函数通知感兴趣的模块,比如aee注册了回调函数,用于保存kernel log/mini dump等关键信息,并将其保存到emmc的expdb分区,等等重启后将其回读并保存成KE db。

nested panic流程:

有时die()/panic()流程不一定能正常走完,可能走到某一步又发生了异常,则就形成了嵌套,这种情况,我们一般不会关注后面的异常,而是关注最开始的那个异常。

为了避免异常嵌套,在发生第2次异常时,我们就拦截下来,我们在3个地方用于拦截nested panic:

  • do_PrefetchAbort()
  • do_DataAbort()
  • do_undefinstr()

拦截后不走die()/panic()流程,因为这些流程可能会再次发生异常,走我们写的函数aee_stop_nested_panic()函数,在这里面尽量少用kernel模块,很有可能也会发生异常,仅仅将寄存器等重要信息输出到ram console就等死(死循环等等看门狗复位!) 在这个db文件里:SYS_LAST_KMSG。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值