Board logo

标题: Kdump 原理探秘(2) [打印本页]

作者: look_w    时间: 2018-6-18 12:15     标题: Kdump 原理探秘(2)

神秘的 purgatory细心的 kdump 用户可能注意过在 kdump 刚开始运行时 console 上会出现这样一句话:“I'm in purgatory.”。这就是进入了 purgatory 时的提示。简单说,purgatory 就是一个 bootloader,一个为 kdump 定作的 boot loader。它被赋予了这样一个古怪的名字应该只是一种调侃。实事上,与其说是内核可以引导另一个内核,不如说是 purgatory 可以引导一个内核。 下面的分析完全基于 ppc64。
purgatory 和 kexec
在特定体系架构上编译 kexec 时,purgatory 会从相应特定体系的源码生成。它是一个 ELF 格式的 relocatable 文件。为了使用上的方 便,它被一个工具,“bin-to-hex”,翻译成一个数组并放在 kexec/purgatory.c 里。这样 kexec 的代码就可以直接使用它了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// kexec/purgatory.c
#iclude <stddef.h>
const char purgatory[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x02, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x15, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x48,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x1d,
... ...
0x79, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x00,
};
size_t purgatory_size = sizeof(purgatory);

// kexec/arch/ppc64/kexec-elf-ppc64.c 中分读入 purgatory:

/* Add v2wrap to the current image */
elf_rel_build_load(info, &info->rhdr, purgatory,
             purgatory_size, 0, max_addr, 1, 0);




在通过 kexec_load() 将 purgatory 传给 kernel 之前,kexec 还会对已经读入的 purgatory 进行一些改造,存进一些在引导 内核时必需的信息,如新内核在内存中的地址、device tree blob 的地址等等。值得注意的是,kexec 用捕获内核的前 256 个字节覆盖从 purgatory 入口点开始的 256 个字节(在 ppc64 上刚好是 64 条汇编指令,覆盖完成后,kexec 会把第一个指令恢复成 purgatory 自己原来的)。这一部分有两个功能:标明 kenrel 是否是 relocatable 的和让 slave cpu 等待 primary cpu。
purgatory 和 kernel
kernel 崩溃了,如果 kexec_load() 加载了捕获内核,它会先让没有发生崩溃的 cpu(slave cpu)通过调用 kexec_wait 进入等待:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_GLOBAL(kexec_wait)
           bl      1f
    1:      mflr    r5
           addi    r5,r5,kexec_flag-1b     
   
    99:     HMT_LOW
    #ifdef CONFIG_KEXEC             /* use no memory without kexec */
           lwz     r4,0(r5)
           cmpwi   0,r4,0
           bnea    0x60
    #endif  
           b       99b
            
    /* this can be in text because we won't change it until we are
    * running in real anyways
    */
    kexec_flag:
           .long   0




这里 kexec_wait 去检查“kexec_flag”的值(初始值是 0),如果是 0 则回到“99:”继续检查;如果不是 0 了,就跳到 0x60 处。这样发生崩溃的 cpu 可以从容地完成一些工作,例如把在 kexec 中得到的捕获内核的前 15 条指令和自己的第一条指令拷到内存的起点等等,再让 slave cpu 跳到 0x60 去等待 primary cpu 完成启动,这是在 kexec_sequence 中完成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
... ...
    /* copy dest pages, flush whole dest image */
       mr      r3,r29
       bl      .kexec_copy_flush       /* (image) */

       /* turn off mmu */
       bl      real_mode

       /* copy  0x100 bytes starting at start to 0 */
       li      r3,0
       mr      r4,r30          /* start, aka phys mem offset */
       li      r5,0x100
       li      r6,0
       bl      .copy_and_flush /* (dest, src, copy limit, start offset) */
1:      /* assume normal blr return */

       /* release other cpus to the new kernel secondary start at 0x60 */
       mflr    r5
       li      r6,1
       stw     r6,kexec_flag-1b(5)
    ... ...




bl 会把“1:”的地址存入 LR,因此 r5 中被 mflr 存入的是这个地址,而“kexec_flag-1b(5)”就是 kexec_flag 的地址了。stw 向这个地址存入了“1”。这都是在前面的拷贝和关闭 MMU 等完成之后了才做的。在 kexec_sequence 最后,将 purgatory 的入口地址存入 CTR,然后调用 bctrl 从而调用 purgatory。purgatory 则利用 kexec 中存入的一系列信息,最终启动了捕获内核。
作为 bootloader 的 purgatory
虽然 kexec 用捕获内核开头的 256 个字节覆盖了 purgatory 入口处的 256 个字节,但保留了入口的第一条指令,即 b master。于是 purgatory 会跳转到“master”,作一些校验工作;加载一系列自己正常运行和引导内核必不可少的信息,如内核(捕获内核)在内存中的位 置、device tree blob 的位置等。这些都是在 kexec 中生成、存放好了,然后写到 purgatory 的 ELF 格式中,同时也由 kexec_load() 传递给了生产内核的,下面是引导内核前的代码片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
80:
       LOADADDR(6,kernel)
       ld      4,0(6)          # load the kernel address
       LOADADDR(6,run_at_load) # the load flag
       lwz     7,0(6)          # possibly patched by kexec-elf-ppc64
       stw     7,0x5c(4)       # and patch it into the kernel
       li      5,0             # r5 will be 0 for kernel
       mtctr   4               # prepare branch too
       mr      3,16            # restore dt address

                               # skip cache flush, do we care?

       bctr                    # start kernel




此处,LOADADDR 是一个宏,它是一组 5 个的 ppc64 向寄存器加载 64 位即时数的专用指令。这里先把内核的保存位置载入寄存器 4,然后把 purgatory 中 run_at_load 处的值写入从该位置往后位移 0x5c 的地方。这个 run_at_load 是在被 kexec 用捕获内核覆盖的那部分中,kexec 在覆盖这部分时还作了一件事,就是检查捕获内核是否是可重定位的。如果是,就在这个位置上写入 1。因此,如果捕获内核是可重定位的内核,那么在 purgatory 引导它之前,距它开头 0x5c 处的值是 1。这个值告诉捕获内核它应该从它被加载的位置启动。接下来,将捕获内核的地址写入 CTR,用 bctr 来启动它。
kdump 实用小技巧如何设定 crashkernel 参数
在 kdump 的配置中,往往困惑于 crashkernel 的设置。“crashkernel=X@Y”,X 应该多大? Y 又应该设在哪里呢?实际我们 可以完全省略“@Y”这一部分,这样,kernel 会为我们自动选择一个起始地址。而对于 X 的大小,般对 i386/x86_64 的系统, 设为 128M 即可;对于 powerpc 的系统,则要设为 256M。rhel6 引入的“auto”已经要被放弃了,代之以原来就有的如下语法:
1
2
3
4
5
6
7
8
crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset]
             range=start-[end]

             'start' is inclusive and 'end' is exclusive.

             For example:

             crashkernel=512M-2G:64M,2G-:128M




如何判断捕获内核是否加载
可通过查看 /sys/kernel/kexec_crash_loaded 的值。“1”为已经加载,“0”为还未加载。
缩小 crashkernel
可以通过向 /sys/kernel/kexec_crash_size 中输入一个比其原值小的数来缩小甚至完全释放 crashkernel。
kdump 相关新技术kexec 原来只是用于内核的快速启动,但很快被用来实现内存转储,成为了企业级的的重要应用。但是创新的步伐 并未就此停止。
系统休眠使用 kexec/kdump 来实现系统休眠(hibernation)已经进行了几年了。目前的状态不得而知,但这种思路上的 创新很让人眼前一亮。目前只支持 x86 体系。相关内容参见最后的链接。
小结kdump 是目前最有效的 linux™ 内存镜像收集机制,广泛应用于各大 linux™ 厂商的各种产品中,在 debug 内核方面起着不可替换的重要作用。本文着重于深入探索 kdump 的实现机制,希望能让读者通过了解细节从而促进对 kdump 使用的掌握。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0