古人诚不欺余,书读百遍其义自见.研究了5天,终于把于渊同志所写的实模式到保护模式的跳转的初级操作系统程序看懂了.文章虽艰涩难懂,但收获亦颇丰.
Intel8086系统的实模式是16位寻址,往后的80386是32位寻址.在保护模式下是32位寻址,Intel8086从实模式跳转到保护模式需要将寻址格式进行切换由16位物理寻址变成16位线性寻址.下面这段程序演示了如何从实模式跳转到保护模式.
1).首先构建pm.inc文件如下,此文件中定义了三个常量,和一个全局描述符宏,该宏可以将传进来的参数自动转换成8字节的GDT表中的全局描述符(32位寻址时用来描述分段地址).
1.1)全局描述符可参考文献:
https://blog.csdn.net/u014774781/article/details/47706213
https://blog.csdn.net/wrx1721267632/article/details/52056910
https://blog.csdn.net/me1171115772/article/details/51750442
https://www.docin.com/p-495926832.html
DA_32 EQU 4000h ;32位段
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_C EQU 98h ;存在的只执行代码段属性
;描述符定义,传进来的参数会自动装到对应的字节中
;usage:Descriptor Base,Limit,Attr
%macro Descriptor 3
dw %2&0FFFFh ;段界限1
dw %1&0FFFFh ;段基址1
db (%1>>16)&0FFh ;段基址2
dw ((%2>>8)&0F00h)|(%3&0F0FFh) ;属性1 + 段界限2 + 属性2
db (%1>>24)&0FFh ;段基址3
%endmacro ;共8个字节
2)创建后pm.inc后可以创建相应的pmtest1.asm文件如下:
; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
%include "pm.inc" ;常量,宏,以及一些说明
org 07c00h
jmp LABEL_BEGIN ;跳到开始处执行,此时不会修改cs,cs仍然为0,偏移地址会加上7c00
[SECTION .gdt] ;定义GDT全局描述符号(为(段地址,段界限,属性)三元组,占8字节空间)
LABEL_GDT: Descriptor 0,0,0 ;空描述符
LABEL_DESC_CODE32:Descriptor 0,SegCode32Len-1,DA_C+DA_32 ;指向32位保护模式非一致代码段
LABEL_DESC_VIDEO:Descriptor 0B8000h,0ffffh,DA_DRW ;显存(显卡)首地址描述符
;GDT结束
GdtLen equ $-LABEL_GDT ;GDT长度
GdtPtr dw GdtLen-1 ;GDT界限
dd 0 ;GDT基地址
;GDT选择子
SelectorCode32 equ LABEL_DESC_CODE32-LABEL_GDT
;这里比较特殊,各个描述符号的最右侧3位都为0,因此相减后,就是描述符在GDT表中的索引
SelectorVideo equ LABEL_DESC_VIDEO-LABEL_GDT
;END of [SECTION .gdt]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h
;初始化32位代码段全局描述符的段基址
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32+2],ax ;相对于基址cs的偏移
shr eax,16
mov byte [LABEL_DESC_CODE32+4],al
mov byte [LABEL_DESC_CODE32+7],ah
;为加载GDT表做准备,先将GDT表的界限和起始地址找到存到Gdtptr中
xor eax,eax
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr + 2],eax ;将GDT的基地址放好
lgdt [GdtPtr] ;将GDT表加载进来,后面可以访问GDT表中的值
;关中断
cli
;打开地址线A20
in al,92h
or al,00000010b
out 92h,al
;准备切换到保护模式
mov eax,cr0
or eax,1
mov cr0,eax
;真正进入保护模式
jmp dword SelectorCode32:0 ;执行这句话,为了将SelectorCode32
;指向的32为保护模式代码载入cs中
;并跳转到SelectorCode32:0处
;END of [SECTION .s16]
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax,SelectorVideo
mov gs,ax ;视屏段选择子加载进gs
mov edi,(80*11 + 79)*2 ;屏幕第11行,79列
mov ah,0Ch ;0,为黑字,c为红字
mov al,'P' ;要显示的字符
mov [gs:edi],ax
;到此停止
jmp $
SegCode32Len equ $-LABEL_SEG_CODE32
;END of [SECTION .s32]
3.用命令nasm pmtest1.asm -o pmtest1.bin编译后,得到下面的文件
4.将上节创建的a.img,boshsrc拷贝到当前工作目录,并用
dd if=pmtest1.asm of=a.img bs=512 count=1 conv=notrunc;
将pmtest1.asm刻录到a.img中.
5.执行bochs -f bochsrc,在命令窗口中先输入6再输入c得到下面的界面,界面上会输出一个P,P其实就是由保护模式输出的.
6.此程序也可以编译成com文件在dos下运行.即将pmtest1.asm改成pmtest1dos.asm文件(将org 7c00h改成org 0100h即可),如下
; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
%include "pm.inc" ;常量,宏,以及一些说明
org 0100h
jmp LABEL_BEGIN ;跳到开始处执行,此时不会修改cs,cs仍然为0,偏移地址会加上7c00
[SECTION .gdt] ;定义GDT全局描述符号(为(段地址,段界限,属性)三元组,占8字节空间)
LABEL_GDT: Descriptor 0,0,0 ;空描述符
LABEL_DESC_CODE32:Descriptor 0,SegCode32Len-1,DA_C+DA_32 ;指向32位保护模式非一致代码段
LABEL_DESC_VIDEO:Descriptor 0B8000h,0ffffh,DA_DRW ;显存(显卡)首地址描述符
;GDT结束
GdtLen equ $-LABEL_GDT ;GDT长度
GdtPtr dw GdtLen-1 ;GDT界限
dd 0 ;GDT基地址
;GDT选择子
SelectorCode32 equ LABEL_DESC_CODE32-LABEL_GDT
;这里比较特殊,各个描述符号的最右侧3位都为0,因此相减后,就是描述符在GDT表中的索引
SelectorVideo equ LABEL_DESC_VIDEO-LABEL_GDT
;END of [SECTION .gdt]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h
;初始化32位代码段全局描述符的段基址
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32+2],ax
shr eax,16
mov byte [LABEL_DESC_CODE32+4],al
mov byte [LABEL_DESC_CODE32+7],ah
;为加载GDT表做准备,先将GDT表的界限和起始地址找到存到Gdtptr中
xor eax,eax
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr + 2],eax ;将GDT的基地址放好
lgdt [GdtPtr] ;将GDT表加载进来,后面可以访问GDT表中的值
;关中断
cli
;打开地址线A20
in al,92h
or al,00000010b
out 92h,al
;准备切换到保护模式
mov eax,cr0
or eax,1
mov cr0,eax
;真正进入保护模式
jmp dword SelectorCode32:0 ;执行这句话,为了将SelectorCode32
;指向的32为保护模式代码载入cs中
;并跳转到SelectorCode32:0处
;END of [SECTION .s16]
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax,SelectorVideo
mov gs,ax ;视屏段选择子加载进gs
mov edi,(80*11 + 79)*2 ;屏幕第11行,79列
mov ah,0Ch ;0,为黑字,c为红字
mov al,'P' ;要显示的字符
mov [gs:edi],ax
;到此停止
jmp $
SegCode32Len equ $-LABEL_SEG_CODE32
;END of [SECTION .s32]
6.1)在bochs中安装Freedos
从ttp://bochs.sourceforge.net/guestos/freedos-img.tar.gz下载Freedos包,解压后,将a.img重命名为freedos.img拷贝到当前工作目录.
6.2)用bximge命令产生一个新的映象文件pm.img(大小1.44就可以)
6.3)将bochsrc中的三行改成下面模式,保存为文件bochsrcdos
floppya: 1_44="freedos.img", status=inserted
floppyb: 1_44="pm.img", status=inserted
boot: a
6.4)用命令bochs -f bochsrcdos启动bochs,然后格式化b盘
如下:
6.5) 用命令nasm pmtest1dos.asm -o pmtest1.com编译出pmtest1.com文件
6.6)将pmtest1.com拷贝到虚拟软盘pm.img上
sudo mount -o loop pm.img /mnt/floppy
sudo cp pmtest1.com /mnt/floppy/
sudo umount /mnt/floppy
6.7)bochs -f bochsrcdos启动后先输入6再输入c,会得到下面的界面,在界面上dos中输入b:pmtest1.com后,执行后也会输出P.