Linux内核精析
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 实模式setup阶段

setup用于体系结构相关的硬件的初始化工作。在32位x86平台,setup的入口点是arch\x86\boot\header.s中的_start,Boot Loader把setup代码加载到0x07c00上的某处后,将DS寄存器设置为该处的地址,之后再跳转到DS:200把控制器交由_start,最后跳转到start_of_setup。相关代码在arch\x86\boot\header.s中的定义如下:

.section ".inittext", "ax"
start_of_setup:
#ifdef SAFE_RESET_DISK_CONTROLLER
    # 复位磁盘控制器
    movw    $0x0000, %ax       # 复位磁盘控制器
    movb    $0x80, %dl         # 所有的磁盘
    int $0x13
# endif
# Force %es = %ds
    movw    %ds, %ax
    movw    %ax, %es
    cld
    # 如果%ss无效,则重新计算该堆栈指针
    movw    %ss, %dx
    cmpw    %ax, %dx            # %ds == %ss?
    movw    %sp, %dx
    je  2f
    # 如果%ss无效,则建立新堆栈
    movw    $_end, %dx
    testb $CAN_USE_HEAP, loadflags
    jz  1f
    movw    heap_end_ptr, %dx
1:  addw    $STACK_SIZE, %dx
    jnc 2f
    xorw    %dx, %dx            # 防止死循环
2:  # %dx应该指向堆栈空间的栈顶
    andw    $~3, %dx            # 双字对齐
    jnz 3f
    movw    $0xfffc, %dx       # 确保%dx非零
3:  movw    %ax, %ss
    movzwl  %dx, %esp           # 清除%esp的上半部
    sti                         # 获取工作堆栈
    pushw   %ds
    pushw   $6f
    lretw
6:
    cmpl $0x5a5aaa55, setup_sig
    jne setup_bad
    # 清空bss
    movw    $_bss_start, %di
    movw    $_end+3, %cx
    xorl %eax, %eax
    subw    %di, %cx
    shrw $2, %cx
    rep; stosl
    # 跳转到C代码
    call main

在该代码中,Boot Loader跳转到该段代码时,DS已经设置为setup加载的基地址,最后跳转到C代码的main函数。main函数在arch\x86\boot\main.c文件中的定义如下:

void main(void)
{
    /* 首先把第一个扇区的参数复制到boot_params中 */
    copy_boot_params();
    /* 堆栈结束检查 */
    if (boot_params.hdr.loadflags & CAN_USE_HEAP) {
                heap_end = (char *)(boot_params.hdr.heap_end_ptr
                            +0x200-STACK_SIZE);
                                                        } else {
                puts("WARNING: Ancient bootloader, some functionality "
                      "may be limited!\n");
                                                        }
                                                        /* 确保有适合CPU的支持 */
                                                        if (validate_cpu()) {
                puts("Unable to boot-please use a kernel appropriate"
                      "for your CPU.\n");
                die();
                                                        }
                                                        /* 通知BIOS将要运行的CPU模式 */
                                                        set_bios_mode();
                                                        /* 检测内存的布局 */
                                                        detect_memory();
                                                        /* 设置键盘重复率 */
                                                        keyboard_set_repeat();
                                                        /* 设置视频模式 */
                                                        set_video();
                                                        /* 查询MCA信息 */
                                                        query_mca();
                                                        #ifdef CONFIG_X86_VOYAGER
                query_voyager();
                                                        #endif
                                                        query_ist();
                                                        /* 查询APM信息 */
                                                        #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
                query_apm_bios();
                                                        #endif
                                                        /* 查询EDD信息 */
                                                        #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
                query_edd();
                                                        #endif
                                                        /* 进入保护模式 */
                                                        go_to_protected_mode();
                                                    }

该函数首先通过copy_boot_params()函数把位于第一个扇区的参数复制到boot_params中,该变量位于setup的数据段,之后设置系统硬件的相关参数,最后调用go_to_protected_mode()函数进入保护模式。对于copy_boot_params()函数的定义如下:

[main-> copy_boot_params]
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;
    BUILD_BUG_ON(sizeof boot_params != 4096);
    /* 把hdr结构中的参数复制到boot_params.hdr中 */
    memcpy(&boot_params.hdr, &hdr, sizeof hdr);
    …
}

该函数的主要操作是调用memcpy(&boot_params.hdr, &hdr, sizeof hdr)把hdr结构 中 的 参 数 复 制 到boot_params.hdr中。回 到main() 函 数 最 后 调 用 的go_to_protected_mode()函数在文件arch\x86\boot\Pm.c中的定义如下:

[main->go_to_protected_mode]
void go_to_protected_mode(void)
{
    /* 在离开实模式前,需要禁用中断 */
    realmode_switch_hook();
    /* 将内核/setup移到其最后的区域 */
    move_kernel_around();
    /* 开启A20门 */
    if (enable_a20()) {
        puts("A20 gate not responding, unable to boot...\n");
        die();
    }
    /* 复位处理器 */
    reset_coprocessor();
    /* 屏蔽PIC的所有中断 */
    mask_all_interrupts();
    /* 开始进入保护模式 */
    setup_idt();
    setup_gdt();
    //跳转至code32_start执行
    protected_mode_jump(boot_params.hdr.code32_start,
                (u32)&boot_params + (ds() << 4));
  }

该函数首先调用realmode_switch_hook()函数,之后进行复位处理器及屏蔽PIC中断等操作,最后调用protected_mode_jump()开启CR0寄存器的PE位进入保护模式,并跳转到code32_start执行。参数boot_params.hdr.code32_start在header.s中的定义如下:

…
code32_start:              # 32位代码的起始地址
#ifndef_BIG_KERNEL_
    .long   0x1000          # 0x1000 = zImage默认地址
#else
    .long   0x100000        # 0x100000 =大内核的默认地址(bzImage)
#endif
…

从该段代码可以看出,程序将跳转到0x1000或0x100000处继续执行;参数(u32)&boot_params + (ds() << 4) 为boot_params的线性地址,由于此时仍为实模式,因此线性地址为段地址左移4 位(乘以16)再加上偏移。protected_mode_jump在arch\x86\boot \pmjump.S中的定义如下:

/*
* void protected_mode_jump(u32 entrypoint, u32 bootparams);
*/
protected_mode_jump:
    movl    %edx, %esi     # 指向boot_params表
    movl    %eax, 2f        # 2f处放置将要跳转的指令
    …
    movl    %cr0, %edx
    orb $1, %dl             # 保护模式(PE)位
    movl    %edx, %cr0
    jmp 1f
1:
    movw    %cx, %ds
    movw    %cx, %es
    movw    %cx, %fs
    movw    %cx, %gs
    movw    %cx, %ss
    # 跳转至32位入口
    .byte 0x66, 0xea       # 跳转指令操作码
2:  .long   0               # 偏移
    .word   _BOOT_CS       # 段
    .size protected_mode_jump, .-protected_mode_jump

由于arch\x86\kernel\head_32.S是编译到内核vmlinux中的,然后vmlinux经objcopy处理再经过压缩,最后被当做数据段的内容和arch\x86\boot\compressed\head_32.0链接,因此执行arch\x86\boot\compressed\head_32.S中的startup_32,将数据段中的压缩内核解压后,跳转至arch\x86\kernel\head_32.S的startup_32。