32位x86保护模式代码

4 保护模式下的内核代码

4.1 3232位x86保护模式代码

到保护模式的代码了,最先执行的代码就是arch/x86/boot/compressed/head_32.S中的startup_32,对于bzImagegrub把它加载到0x100000的位置。

 

首先,来到34ENTRY(startup_32),在这里,第一条保护模式的指令开始了。注意,前面的set_gdt仅仅是为了进入保护模式后的ljmp,意义并不大。

 

  34ENTRY(startup_32)

  35        cld

  36        /*

  37         * Test KEEP_SEGMENTS flag to see if the bootloader is asking

  38         * us to not reload segments

  39         */

  40        testb   $(1<<6), BP_loadflags(%esi)

  41        jnz     1f

  42

  43        cli

  44        movl    $__BOOT_DS, %eax

  45        movl    %eax, %ds

  46        movl    %eax, %es

  47        movl    %eax, %fs

  48        movl    %eax, %gs

  49        movl    %eax, %ss

  501:

  51

  52/*

  53 * Calculate the delta between where we were compiled to run

  54 * at and where we were actually loaded at.  This can only be done

  55 * with a short local call on x86.  Nothing  else will tell us what

  56 * address we are running at.  The reserved chunk of the real-mode

  57 * data at 0x1e4 (defined as a scratch field) are used as the stack

  58 * for this calculation. Only 4 bytes are needed.

  59 */

  60        leal    (BP_scratch+4)(%esi), %esp

  61        call    1f

 

4.1.1 内核解压缩的前期工作

35行,第一条进入32位保护模式后的指令,清除方向标志指令。随后根据hdrloadflags的第7位是否被设置,我们看到根本没有设置,所以经过44~49行设置各个段寄存器。注意40行有一个%esi寄存器,我们在protected_mode_jump汇编代码中看到了,它存放着刚才c语言代码中设置好了的boot_params变量的地址。这个地址在传入protected_mode_jump之后变成了32位:(u32)&boot_params + (ds() << 4)。这个东西就是所谓0号页面的内容。

 

再一个就是BP_loadflags,后面会看到很多BP开头的常量。这些常量都是编译的时候设置的某些数据结构中一些字段地址偏移,大家可以去看看arch/x86/kernel/asm-offsets_32.c的最后几行,BP开头的常量就是boot_params结构的某字段的偏移。BP_loadflags就是hdr.loadflags相对于boot_params结构首部的偏移,于是BP_loadflags+%esi就是loadflags的地址。

 

再看到60行,BP_scratchscratch字段相对于boot_params结构首部的偏移,其实就是让栈顶指向(BP_scratch+4)(%esi)地址,根据注释来讲这是一个“刮刮卡”区域。这个“刮刮卡”是用来干什么的呢?前面main.c中没有对它赋值啊。原来,它是用来计算当期内核映像位置的。60lea指令得到boot_params.scratch32位物理地址,存放到esp寄存器中。虽然前面说了的那个Xgrub加载linuz的时候把这个值设置成0x90000,但为了对所有boot loader兼容,所有代码在这里做了一个通用化处理,现场计算这个值。但请注意,这里并不是0x90000,而是boot_params.scratch32位物理地址,这样可以把前面已经执行过的代码略过,减少待会儿的拷贝量。然后到62行:

 

  621:      popl    %ebp

  63        subl    $1b, %ebp

  64

  65/*

  66 * %ebp contains the address we are loaded at by the boot loader and %ebx

  67 * contains the address where we should move the kernel image temporarily

  68 * for safe in-place decompression.

  69 */

  70

  71#ifdef CONFIG_RELOCATABLE

  72        movl    %ebp, %ebx

  73        movl    BP_kernel_alignment(%esi), %eax

  74        decl    %eax

  75        addl    %eax, %ebx

  76        notl    %eax

  77        andl    %eax, %ebx

  78#else

  79        movl    $LOAD_PHYSICAL_ADDR, %ebx

  80#endif

  81

  82        /* Target address to relocate to for decompression */

  83        addl    $z_extract_offset, %ebx

  84

  85        /* Set up the stack */

  86        leal    boot_stack_end(%ebx), %esp

  87

  88        /* Zero EFLAGS */

  89        pushl   $0

  90        popfl

 

62行出栈,于是ebp寄存器就存放boot_params.scratch开始的32位的地址,随后再减4,把scratch再略过。再来,由于我们在顶层.config文件中看见CONFIG_RELOCATABLE=y,所以进入72行,经过7277行的处理,ebx就指向了BP_kernel_alignment偏移的位置,按照注释讲是将要存放内核映像位置的32位首地址。因为BP_kernel_alignment正是boot_params.hdr.kernel_alignment,在我们的header.S的第200行把它赋值成了CONFIG_PHYSICAL_ALIGN,查看我们的.config文件,这个值是0x1000000。现在保护模式打开了,这个32位地址是在1MB以后的。所有后面的拷贝主要是把内核映像其余部分拷贝到1MB以后。

 

随后,对ebx进行调整,增加一个z_extract_offset的值,这个值我不太清楚是怎样来的,好像是编译的时候产生的。然后再把ebx偏移boot_stack_end的地址赋值给esp,这样就可以使用栈了。

 

  92/*

  93 * Copy the compressed kernel to the end of our buffer

  94 * where decompression in place becomes safe.

  95 */

  96        pushl   %esi

  97        leal    (_bss-4)(%ebp), %esi

  98        leal    (_bss-4)(%ebx), %edi

  99        movl    $(_bss - startup_32), %ecx

 100        shrl    $2, %ecx

 101        std

 102        rep     movsl

 103        cld

 104        popl    %esi

 

然后进入96行代码,先把boot_params首地址压栈保存起来,然后将内核映像从ebp偏移_bss-4开始的内容拷贝到ebx偏移_bss-4开始对应的内存单元中,拷贝的长度是_bss- startup_32。注意,_bss 是待会看到的解压缩程序的BSS段,_bss - startup_32涵盖了linuzstartup_32起整个以后的代码长度,包括整个待解压内核,然后再右移2位,即除以4,这样102rep的时候按4个字节32位一组地进行拷贝,方便快捷。

 

注意第101行,设置了方向标志置位,所有rep的时候是由高地址向低地址进行,从_bss到现在我们正在进行的startup_32

 

做这么一个拷贝的目的是把内核映像拷贝到0x1000000以后的内存单元中,进入保护模式后,下一步主要是解压缩内核映像,而这样就可以很安全的执行解压缩的c代码了。

 

105

 106/*

 107 * Jump to the relocated address.

 108 */

 109        leal    relocated(%ebx), %eax

 110        jmp     *%eax

 111ENDPROC(startup_32)

 

拷贝完成后,加载存在于0x1000000以后的内存单元中新的relocated位置。所用的是%ebx对应的地址,而不是直接jmp relocated。所以,看到114行的relocated,执行到此时此刻,已经离开了刚才grub加载linuz的位置了,来到了新的保护模式位置。

 

113        .text

 114relocated:

 115

 116/*

 117 * Clear BSS (stack is currently empty)

 118 */

 119        xorl    %eax, %eax

 120        leal    _bss(%ebx), %edi

 121        leal    _ebss(%ebx), %ecx

 122        subl    %edi, %ecx

 123        shrl    $2, %ecx

 124        rep     stosl

 125

 

relocated一来首先清洗BSS段,使用的rep指令,很简单因为刚才没有拷贝BSS段,而在这里必须建立一个新的,继续走:

 

126/*

 127 * Do the decompression, and jump to the new kernel..

 128 */

 129        leal    z_extract_offset_negative(%ebx), %ebp

 130                                /* push arguments for decompress_kernel: */

 131        pushl   %ebp            /* output address */

 132        pushl   $z_input_len    /* input_len */

 133        leal    input_data(%ebx), %eax

 134        pushl   %eax            /* input_data */

 135        leal    boot_heap(%ebx), %eax

 136        pushl   %eax            /* heap area */

 137        pushl   %esi            /* real mode pointer */

 138        call    decompress_kernel

 139        addl    $20, %esp

 140

 

这段代码全是pushl,即压栈操作,为将进行的解压缩程序decompress_kernel函数准备参数,我们将在下一节予以详细分析。

 

你可能感兴趣的:(数据结构,buffer,input,output,alignment,X86)