上篇文章我们完成了一个简单的bootloader,与其说是bootloader,不如说是boot,本篇我们完成loader部分功能.loader部分是在boot部分基础上,通过到约定好的启动盘位置上读数据载入内存,达到loader的目的。到启动盘读数据是bios提供的功能调用.
1. 铺垫
(1)我们这次的程序分两个部分,一个部分是bootloader,boot和loader功能;一个是head程序,这个程序什么也不做,简单的几条指令,我们只是要加载它执行它而已.
(2)bootloader是as86+ld86的产物,语法遵从as86语法;head是gnu汇编器语法,使用gcc编译ld链接,是32位的程序.
(3)默认bootloader程序会被放在软盘的引导扇区,就是虚拟软盘的前512Byte;head程序则放在从第二个512Byte开始处和以后的地方.
(4)bootloader的任务是加载head,执行head;注意,我们如果还是简单的加载as86汇编程序,实模式下跳转,哪有什么意思呢?我们需要更近一步,进入保护模式,同时跳转到head程序,为以后的AB任务切换做准备.
2. bootloader程序
(1)代码boot.s
BOOTSEG = 0x07c0 SYSSEG = 0x1000 SYSLEN = 4 entry start start: jmpi go , # BOOTSEG go: mov ax , cs mov ds , ax mov es , ax mov ss , ax mov sp , # 0x400 ! clear screen mov ax , # 0x0600 mov cx , # 0x0000 mov dx , # 0xFFFF int 0x10 ! show "Loading..." mov cx , # 10 mov dx , # 0x0000 mov bx , # 0x000c mov bp , # msg mov ax , # 0x1301 int 0x10 load_system: mov dx , # 0x0000 mov cx , # 0x0002 mov ax , # SYSSEG mov es , ax xor bx , bx mov ax , # 0x200 + SYSLEN int 0x13 jnc ok_load mov dx , # 0x0000 mov ax , # 0x0000 int 0x13 jmp load_system ok_load: cli mov ax , # SYSSEG mov ds , ax xor ax , ax mov es , ax mov cx , # 0x1000 sub si , si sub di , di rep movw mov ax , cs mov ds , ax lidt idt_48 lgdt gdt_48 mov ax , # 0x0001 lmsw ax jmpi 0 , 8 msg: .ascii "Loading..." .byte 13 , 10 gdt: .word 0 , 0 , 0 , 0 .word 0x07FF .word 0x0000 .word 0x9A00 .word 0x00C0 .word 0x07FF .word 0x0000 .word 0x9200 .word 0x00C0 idt_48: .word 0 .word 0 , 0 gdt_48: .word 0x7FF .word 0x7c00 + gdt , 0 .org 510 .word 0xAA55(2)代码分析
BOOTSEG = 0x07c0 SYSSEG = 0x1000 SYSLEN = 4 entry start start: jmpi go,#BOOTSEG go: mov ax,cs mov ds,ax mov es,ax mov ss,ax mov sp,#0x400
只有mov sp,#0x400需要说一下,大家都知道sp是堆栈指针,对堆栈的操作会引起sp的变化,这里简单留出一段空间即可.
! clear screen mov ax,#0x0600 mov cx,#0x0000 mov dx,#0xFFFF int 0x10 ! show "Loading..." mov cx,#10 mov dx,#0x0000 mov bx,#0x000c mov bp,#msg mov ax,#0x1301 int 0x10
bios程序0x10的两段程序,分别是清屏幕和写字符串.
load_system: mov dx,#0x0000 mov cx,#0x0002 mov ax,#SYSSEG mov es,ax xor bx,bx mov ax,#0x200+SYSLEN int 0x13 jnc ok_load mov dx,#0x0000 mov ax,#0x0000 int 0x13 jmp load_system
这段程序是真正的loader部分了,也是对bios功能的调用,准备参数如下:
mov dx,#0x0000 - dh磁头是0,dl是0表示软盘 mov cx,#0x0002 - ch柱面是0,cl开始扇区为2 mov ax,#SYSSEG mov es,ax - es:bx = 0x1000:0x0 表示目的地址,0x13中断把扇区读到此位置. xor bx,bx - 清零bx. mov ax,#0x200+SYSLEN - ah对应int 0x13调用功能号02表示读扇区,al对应扇区个数. int 0x13
整体的意思是把0柱面,0磁头,从2扇区开始的4个扇区读到内存0x1000:0x0处.
jnc ok_load mov dx,#0x0000 mov ax,#0x0000 int 0x13 jmp load_system
这段代码意思是如果出错了,就反复读,直到读出来正确为止,正确后跳转到ok_load标号处.
ok_load: cli mov ax,#SYSSEG mov ds,ax xor ax,ax mov es,ax mov cx,#0x1000 sub si,si sub di,di rep movw
上边的代码目标是把读出来的代码移动到0x0处,为什么要移动到0x0处呢,就在0x1000:0x0处执行不成?实际上是可以的,只是需要和gdt描述符配合使用.
mov ax,cs mov ds,ax lidt idt_48 lgdt gdt_48
上边代码看似复杂,其实没什么,lidt是指令,idt_48是操作数;lgdt是指令,gdt_48是操作数;意思是加载中断描述符表和全局描述符表,为啥要加载这两个表呢?
因为保护模式下,cpu取指令和数据不再是0x7c00:0x0这样的方式了,而是根据你给定的0x1:0x0来找一个表,之后通过这个表找到具体的物理地址.这个过程中,
可以检查点权限什么的,起到保护作用.关于保护模式的问题慢慢理解即可,无需急于求成.mov ds,ax这句实际是提供数据段位置以便找到正确的变量地址.
mov ax,#0x0001 lmsw ax jmpi 0,8
以上几句看似神奇,其实也很简单,通过设置寄存器的值,让cpu进入保护模式,保护模式无非就是寻址方式变了而已,理解就好.寻址方式变了之后,注意jmpi 0,8不再是跳转到0x8段:0x0偏移处了,而8是gdt表的选择符,0是偏移,8是选择符在gdt表中的偏移,这个时候cs就会被赋值8,但是不会从0x8:0x0处取地址,而是从gdt+8这个描述符定义的物理基地址+0x0物理地址处取指令噢!
至此,跳转到了head程序里了。head程序被我们加载到0x1000:0x0处,后被移动到0x0:0x0处,我们判断8对应的gdt里的描述符定义的物理基地址是0x0,下面重点分析gdt表定义。
gdt: .word 0,0,0,0 - .word定义了一个字,就是两个字节,此处首先定义了0,0,0,0 8个空的字节,系统规定保留.一个段描述符就是8个字节哦. .word 0x07FF - 可以想到前边的jmpi 0,8中的8(前边有了8个字节,偏移分别是0^7)指的就是接下来的8个字节定义的这个段. 0x07FF表示段限制长度,就是说这个段有多长,0x07FF十进制是2047,这里还不能确定是2047*1B还是2047*4KB,要看后边的定义.后边定了颗粒度为4KB,表示段限长度为8M .word 0x0000 - 表示段基地址的0-15位 .word 0x9A00 - 00表示基地址的16-23位,9A为0x10011010分别表示代码段可读、执行 .word 0x00C0 - 00表示基地址的24-31为,C为0x1100表示颗粒度为4KB等. .word 0x07FF - 此段为数据段描述符,意义基本同上. .word 0x0000 .word 0x9200 - 00表示基地址的16-23位,92为0x10010010分别表示数据段可读写 .word 0x00C0
综上分析,我们实现了加载head程序,进入保护模式,同时最后跳转到了0x00000000物理地址处开始执行,我们知道head程序的代码就在那.
3. head程序
(1)代码head.s
.globl startup_32 .text startup_32: movl $ 0x10 , % eax mov % ax , % ds mov % ax , % es mov % ax , % fs mov % ax , % gs # shou msg movl $ 0xcc , % ax movw $ 0xb8000 , % dx die: jmp di e(2)代码分析
.globl startup_32 .text
声明全局符号,声明代码段开始
startup_32: movl $0x10,
