大家一起写操作系统(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是如何在保护模式使用的了。
如果对代码有疑问,可以一起讨论交流。
实验环境:
编译器: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是如何在保护模式使用的了。
如果对代码有疑问,可以一起讨论交流。
相关文章推荐
- 写操作系统(五)执着 初始引导程序 加载汇编内核
- 构建自己的操作系统[1]-引导程序
- 引导程序和操作系统的区别
- 自己动手实现操作系统引导程序(OS bootloader)——借助QEMU/GDB/losetup/dd等工具
- 网上看到的,转过来和大家一起分享侃侃单片机裸奔的程序
- 自己动手写操作系统 将引导程序成功写入优盘启动电脑
- 比LILO更强劲的多操作系统引导程序
- 写操作系统(三)执着 初始引导程序
- 大家一起写操作系统(2)-简单的内核
- 用Bochs搭建操作系统引导程序调试环境
- 那些年,我们一起学过的汇编----之程序返回操作系统
- 操作系统引导程序(nasm)
- 大家一起写操作系统-准备知识(1)--计算机的启动过程
- 大家一起写操作系统(3)-时钟中断
- 操作系统实践之引导程序
- 我的Sinix操作系统(2)引导程序(1)
- 看了网上的大多防SQL攻击程序,自己编写了一个函数,比较完美。大家一起测测~
- 普通管理类程序开发之难度系数、层次之说法,可以看看自己停留在哪个层次,不足之处,请大家一起补充
- Linux操作系统GRUB引导程序配置方法大全(2)
- 那些年,我们一起学过的汇编----之程序返回操作系统