系统启动(7)main.c

2434阅读 0评论2012-07-16 qtdszws
分类:LINUX

main->copy_boot_params

    /* First, copy the boot header into the "zeropage" */
    copy_boot_params();

复制启动参数到hdr中
static void copy_boot_params(void)
{
    struct old_cmdline {
        u16 cl_magic;
        u16 cl_offset;
    };
    const struct old_cmdline * const oldcmd =
        (const struct old_cmdline *)OLD_CL_ADDRESS;// 0x020

    BUILD_BUG_ON(sizeof boot_params != 4096);
    memcpy(&boot_params.hdr, &hdr, sizeof hdr);//hdr在header.S中定义

    if (!boot_params.hdr.cmd_line_ptr &&//cmd_line_ptr == 0x99000
        oldcmd->cl_magic == OLD_CL_MAGIC) {
        /* Old-style command line protocol. */
        u16 cmdline_seg;

        /* Figure out if the command line falls in the region
           of memory that an old kernel would have copied up
           to 0x90000... */
        if (oldcmd->cl_offset < boot_params.hdr.setup_move_size)
            cmdline_seg = ds();
        else
            cmdline_seg = 0x9000;

        boot_params.hdr.cmd_line_ptr =
            (cmdline_seg << 4) + oldcmd->cl_offset;
    }
}

有用的就两行
    BUILD_BUG_ON(sizeof boot_params != 4096);
    memcpy(&boot_params.hdr, &hdr, sizeof hdr);//hdr在header.S中定义

BUILD_BUG_ON是一个宏
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
如果条件为真则引起一个编译时错误。
因为(void)sizeof(char[-1])将触发 error: size of unnamed array is negative编译错误
(void)的作用是避免触发warning: statement with no effect [-Wunused-value]编译警告

hdr的声明在boot.h中
extern struct setup_header hdr;
定义在header.S中


接下来调用init_heap
main->init_heap
static void init_heap(void)
{
    char *stack_end;
//header.S中movw    $GRUB_LINUX_SETUP_STACK, %sp #0x9000,一直到后面的0x9100处是command line

    if (boot_params.hdr.loadflags & CAN_USE_HEAP) {//条件为true,参考grub
        asm("leal %P1(%%esp),%0"
            : "=r" (stack_end) : "i" (-STACK_SIZE));// 512栈大小
// leal -512(%esp),%eax           
//没有P,则会生成leal $-512(%esp),%eax,出错
//stack_end指向栈底
        heap_end = (char *)
            ((size_t)boot_params.hdr.heap_end_ptr + 0x200);// heap_end_ptr=0x9000-0x200,见grub
        if (heap_end > stack_end)//超过栈底,
            heap_end = stack_end;
    } else {
        /* Boot protocol 2.00 only, no heap available */
        puts("WARNING: Ancient bootloader, some functionality "
             "may be limited!\n");
    }
}

到此,可以归纳出实模式下的内存组织
0x9000:x00000----------------------
              bootsect.S
0x9000:0x0200----------------------_start
              setup.S

                             
      -----------------------------__bss_start

             未初始化区                            
       ------------------------------_end
              临时堆                    
       ------------------------------heap_end
            临时栈(512B)
0x9000:0x9000---------------------- 栈底
             cmdline(256B)
0x9000:0x9100______________________


main->detect_memory->detect_memory_e820
使用系统中断获得系统内存配置情况

#define SMAP    0x534d4150    /* ASCII "SMAP" */

static int detect_memory_e820(void)
{
    int count = 0;
    struct biosregs ireg, oreg;
    struct e820entry *desc = boot_params.e820_map;
    static struct e820entry buf; /* static so it is zeroed */
/*
Input: 
        EAX     功能代码    0XE820 
        EBX     继续信息    包含获取下一块物理内存的继续值,这一值是由上一次调用返回的,如果是第一次调用,则为0 
        ES:DI   缓冲区指针   指向 BIOS 要填写的地址描述符结构体 
        ECX     缓冲区大小   传入 BIOS 结构体字节数,BIOS 和调用者支持最小为 20 字节。 
        EDX     标识符     'SMAP' -  核实 BIOS 正确版本的标识 
     
    Output: 
        CF      进位标志    无进位标志表明没有错误 
        EAX     标识符     'SMAP' - 核实 BIOS 正确版本的标识 
        ES:DI   缓冲区指针   返回地址描述符缓冲区指针,与输入相同 
        ECX     缓冲区大小   传入 BIOS 结构体字节数,与输入相同 
        EBX     继续信息    包含得到下一地址描述符的继续值。为了迭代调用E820 获得下一地址描述符,调用者必须传入 
                            不经改变的继续值。返回值为 0 表示这是最后一个地址描述符。 
*/
    initregs(&ireg);
    ireg.ax  = 0xe820;
    ireg.cx  = sizeof buf;
    ireg.edx = SMAP;
    ireg.di  = (size_t)&buf;

    /*
     * Note: at least one BIOS is known which assumes that the
     * buffer pointed to by one e820 call is the same one as
     * the previous call, and only changes modified fields.  Therefore,
     * we use a temporary buffer and copy the results entry by entry.
     *
     * This routine deliberately does not try to account for
     * ACPI 3+ extended attributes.  This is because there are
     * BIOSes in the field which report zero for the valid bit for
     * all ranges, and we don't currently make any use of the
     * other attribute bits.  Revisit this if we see the extended
     * attribute bits deployed in a meaningful way in the future.
     */

    do {
        intcall(0x15, &ireg, &oreg);//在bioscall.S中
        ireg.ebx = oreg.ebx; /* for next iteration...下次循环需要这个值 */

        /* BIOSes which terminate the chain with CF = 1 as opposed对照
           to %ebx = 0 don't always report the SMAP signature on
           the final, failing, probe. */
        if (oreg.eflags & X86_EFLAGS_CF)//出错
            break;

        /* Some BIOSes stop returning SMAP in the middle of
           the search loop.  We don't know exactly how the BIOS
           screwed up the map at that point, we might have a
           partial map, the full map, or complete garbage, so
           just return failure. */
        if (oreg.eax != SMAP) {
            count = 0;
            break;
        }

        *desc++ = buf;//复制到boot_params
        count++;
    } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));

    return boot_params.e820_entries = count;//赋值
}


main的最后一步是调用go_to_protected_mode,进入保护模式,不再返回
/*
 * Actual invocation sequence
 */
void go_to_protected_mode(void)
{
    /* Hook before leaving real mode, also disables interrupts */
    realmode_switch_hook();

    /* Enable the A20 gate */
    if (enable_a20()) {
        puts("A20 gate not responding, unable to boot...\n");
        die();
    }

    /* Reset coprocessor (IGNNE#) */
    reset_coprocessor();

    /* Mask all interrupts in the PIC */
    mask_all_interrupts();

    /* Actual transition to protected mode... */
    setup_idt();
    setup_gdt();
    //在pmjump.S中
    protected_mode_jump(boot_params.hdr.code32_start,
                (u32)&boot_params + (ds() << 4));
}

setup_idt,中断描述符表指向空描述符
/*
 * Set up the IDT
 */
static void setup_idt(void)
{
    static const struct gdt_ptr null_idt = {0, 0};
    asm volatile("lidtl %0" : : "m" (null_idt));
}

static void setup_gdt(void)
{
    /* There are machines which are known to not boot with the GDT
       being 8-byte unaligned.  Intel recommends 16 byte alignment. */
    static const u64 boot_gdt[] //__attribute__((aligned(16)))
    = {
        /* CS: code, read/execute, 4 GB, base 0 */
        [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
        /* DS: data, read/write, 4 GB, base 0 */
        [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
        /* TSS: 32-bit tss, 104 bytes, base 4096 */
        /* We only have a TSS here to keep Intel VT happy;
           we don't actually use it for anything. */
        [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
    };
    /* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
       of the gdt_ptr contents.  Thus, make it static so it will
       stay in memory, at least long enough that we switch to the
       proper kernel GDT. */
    static struct gdt_ptr gdt;

    gdt.len = sizeof(boot_gdt)-1;
    gdt.ptr = (u32)&boot_gdt + (ds() << 4);

    asm volatile("lgdtl %0" : : "m" (gdt));
}

临时GDT表中有四个描述符,NULL,BOOT_CS,BOOT_DS,BOOT_TSS

最后执行protected_mode_jump
    //在pmjump.S中
    protected_mode_jump(boot_params.hdr.code32_start,
                (u32)&boot_params + (ds() << 4));

code32_start值为0x100000,后一个表达式计算boot_params的线性地址

/* ----------------------------------------------------------------------- *
 *
 *   Copyright (C) 1991, 1992 Linus Torvalds
 *   Copyright 2007 rPath, Inc. - All Rights Reserved
 *
 *   This file is part of the Linux kernel, and is made available under
 *   the terms of the GNU General Public License version 2.
 *
 * ----------------------------------------------------------------------- */

/*
 * The actual transition into protected mode
 */

#include
#include
#include
#include

    .text
    .code16 #现在还是16位模式

/*
 * void protected_mode_jump(u32 entrypoint, u32 bootparams);
 */
GLOBAL(protected_mode_jump)
    movl    %edx, %esi        # Pointer to boot_params table

    xorl    %ebx, %ebx
    movw    %cs, %bx
    shll    $4, %ebx #0x90000
    addl    %ebx, 2f#计算in_pm32的线性地址
    jmp    1f            # Short jump to serialize on 386/486
1:

    movw    $__BOOT_DS, %cx
    movw    $__BOOT_TSS, %di

    movl    %cr0, %edx
    orb    $X86_CR0_PE, %dl    # Protected mode 置保护位
    movl    %edx, %cr0

    # Transition to 32-bit mode 进入32位保护模式
    .byte    0x66, 0xea        # ljmpl opcode
2:    .long    in_pm32            # offset
    .word    __BOOT_CS        # segment
ENDPROC(protected_mode_jump)

    .code32 #现在是32位模式
    .section ".text32","ax"
GLOBAL(in_pm32)
    # Set up data segments for flat 32-bit mode
    movl    %ecx, %ds #都指向BOOT_DS
    movl    %ecx, %es
    movl    %ecx, %fs
    movl    %ecx, %gs
    movl    %ecx, %ss
    # The 32-bit code sets up its own stack, but this way we do have
    # a valid stack if some debugging hack wants to use it.
    addl    %ebx, %esp #调整esp,高位怎么办? 保证是0? header.S中    movzwl    %dx, %esp    # Clear upper half of %esp 0x9000

    # Set up TR to make Intel VT happy
    ltr    %di #记载TR

    # Clear registers to allow for future extensions to the
    # 32-bit boot protocol
    #eax中存放code32_start
    #esi存放boot_params
    xorl    %ecx, %ecx #清空寄存器
    xorl    %edx, %edx
    xorl    %ebx, %ebx
    xorl    %ebp, %ebp
    xorl    %edi, %edi

    # Set up LDTR to make Intel VT happy
    lldt    %cx #ldt执行NULL描述符

    jmpl    *%eax            # Jump to the 32-bit entrypoint 跳到0x100000
ENDPROC(in_pm32)

main的任务结束
上一篇:系统启动(6)main.c
下一篇:系统启动(9)构建系统之三次连接生成vmlinux