您的位置:首页 > 其它

大家一起写操作系统(1)-引导程序

2013-01-20 19:53 295 查看
上一节我们知道bios会把启动设备第一扇区的512B程序加载到0x7c00处执行.由于512B程序大小所限,所以这段程序一般用于引导,就是说将内核程序从启动设备中读出并复制到一个合适的位置,然后引导程序把控制权将给内核。这一节我们就实现这样一个引导程序。

实验环境:

编译器:NASM

虚拟机vmware8.0

二进制编辑器winHex:用于将引导程序和内核写入软盘映射文件img.

代码编辑器notepad++

结合代码详细分析引导程序的内容.

下面是引导程序内容:

BOOTSEG equ 0x07c0 ;boot.bin 被bios加载到0x7c00内存处

SYSSEG equ 0x1000 ;kernel先被加载到0x10000,再移动到0x0处

SYSLEN equ 17 ;内核最多占用17个扇区 17*512字节

KERNEL_CS equ 0x08 ;内核代码段选择符

start:

jmp 0x07c0:go ;这条指令将cs寄存器设为0x07c0,便于寻址

go:

mov ax,cs

mov ds,ax

mov es,ax

mov ss,ax

mov sp,0x400 ;开辟栈空间逻辑地址0x400-0x200用作栈空间,0x200-0x00用于存放boot.bin

上面的代码先用一个jmp跳转指令将cs寄存器的内容置为0x07c0,并同步给ds,es,ss.这是为了便于后面寻址,这样后面用label(比如load_kernel作为偏移地址时就是实际的物理地址,因为这段程序被加载到0x7c00处,就相当于cs*16 + label = 0x7c00 + label偏移).然后sp赋值为0x400,由于这段程序本身占一个扇区0x200,所以栈空间可用为0x200.

load_kernel:

mov dx,0x0000 ;DH is head,DL is driver

mov cx,0x0002; CL 6,7位是磁道号高2位.位5~0是起始扇区号(从1开始)

mov ax,SYSSEG

mov es,ax ;读入缓冲区基址0x1000

xor bx,bx

mov ax,0x200 + SYSLEN ;AH-读扇区功能号(2);AL-需要读的扇区数(17,即第一柱面除去第1个扇区(是boot.s),剩余的17个扇区是kernel.bin)

int 0x13

jnc load_ok; 如果有错误, CF位会被置位

load_fail: jmp load_fail ;死循环

上面的程序用于将软盘第一柱面的后面17个扇区读入内存地址0x10000处。一个软盘有80个柱面,每个柱面18个扇区.然后分正反2磁道。由于本程序在第一柱面第一扇区,所以内核放在第一柱面后面17个扇区内。读扇区如果有错误,CF标志会被置位,所以用jnc判断是否读错,如果错误程序死循环。

load_ok:

cli ;关中断

mov ax,SYSSEG

mov ds,ax ;源位置

xor ax,ax

mov es,ax ;目的位置0.这会把bios的代码和数据覆盖,bios中断将不再可用

sub si,si

sub di,di

mov cx,0x1000 ;移动4k

rep movsw ;移动 [ds:si]--->>[es:di]

上面程序用于将内核从0x10000处移到0x0处。由于覆盖了bios的代码和数据区,bios中断不可再用。

;加载IDT,GDT

mov ax,BOOTSEG

mov ds,ax ;数据段基址应为0x07c0

lidt [idt_48]

lgdt [gdt_48]

上面程序将GDTR,IDTR寄存器设置。

;设置CR0,进入保护模式(段级保护,不启用分页)

mov ax,0x0001

lmsw ax ;lmsw只设置cr0低4位

jmp KERNEL_CS:0

;设置CR0第0位,开启段级保护,注意并没有开启分页.lmsw是为了和8086处理器兼容而保留的指令,用于设置cr0寄存器的低4位.由于这段程序开始时运行用实模式,cs段寄存器存储的是一个16位段描述符,这个描述符被缓存起来使用,如果cs没有变化,这个缓存值会被一直用来当作段寄器的值来使用,所以要显式调用jmp指令将cs改变,用于选择内核段描述符,这是一个32位段描述符,基址为0.所以紧接着会执行0x00地址的代码,这就是上面将内核从0x10000移动到0x00的作用.

gdt:

dw 0,0,0,0 ;0描述符不用

;0x08

dw 0x07ff ;段限长2kb 段限长15---0位

dw 0x0000 ;段基址

dw 0x9a00 ; 47 --------------- 40 ------------ 32

; 1 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0

; P DPL S TYPE 段基址23-16位 P=1在内存中 DPL=00最高特权级别 S=1代码段或数据段 TYPE

dw 0x00c0 ; 63 --------------- 55 -------------- 47

;0x10 ;

dw 0x07FF

dw 0x0000

dw 0x9200

dw 0x00c0

;0x18

dw 0x07FF ;显存数据段,基地址0xb8000

dw 0x8000

dw 0x920b

dw 0x00c0

;END gdt

上面定义了GDT描述表,每个描述符8字节,定义了4个描述符,第一个为0,不使用。第二个为内核代码段只读,第三个为内核数据段可读写,第四个为显存数据段可读写。

idt_48:

dw 0 ;表长度0 0---------15 16------------------------47

dw 0,0 ;基址0 IDT限长度 IDT基址

;IDTR为6字节,这里暂定义为0,不使用.

gdt_48:

dw 0x7ff ;GDT表长度2KB,容纳256段描述符

dw 0x7c00+gdt,0;线性地址为0x7c00 + 偏移gdt

GDTR同样为6字节,定义线性地址为0x7c00+gdt,即为上面定义的gdt在物理内存中的地址。

上面这段代码用nasm.exe -o boot.bin boot.asm命令生成bin文件后,将它用winHex工具写入一个img文件的开头部分,并在512B最后两个字节处写上AA,55代表是一个引导扇区。然后在vmware中设置从软盘启动,并设置这个img文件为软盘映像,启动虚拟机即可执行引导程序。由于现在img的后面17个扇区还没有内容,所以什么结果也有。下一节将写一个打印字符口中的内核程序,将其放在第2扇区。这样这段程序执行后,就会把控制权交给打印程序,就模拟出了加载内核并执行内核的效果。下章见。

注意,这段代码没有对实模式和保护模式分段单元的寻址进行介绍,这部分内容可以在网上找到,如果有需要,可以网上了解。就可以明白GDT是如何在保护模式使用的了。

如果对代码有疑问,可以一起讨论交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: