INITSEG = 0x9000 ! we move boot here - out of the way SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). SETUPSEG = 0x9020 ! this is the current segment start: mov ax,#INITSEG mov ds,ax mov ah,#0x03 xor bh,bh int 0x10 ! mov [0],dx ! mov ah,#0x88 int 0x15 mov [2],ax ! mov ah,#0x0f int 0x10 mov [4],bx ! mov [6],ax ! mov ah,#0x12 mov bl,#0x10 int 0x10 mov [8],ax mov [10],bx mov [12],cx上面的代码主要对硬件进行检测,为了保证所有的段寄存器都指向正确的内存地址,代码起始位置首先重新设置一下段寄存器。由于bootsect已经不再有使用价值,所以可以重新覆盖用于保存检测到的一些硬件信息。首先利用int 0x10得到当前光标的信息,并将相应的信息存放到INITSEG段的起始位置。http://www.ctyme.com/intr/rb-0088.htm
int 0x15用于获取系统的扩展内存大小,也就是绝对地址超过1M的内存的大小。大小按照KB为单位保存在AX中,也就是扩展内存最大位64MB。http://www.ctyme.com/intr/rb-1529.htm
int 0x10用于返回当前显示模式,AX返回显示的显示的列数墓,BX则是当前显示的活动页。http://www.ctyme.com/intr/rb-0108.htm。接下来的int 0x10则用于返回显示内存,以后的显示打印可以通过这一部分内存进行直接的显示。
mov ax,#0x0000 mov ds,ax lds si,[4*0x41] mov ax,#INITSEG mov es,ax mov di,#0x0080 mov cx,#0x10 rep movsb mov ax,#0x0000 mov ds,ax lds si,[4*0x46] mov ax,#INITSEG mov es,ax mov di,#0x0090 mov cx,#0x10 rep movsb mov ax,#0x01500 mov dl,#0x81 int 0x13 jc no_disk1 cmp ah,#3 je is_disk1 no_disk1: mov ax,#INITSEG mov es,ax mov di,#0x0090 mov cx,#0x10 mov ax,#0x00 rep stosb is_disk1: cli ! no interrupts allowed ! mov ax,#0x0000 cld ! 'direction'=0, movs moves forward do_move: mov es,ax ! destination segment add ax,#0x1000 cmp ax,#0x9000 jz end_move mov ds,ax ! source segment sub di,di sub si,si mov cx,#0x8000 rep movsw jmp do_move end_move:代码开始首先测试是否存在硬盘,两个硬盘参数存放在中断向量0x41和0x46中。因为英特尔体系架构下总共可以有256个中断。其中系统保留使用的有0x0-0x31则用于保留给异常使用,而剩下的0x32-0x47则用于系统中的可编程中断,剩下的可以由用户任意分配。因此这里的0x41和0x46号中断向量存放着找到硬盘信息的地址应该是一个默认的惯例。不过这也是因为Linux中的MBR部分不规范所导致的,按照规范的可引导MBR中,应该存放BPB参数表,参数表中包含相应的磁盘信息。但是这样在安装的时候需要收集磁盘的相关信息。int 0x13用于检测磁盘的格式(这里的第二个磁盘是否是除软盘外的第二个磁盘),如果第二个盘不存在则会将它的磁盘格式表进行清零操作。在检测得到所有的硬件信息后,将system中复制到0x0000段位置处。
end_move: mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-) mov ds,ax lidt idt_48 ! load idt with 0,0 lgdt gdt_48 ! load gdt with whatever appropriate call empty_8042 mov al,#0xD1 ! command write out #0x64,al call empty_8042 mov al,#0xDF ! A20 on out #0x60,al call empty_8042 mov al,#0x11 ! initialization sequence out #0x20,al ! send it to 8259A-1 .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 out #0xA0,al ! and to 8259A-2 .word 0x00eb,0x00eb mov al,#0x20 ! start of hardware int's (0x20) out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 ! start of hardware int's 2 (0x28) out #0xA1,al .word 0x00eb,0x00eb mov al,#0x04 ! 8259-1 is master out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 ! 8259-2 is slave out #0xA1,al .word 0x00eb,0x00eb mov al,#0x01 ! 8086 mode for both out #0x21,al .word 0x00eb,0x00eb out #0xA1,al .word 0x00eb,0x00eb mov al,#0xFF ! mask off all interrupts for now out #0x21,al .word 0x00eb,0x00eb out #0xA1,al mov ax,#0x0001 ! protected mode (PE) bit lmsw ax ! This is it! jmpi 0,8 ! jmp offset 0 of segment 8 (cs) empty_8042: .word 0x00eb,0x00eb in al,#0x64 ! 8042 status port test al,#2 ! is input buffer full? jnz empty_8042 ! yes - loop ret gdt:对8042的设置可以防止系统内存的回卷,为了兼容以前的系统,在后续的英特尔架构下同样可以访问1M以及1M以上的内存。然而,这就不好区分是在实模式下访问1M以上内存还是在保护模式下访问1M以上内存。因此需要设置一个标志,防止系统在保护模式下访问1M以上内存出现回卷。这个标志就是设置键盘中的8042中控制寄存器的第二位为1。至于8042的操作就不具体介绍了,需要注意的是在进行这些处理时需要在关中断情况下进行处理。还有另外一个地方的0x00eb,起始是一个指令码。这个指令码表示向前跳转两个字节,主要用于同步。为了初始化8259中断控制器,总共发出了四个ICW命令控制字,除了ICW3之外用于区分8259的主从之分外,其他的都是一样的。最后将两个8259中的中断给屏蔽掉。然后跳转到32位代码处开始执行。在英特尔架构下CR0是一个控制寄存器,用于控制当前处理器的状态,比较重要的有两个位,一个是PE开启保护模式,另一个是PG开启页式内存管理。
gdt: .word 0,0,0,0 ! dummy .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00C0 ! granularity=4096, 386 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9200 ! data read/write .word 0x00C0 ! granularity=4096, 386 idt_48: .word 0 ! idt limit=0 .word 0,0 ! idt base=0L gdt_48: .word 0x800 ! gdt limit=2048, 256 GDT entries .word 512+gdt,0x9 ! gdt base = 0X9xxxx进入到下一个话题之前,首先需要对上面这些结构进行分析。需要注意的第一点是英特尔是小端模式,因此高位在内存的高位地址。而GDTR中包含48位数据,32位为基地址找到对应的全局段寄存器,而16位作为GDTR的上限;IDTR也是类似的。因此GDTR给定的基地址是0x90512+gdt(也就是上面定义的gdt表在内存中的位置)。由于此时中断被禁止所以是一个无用的数值。gdt中第一个表项被设置为全部为0,是英特尔默认这一项无用。
左图是每一个段的表项的每一个位的解释,整个结构体单元被划分的稀烂。重点看三个,基地址,上限以及DPL。gdt的第二项的基地址是0x0,而上限时0x07ff;dpl则为0。而这正好和之前的长跳转相符,因此下一步的system代码将会在段基地址位0x0,上限为0x07ff处运行,正好和复制时0x8000一致。另外,需要注意的一点是第三个gdt表项,这个表项在接下来的处理中有着隐含的含义。另外需要注意的是,在jmpi指令之前,只需要直接按照原来的存取方式进行读取就可以了。