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