如上所述,第一和第二部分是实模式代码,这些代码来自于汇编程序arch/x86/boot/header.S和c程序arch/x86/boot/main.c。
再从加载开始说,如上所述,vmlinuz保护模式的代码加载到0x100000开始的位置。而实模式的代码,因为被加载的位置不要求是固定的,也就是上面文档中看到的:
Kernel setup | The kernel real-mode code.
Kernel boot sector
它们的位置是X,其不确定性是受Boot loader,也就是grub大小的影响。
所以,我们先来看header.S的代码,第46行:
46 .global bootsect_start
47 bootsect_start:
48
49 # Normalize the start address
50 ljmp $BOOTSEG, $start2
这是bootsect开始代码,也就是vmlinuz第一个512字节的源代码。
我们看到$BOOTSEG是在源代码的第28行定义的BOOTSEG=0x07C0。没错,这个ljmp的意思就是说的是跳转到07C0的偏移start2处。还记得我们BIOS一节说过,BIOS不管你是内核还是bootloader,总会一来把第一个块加载到内存然后执行0x07c0处的代码。那么如果从这里开始执行,就说明vmlinuz是被BIOS直接加载过来的,这是不允许的,因为现在linux都需要经过一个特定的bootloader,如前面大篇幅介绍的GRUB。这也就是前面说的bootsect有点特殊的地方,就是说它并没打算用来执行,是个死代码。所以万一它被作为bootsect由BIOS直接执行,那么就直接提示reboot。可以拿vmware实验一下:
dd if=/boot/vmlinuz-2.6.34.1 of=vm.img bs=512 count=1
然后用vm.img作为软盘启动。程序就会跳到$start2处:
52 start2:
53 movw %cs, %ax
54 movw %ax, %ds
55 movw %ax, %es
56 movw %ax, %ss
57 xorw %sp, %sp
58 sti
59 cld
60
61 movw $bugger_off_msg, %si
62
我们看到,start2首先将将ds,es,ss全设置为cs,其内容为0x07C0;然后将sp设置成0、开中断sti、cld清方向标志最后把下面bugger_off_msg 符号的偏移地址放在si寄存器中。继续走:
63 msg_loop:
64 lodsb
65 andb %al, %al
66 jz bs_die
67 movb $0xe, %ah
68 movw $7, %bx
69 int $0x10
70 jmp msg_loop
71
63行的msg_loop的lodsb指令把si指向的源串的内容逐步装入al中(另一个指令stosb是将al中数据装入di指向的地址中)。64行,当取完的时候,al=0,此时操作andb后方向为0,执行下句跳转语句到bs_die。当然,al不为0的时候,执行69行Video中断服务指令,其中AH = 0Eh,AL = 对应字符(8位),BL = Color (only in graphic mode)。
72 bs_die:
73 # Allow the user to press a key, then reboot
74 xorw %ax, %ax
75 int $0x16
76 int $0x19
77
78 # int 0x19 should never return. In case it does anyway,
79 # invoke the BIOS reset code...
80 ljmp $0xf000,$0xfff0
81
82 .section ".bsdata", "a"
83 bugger_off_msg:
84 .ascii "Direct booting from floppy is no longer supported./r/n"
85 .ascii "Please use a boot loader program instead./r/n"
86 .ascii "/n"
87 .ascii "Remove disk and press any key to reboot . . ./r/n"
88 .byte 0
89
90
91 # Kernel attributes; used by setup. This is part 1 of the
92 # header, from the old boot sector.
93
上面这些代码的作用就是打印消息并等待重启,就不多说了。在这里顺便说一下,grub的boot_func中的big_linux_boot里,描述了实际上grub的stage2将内核的bootsect和setup实模式代码载入到地址0x90000后。也就是说,这里就是对应Grub是0x90000的证据。由于我们setup.ld链接脚本里规定的setup.bin入口点是_start,那么grub加载vmlinuz后将跳过前512个字节,即0x200个字节的,直接跳转到地址0x90200处执行的。我们还是来关注真正的setup代码了,从偏移内核映像vmlinuz的0x0200开始。
那么grub怎么执行这条代码呢?grub会执行jmp_far(0x20, 0); 也就是跳过vmlinuz的0x200个字节,也就是跳到vmlinuz实模式代码_start执行了,因为这样就略过了前512字节的bootsect。这也就是一开始我们说bootsect.S消失了的秘密。
实际上,jmp_far就是grub中一条指令,ljmp。再看看第105行的注释,这就是ljmp跳到的位置,即_start,也就是进入“中世纪时代”。