...
!这里的代码都是做一些获取并保存机器系统数据的操作,这里略去了,具体可看源代码。
...
! Get hd0 data
mov ax,#0x0000
mov ds,ax
!4*0x41 = 0x0000:0x104,这里是BIOS的中断向量表的位置里头存放着硬盘参数阵列表的首地址0xf000:0xe401。
!把ds:si赋值为0xf000:0xe401,从上图可知这个地址刚好是BIOS程序占地址段(0xFE000-0xFFFFF)
lds si,[4*0x41]
mov ax,#INITSEG
mov es,ax
mov di,#0x0080
mov cx,#0x10
rep
movsb
!BIOS中断向量表和BIOS数据区被完全覆盖,此后,在新的中断服务体系建立之前,
!操作系统将不再具备响应并处理中断的能力。
! 下面是进入保护模式前的准备工作,这个时候BIOS占领的区域(0x00000-0x10000中被BIOS占用的地方)
! 也没什么作用了,于是将system模块向内存低端移动了64K(即system模块的起始位置从0x10000变成了0x00000)
! first we move the system to it is rightful place
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
! 完成system模块的搬移之后,就开始设置中断描述符表和全局描述符表
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
! that was painless, now we enable A20
! 下面开启A20
call empty_8042
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042
mov al,#0xDF ! A20 on
out #0x60,al
call empty_8042A20地址线问题!下面的代码是对中断重新进行编程的代码
! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right afterthe intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up withthe original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at0x08-0x0f,
! which is used forthe internal hardware interrupts as well. We just
! have to reprogram the8259's, anditisn't fun.
mov al,#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
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al
.word0x00eb,0x00eb
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
.word0x00eb,0x00eb
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word0x00eb,0x00eb
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word0x00eb,0x00eb
mov al,#0x01 ! 8086 mode for both
out #0x21,al
.word0x00eb,0x00eb
out #0xA1,al
.word0x00eb,0x00eb
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word0x00eb,0x00eb
out #0xA1,al8259A编程
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except forthe initial loading :-).
! The BIOS-routine wants lots of unnecessary data, andit's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's thetimeto actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in32-bit protected mode.
!下面两行开启保护模式
mov ax,#0x0001 ! protected mode (PE) bit
lmsw ax ! This isit!
!在保护模式下,这条语句的8不能单纯的理解成数字8,而应理解成段选择子,有的书上也管它叫段选择符(占2字节)
!也就是说在实模式下段基址寄存器的作用已经发生了改变,用于选择描述符表和描述符表项以及所要求的特权级
!执行完后就直接跳转到system模块中去执行head.S的代码了。
jmpi 0,8 ! jmp offset0of segment 8 (cs) 段选择子与段描述符结构! This routine checks thatthe keyboard command queue is empty
! No timeoutis used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.
empty_8042:
.word0x00eb,0x00ebin al,#0x64 ! 8042 status port
test al,#2 ! is input buffer full?
jnz empty_8042 ! yes - loop
ret
gdt:
.word0,0,0,0 ! dummy为什么全局描述符表GDT的第0项总是一个空描述符,而局部描述符表却不是这样?!接下来继续定义了两个段描述符,分别用来描述代码段和数据段,
!每个段描述符占据8字节的内存空间,有关段描述符的具体介绍可在赵炯先生的书中找到。
.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实际上只是一张空表,因目前处于关中断状态不需要调用中断服务程序,反应了一种够用就行的思想。
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 !这里的512是因为setup模块是从0x90200开始运行的
.textendtext:.dataenddata:.bssendbss:
以上添加的一些参考链接,其实在赵炯先生的书中也都有提及。
head.s
...
_pg_dir: !在这个地方放置该符号的目的是暗示着这里将会存放页目录表
......
!这段代码是用来测试A20是否已经成功打开,不断的把%eax中不同的值送入0x000000地址处
!然后与0x100000地址处的值进行比较,如果相等就陷入死循环,表示A20未选通,内核无法使用1MB以上的内存。
xorl %eax,%eax
1: incl %eax # check that A20 really IS enabled
movl %eax,0x000000# loop forever if it isn't
cmpl %eax,0x100000
je 1b
...fninit fstsw指令!这两条是80x87协处理器指令
fninit
fstsw %ax
...
!这里是一个比较关键的地方,设计者用了一种模拟call调用返回的方法,提前将main地址压栈了,
!这里的_main是编译程序对main的内部表示方法,事后在执行完setup_paging后,用ret指令将会直接
!跳转到main函数执行,也就是正式进入了内核,在main.c中将会进行内核初始化的操作。
after_page_tables:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $_main
jmp setup_paging
L6:
jmp L6 # main should never return here, but
# just incase, we know what happens.
...
!接下来的过程便是设置页目录项和页表项,然后开启分页机制,最后跳转到main函数执行内核的初识化工作。
.align2
setup_paging:
movl $1024*5,%ecx/* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi/* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,_pg_dir /* set present bit/user r/w */
movl $pg1+7,_pg_dir+4/* --------- " " --------- */
movl $pg2+7,_pg_dir+8/* --------- " " --------- */
movl $pg3+7,_pg_dir+12/* --------- " " --------- */
movl $pg3+4092,%edi
movl $0xfff007,%eax/* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax/* pg_dir is at 0x0000 */
movl %eax,%cr3/* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0/* set paging (PG) bit */
ret /* this also flushes prefetch-queue */
...
要理解这段代码需要了解一点一些GNU汇编和x86/x86_64 CPU控制寄存器(Control Registers)的知识。