这段代码就是网上很流行的自己动手写内核系列中的第二课,保护模式的程序代码。主要功能就是在保护模式下打印"Hello world!"。代码中本来是有一些注释的,但是对于我们这样的新手来说,那样的注释实在是太简略了,对于理解程序还是有很大难度的,因此我就花了3、4天时间,把这段代码重新研究了一遍,并给代码进行了更为详细的注释。之所以选择这段代码进行注释,是因为在我看来,这段代码是系统启动过程中最有技术含量的一段代码(呵呵,貌似抄袭了赵炯博士的话。。。)。
PS:因为本人也是个新手,而且不是计算机科班出身,因此水平不是很高,也许注释中有一些不妥,敬请各位指正!
1、bootsect.s代码
#bootsect.s程序会被放在内存的0x7c00处,BIOS在完成自检之后会调用这个地址上的代码开始执行。这就是boot代码的开始过程...
#bootsect.s程序大体上做了五件事情:1、把内核代码移动到0x10000地址处,不移动第一个扇区数据 2、把内核代码的前512字节移动到0x00地址处
#3、移动GDT表项到GDT_ADDR处4、开启A20地址线 5、设置gdtr,cr0,长跳转进入保护模式
.text
.globl start
.include "kernel.inc"
.code16
start:
jmp code
gdt:
#设置全局中断表的前五项值,第0项系统保留不用,第二项为cs寄存器的项,第三项是ds寄存器的项,四五项保留后用
.quad 0x0000000000000000 # null descriptor
.quad 0x00cf9a000000ffff # cs
.quad 0x00cf92000000ffff # ds
.quad 0x0000000000000000 # reserved for further use
.quad 0x0000000000000000 # reserved for further use
#48位lgdt寄存器要存放的值,内容包括GDT寄存器的项目数目以及GDT表的首地址。
gdt_48:
.word .-gdt-1#当前地址减去gdt标志的地址再减1
.long GDT_ADDR
#程序代码的入口地址
code:
#复制内核代码到0x10000处,注意这里的代码是从软盘中复制过来的,而不是从内存中某个位置复制而来的,因为此时内存中除了0x7c00移动过来的1个扇区的引导代码(本程序代码)之外,其余地方还没有有效数据。
xorw %ax, %ax
movw %ax, %ds # ds = 0x0000内核代码的源地址,移动内核代码的前512字节时使用
movw %ax, %ss # stack segment = 0x0000
movw $0x1000,%sp # arbitrary value 后边要用到函数调用,需要栈结构,再此进行预先设置
# used before pmode
## read rest of kernel to 0x10000
movw $0x1000,%ax
movw %ax, %es
xorw %bx, %bx #移动的目标地址指定为es:bx
movw $KERNEL_SECT,%cx #总共要移动的扇区数
movw $1, %si #移动的扇区号,软盘中经过编译链接,0扇区存放的是引导程序(本程序,已经被BIOS复制到了0x7c00内存处),不需要再复制了,因此设要复制扇区号的初值为1,忽略0引导扇区从,1引导扇区开始复制..后边的代码都是内核代码
rd_kern:
call read_sect #调用读扇区函数移动一个扇区的数据,移动的参数由上边的设置给定(es:bs,si)
addw $512, %bx #每次移动一个扇区,因此目标地址每次移动512字节
incw %si #扇区号加1
loop rd_kern #循环复制,直到cx减为0
cli #进入保护模式之前关闭中断
#移动内核代码的前512字节(不包含第一扇区的启动代码)到0x0000处,这512字节是load.s代码编译的代码
## move first 512 bytes of kernel to 0x0000
## it will move rest of kernel to 0x0200,
## that is, next to this sector
cld #设置复制的方向
movw $0x1000,%ax
movw %ax, %ds #要复制的源地址段寄存器
movw $0x0000,%ax
movw %ax, %es #目标地址段寄存器
xorw %si, %si #源地址偏移地址0
xorw %di, %di #目标地址偏移地址0
movw $512>>2,%cx #要复制的次数,每次4字节,次数就是512/4
rep
movsl
#把GDT表项放到GDT_ADDR地址上去。
#这里涉及一个节与子节地址的问题,bootsect.s这段代码是在.text节中的(文件的开头处伪指令指定),该段代码是被BIOS复制到0x7c00地址处执行的,因此节地址>为0x7c00,程序中标号是此节中的子节,gdt就是一个子节,从bootsect.map文件中可以找到gdt的定义值为0x00002,但是得到这个值是假设节.text的地址为0的,>即为相对地址,因此gdt值在这里代表的地址是一个绝对地址,计算方法为:节地址+相对地址=0x7c00+0x002 = 0x7c02
xorw %ax, %ax
movw %ax, %ds #设置源段寄存器地址为0
## move gdt
movw $GDT_ADDR>>4,%ax
movw %ax, %es #GDT表的段地址实际地址除以2^4
movw $gdt, %si #源偏移地值
xorw %di, %di #GDT表的偏移地址
movw $GDT_SIZE>>2,%cx
rep
movsl
#开启A20地址线
enable_a20:
## The Undocumented PC
inb $0x64, %al #把0x64端口(8042键盘控制器)的状态写入al中
testb $0x2, %al #检测0x64端口的位2的状态,检测缓冲器是否是空的
jnz enable_a20 #如果缓冲器空,则ZF=1则往下继续执行,否则跳转回去继续等待
movb $0xdf, %al
outb %al, $0x64 #此时缓冲器已空,可以直接给8042写入数据打开A20地址线
lgdt gdt_48 #向LGDT寄存器中写入值
## enter pmode
movl %cr0, %eax
orl $0x1,%eax
movl %eax, %cr0 #设置CR0的0位,进入保护模式
ljmp $CODE_SEL, $0x0 #使用长跳转指令,跳转到段选择子CODE_SEL所指定的段,偏移地址0x00处,同时段寄存器CS也被设置成CODE_SEL的值。
#注意由于前一条语句已经置位CR0的bit0,进入了保护模式,下边真正开始执行保护模式程序...
##########################################到此,bootsect.s程序执行结束#####################################
##################################################子函数模块###############################################
#复制n个扇区数据的子函数,主要是调用BIOS中断0x13,功能号为0x02
#参数说明:目标地址:es:bx 入口参数:AH=02H 功能号
# AL=扇区数
# CH=柱面
# CL=扇区
# DH=磁头
# DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘
# ES:BX=缓冲区的地址
# 出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号 01H 中的说明
#1.44MB软盘,扇区号S和磁头(扇面)A,磁道(柱面)B,扇区C之间的转换关系:
#A = (S/18)%2 B = (S/36) C = S%18 + 1 软盘每个磁道有18个扇区
read_sect:
#在子函数中要用到ax,bx,cx,dx寄存器,因此先把他们入栈,保存原来的数据
pushw %ax
pushw %cx
pushw %dx
pushw %bx #因为入栈之前bx中存的是要复制的目标地址,因此准备完成之后,需要先出栈bx,因此把bx放在栈顶
movw %si, %ax #要复制的扇区号
xorw %dx, %dx #dx清0
movw $18, %bx # 18 sectors per track
divw %bx #除数bx为16位,被除数应该为32位(dx:ax) (dx:ax)/bx 除法的结果是:ax存放除法的商,dx存放除法的余数
incw %dx #dx+1得到扇区数C
movb %dl, %cl #按照int 13h的要求,扇区数应该放到cl中
xorw %dx, %dx #dx清0,以做他用
movw $2, %bx
divw %bx #此时ax中的值为S/18的值,做除法之后,ax存放的是S/18/2=S/36,dx中存放的是(S/18)%2
movb %dl, %dh #按照int 13h的要求,磁头数放在dh中
xorb %dl, %dl #按照int 13h的要求,驱动器号放在dl中,dl=0表示为0号软盘
movb %al, %ch #按照int 13h的要求,磁道数放在ch中
popw %bx #把bx出栈,确定目标地址
rp_read:
movb $0x1, %al #要复制的扇区数目
movb $0x2, %ah #中断功能号
int $0x13 #调用BIOS中断,复制扇区数据
jc rp_read #判断CF是否为0,若为0说明复制成功,继续往下执行,否则说明复制出错,重新复制该扇区
#程序执行完成,依次把dx,cx,ax出栈
popw %dx
popw %cx
popw %ax
ret #函数返回指令
.org 0x1fe, 0x90 #0x1fe之前的空余空间给NOP指令
.word 0xaa55 #说明这是引导程序
##################################END#####################################
2、load.s代码
.text
.global pm_mode
.include "kernel.inc"
#.code32
.org 0 #告诉机器,代码从逻辑地址0开始执行(也是物理地址0)
pm_mode:
#把所有数据段以及堆栈段寄存器设为数据段选择子的值
movl $DATA_SEL, %eax
movw %ax, %ds
movw %ax, %es
movw %ax, %gs
movw %ax, %fs
movw %ax, %ss
movl $STACK_BOT, %esp #栈底指针
#复制内核的512字节之后的部分到0x200
cld #复制字符串的方向
#设置要复制的数据的源地址以及目标地址的段地址以及初始偏移地址
movl $0x10200, %eax
movl %eax, %esi #源地址的偏移地址
movl $0x200, %eax
movl %eax, %edi #目标地址的偏移地址
movl $KERNEL_SECT<<7,%ecx#要复制的数目,KERNEL_SECT的单位是扇区(512字节2^9),每次movsl指令每次复制4个字节,因此复制次数为KERNEL_SECT*2^9/4
rep #EP前缀用在MOVS,STOS,LODS指令前;每执行一次串指令,CX减1;直到CX=0,重复执行结束
movsl
movb $0x07, %al #显存控制字,表示颜色设置
movl $msg, %esi
movl $0xb8000, %edi #0xb8000是显存在内存中的映射地址
1:
cmp $0x0, (%esi)
je 1f
movsb #movsb是复制ds:si-->es:di,并且如果DF为0(CLD),则调用该指令后,SI,DI自增1,如果DF为1(STD),SI,DI自减1.
#这里的意思就是把msg字符串复制到显存所在内存地址,这样就可以通过控制台显示出来。
#显存中两个字节的内存控制一个字符的显示,低字节中存放字符内容,高字节中存放显示控制字,控制字体的颜色等信息。
# stosb #al-->ds:di,并且如果DF为0(CLD),则调用该指令后,DI自增1,如果DF为1(STD),DI自减1.
incw %di #不写入颜色控制字,直接把di加1,准备写下一个字符
jmp 1b
1: jmp 1b #复制结束之后,进入死循环等待
msg:
.string "Hello World!\0"
阅读(4448) | 评论(0) | 转发(0) |