系统启动(11)内核解压

3382阅读 0评论2012-07-28 qtdszws
分类:LINUX

前面兜了一大圈,罗嗦了许多,就是为了更好地理解arch/x86/boot/compressed/head_32.S是如何工作的。
    __HEAD

宏定义,在linux/init.h中:
#define __HEAD             .section        ".head.text","ax"

ENTRY(startup_32)
#现在是32位代码模式了
    cld
    /*
     * Test KEEP_SEGMENTS flag to see if the bootloader is asking
测试KEEP_SEGMENTS标志,看看是否bootloader要求不要重新初始化段寄存器
     * us to not reload segments
我们的场景中,没有设置这个标志
     */
    testb    $(1<<6), BP_loadflags(%esi)

#在generated/asm-offsets.h中:
#define BP_loadflags 529 /* offsetof(struct boot_params, hdr.loadflags) # */
esi指向boot_params

    jnz    1f

    cli  关中断
    movl    $__BOOT_DS, %eax
    movl    %eax, %ds
    movl    %eax, %es
    movl    %eax, %fs
    movl    %eax, %gs
    movl    %eax, %ss
1:
/*
 * Calculate the delta between where we were compiled to run
 * at and where we were actually loaded at.  This can only be done
计算编译地址和加载地址之间的偏移。
 * with a short local call on x86.  Nothing  else will tell us what
只能使用近距call指令。
 * address we are running at.  The reserved chunk of the real-mode
 * data at 0x1e4 (defined as a scratch field) are used as the stack
 * for this calculation. Only 4 bytes are needed.
保留的scratch字段用于此计算
 */
    leal    (BP_scratch+4)(%esi), %esp #是esp指向boot_params->e820_e820_entries,现在中断已关(realmode_switch_hook和前面的cli),不需要担心
/*
    __u32 scratch;        /* Scratch field! */    /* 0x1e4 */
    __u8  e820_entries;                /* 0x1e8 */

*/   
    call    1f
1:    popl    %ebp #ebp中是1的线性地址,同时被保存到scratch中
    subl    $1b, %ebp #ebp减去1的编译时地址,即为加载偏移
/*
 * %ebp contains the address we are loaded at by the boot loader and %ebx
%ebp包含加载地址1M,%ebx包含编译地址16M
 * contains the address where we should move the kernel image temporarily
 * for safe in-place decompression.
 */

#ifdef CONFIG_RELOCATABLE #一般都定义了此宏
    movl    %ebp, %ebx # 0x100000,1M
    movl    BP_kernel_alignment(%esi), %eax # 一般是16M
    decl    %eax # 0x1000000->0xFFFFFF
    addl    %eax, %ebx# 0x10FFFFF
    notl    %eax# 0xFF00000
    andl    %eax, %ebx#0x1000000
#else
    movl    $LOAD_PHYSICAL_ADDR, %ebx # 0x1000000 = 16M
#endif
    /* Target address to relocate to for decompression 重定向到地址,便于解压*/
    addl    $z_extract_offset, %ebx

    /* Set up the stack */
    leal    boot_stack_end(%ebx), %esp//建立栈

    /* Zero EFLAGS */
    pushl    $0
    popfl

/*
 * Copy the compressed kernel to the end of our buffer
复制压缩过的内核到缓冲区尾部,这样解压操作就安全了
 * where decompression in place becomes safe.
 */
    pushl    %esi
    leal    (_bss-4)(%ebp), %esi #减去4,只复制_bss和startup_32之间的数据,即_bss地址前面的数据,包括压缩内核
    leal    (_bss-4)(%ebx), %edi
    movl    $(_bss - startup_32), %ecx//长度
    shrl    $2, %ecx
    std #反向复制
    rep    movsl
    cld
    popl    %esi//还原esi
/*
 * Jump to the relocated address.跳到重定位的地址
 */
    leal    relocated(%ebx), %eax
    jmp    *%eax
ENDPROC(startup_32)

    .text
relocated:#现在是16M+$z_extract_offset+$relocated的地址了

/*
 * Clear BSS (stack is currently empty)
初始化BSS
 */
    xorl    %eax, %eax
    leal    _bss(%ebx), %edi
    leal    _ebss(%ebx), %ecx
    subl    %edi, %ecx
    shrl    $2, %ecx
    rep    stosl

/*
 * Adjust our own GOT
调整GOT
 */
    leal    _got(%ebx), %edx
    leal    _egot(%ebx), %ecx
1:
    cmpl    %ecx, %edx
    jae    2f
    addl    %ebx, (%edx)#修正
    addl    $4, %edx
    jmp    1b
2:

/*
 * Do the decompression, and jump to the new kernel..
执行解压,然后跳到新内核
 */
    leal    z_extract_offset_negative(%ebx), %ebp #即16M物理地址
                /* push arguments for decompress_kernel: */
    pushl    %ebp        /* output address */
    pushl    $z_input_len    /* input_len */
    leal    input_data(%ebx), %eax #输入数据
    pushl    %eax        /* input_data */
    leal    boot_heap(%ebx), %eax #临时堆
    pushl    %eax        /* heap area */
    pushl    %esi        /* real mode pointer */
    call    decompress_kernel #解压
    addl    $20, %esp #平栈


decompress_kernel定义在compressed/misc.c中
asmlinkage void decompress_kernel(void *rmode, memptr heap,
                  unsigned char *input_data,
                  unsigned long input_len,
                  unsigned char *output)
{
    real_mode = rmode;

    if (cmdline_find_option_bool("quiet"))
        quiet = 1;
    if (cmdline_find_option_bool("debug"))
        debug = 1;

    if (real_mode->screen_info.orig_video_mode == 7) {
        vidmem = (char *) 0xb0000;
        vidport = 0x3b4;
    } else {
        vidmem = (char *) 0xb8000;
        vidport = 0x3d4;
    }

    lines = real_mode->screen_info.orig_video_lines;
    cols = real_mode->screen_info.orig_video_cols;

    console_init();
    if (debug)
        putstr("early console in decompress_kernel\n");

    free_mem_ptr     = heap;    /* Heap */
    free_mem_end_ptr = heap + BOOT_HEAP_SIZE;

    if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
        error("Destination address inappropriately aligned");
#ifdef CONFIG_X86_64
    if (heap > 0x3fffffffffffUL)
        error("Destination address too large");
#else
    if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))
        error("Destination address too large");
#endif
#ifndef CONFIG_RELOCATABLE
    if ((unsigned long)output != LOAD_PHYSICAL_ADDR)
        error("Wrong destination address");
#endif

    if (!quiet)
        putstr("\nDecompressing Linux... ");
    decompress(input_data, input_len, NULL, NULL, output, NULL, error);
    parse_elf(output);
    if (!quiet)
        putstr("done.\nBooting the kernel.\n");
    return;
}

主要是调用decompress执行解压,目前内核的压缩和解压方法支持很多种,包括bzip2,inflate,lzma,lzo,xz等,根据配置不同可选用不同算法。

解压成功后,调用parse_elf

static void parse_elf(void *output)
{
#ifdef CONFIG_X86_64
    Elf64_Ehdr ehdr;
    Elf64_Phdr *phdrs, *phdr;
#else
    Elf32_Ehdr ehdr;
    Elf32_Phdr *phdrs, *phdr;
#endif
    void *dest;
    int i;

    memcpy(&ehdr, output, sizeof(ehdr));
    if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
       ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
       ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
       ehdr.e_ident[EI_MAG3] != ELFMAG3) {
        error("Kernel is not a valid ELF file");
        return;
    }//检查是否为ELF幻数

    if (!quiet)
        putstr("Parsing ELF... ");

    phdrs = malloc(sizeof(*phdrs) * ehdr.e_phnum);//准备复制程序头表
    if (!phdrs)
        error("Failed to allocate space for phdrs");

    memcpy(phdrs, output + ehdr.e_phoff, sizeof(*phdrs) * ehdr.e_phnum);

    for (i = 0; i < ehdr.e_phnum; i++) {//遍历程序头
        phdr = &phdrs[i];

        switch (phdr->p_type) {
        case PT_LOAD:
#ifdef CONFIG_RELOCATABLE
            dest = output;//前面是ELF头和程序头,将代码移到16M处
            dest += (phdr->p_paddr - LOAD_PHYSICAL_ADDR);//0x01000000-16M=0
#else
            dest = (void *)(phdr->p_paddr);
#endif
            memcpy(dest,
                   output + phdr->p_offset,
                   phdr->p_filesz);//移动代码和数据到正确的位置
            break;
        default: /* Ignore other PT_* */ break;
        }
    }
}

到这里,内核代码和数据已就位

#if CONFIG_RELOCATABLE
/*
 * Find the address of the relocations.
 */
    leal    z_output_len(%ebp), %edi

%ebp值是16M,z_output_len值是内核大小(包括vmlinux.relocs),%edi是内核尾部地址,实际上是vmlinux.relocs的结束地址

/*
 * Calculate the delta between where vmlinux was compiled to run
 * and where it was actually loaded.
计算编译地址和加载地址的偏移
 */
    movl    %ebp, %ebx
    subl    $LOAD_PHYSICAL_ADDR, %ebx//计算差值
    jz    2f    /* Nothing to be done if loaded at compiled addr. 地址未变动*/

/*
 * Process relocations.处理重定位
 */

1:    subl    $4, %edi//地址由高往低
    movl    (%edi), %ecx//取重定位地址
    testl    %ecx, %ecx//主要,前面生成vmlinux.relocs,最前面加入一个双字0,作为重定位结束标志
    jz    2f//已结束重定位
    addl    %ebx, -__PAGE_OFFSET(%ebx, %ecx)//%ecx的值是内核线性地址,从PAGE_OFFSET(一般3G)开始,所有要减去PAGE_OFFSET,成为物理地址,加上加载偏移,得到真实的重定位位置,将该处值再加上加载偏移,进行修正
    jmp    1b//循环
2:
#endif

/*
 * Jump to the decompressed kernel.
 */
    xorl    %ebx, %ebx
    jmp    *%ebp//跳到16M处,即arch/x86/kernel/head_32.S的startup_32处

/*
 * Stack and heap for uncompression
 */
    .bss
    .balign 4
boot_heap:
    .fill BOOT_HEAP_SIZE, 1, 0 ##define BOOT_HEAP_SIZE    0x8000
boot_stack:
    .fill BOOT_STACK_SIZE, 1, 0##define BOOT_STACK_SIZE    0x1000
boot_stack_end:

上一篇:系统启动(9)构建系统之三次连接生成vmlinux
下一篇:使用cacti监控windows下cpu温度