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。