《Orange’s 一个操作系统的实现》3.保护模式3----保护模式进阶

在前面的代码上进行修改后,测试读写大地址内存(实模式下的1MB限制),而且从保护模式再调回实模式.

上代码分析(省略前面重复的代码部分):

; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o pmtest2.com
; ==========================================

%include	"pm.inc"	; 常量, 宏, 以及一些说明

org	0100h                   ;参看DOS系统中.EXE文件的加载过程
	jmp	LABEL_BEGIN

[SECTION .gdt]
; GDT
;                            段基址,        段界限 , 属性
LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符
LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符
LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代码段, 16
LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data
LABEL_DESC_STACK:  Descriptor    0,     TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW    ;5M基址
LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 显存首地址
; GDT 结束

GdtLen		equ	$ - LABEL_GDT	; GDT长度
GdtPtr		dw	GdtLen - 1	; GDT界限
		dd	0		; GDT基地址

; GDT 选择子
SelectorNormal		equ	LABEL_DESC_NORMAL	- LABEL_GDT
SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
SelectorCode16		equ	LABEL_DESC_CODE16	- LABEL_GDT
SelectorData		equ	LABEL_DESC_DATA		- LABEL_GDT
SelectorStack		equ	LABEL_DESC_STACK	- LABEL_GDT
SelectorTest		equ	LABEL_DESC_TEST		- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
; END of [SECTION .gdt]

[SECTION .data1]	 ; 32位数据段
ALIGN	32
[BITS	32]
LABEL_DATA:
SPValueInRealMode	dw	0                               ;保存实模式下的SP
; 字符串
PMMessage:		db	"In Protect Mode now. ^-^", 0	; 在保护模式中显示
OffsetPMMessage		equ	PMMessage - $$                  ;PMMessage在LABEL_DATA节中的偏移
StrTest:		db	"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest		equ	StrTest - $$                    ;StrTest在LABEL_DATA节中中的偏移
DataLen			equ	$ - LABEL_DATA                  ;LABEL_DATA节的长度
; END of [SECTION .data1]


; 全局堆栈段
[SECTION .gs]
ALIGN	32
[BITS	32]
LABEL_STACK:
	times 512 db 0                      ;512字节的堆栈段

TopOfStack	equ	$ - LABEL_STACK – 1 ;栈顶(偏移)

; END of [SECTION .gs]


[SECTION .s16]
[BITS	16]
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0100h

	mov	[LABEL_GO_BACK_TO_REAL+3], ax ;保存实模式下的CS
	mov	[SPValueInRealMode], sp       ;保存实模式下的SP

	; 初始化 16 位代码段描述符
	mov	ax, cs
	movzx	eax, ax
	shl	eax, 4
	add	eax, LABEL_SEG_CODE16
	mov	word [LABEL_DESC_CODE16 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE16 + 4], al
	mov	byte [LABEL_DESC_CODE16 + 7], ah

	; 初始化 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

	; 初始化数据段描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_DATA
	mov	word [LABEL_DESC_DATA + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_DATA + 4], al
	mov	byte [LABEL_DESC_DATA + 7], ah

	; 初始化堆栈段描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_STACK
	mov	word [LABEL_DESC_STACK + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_STACK + 4], al
	mov	byte [LABEL_DESC_STACK + 7], ah

	; 为加载 GDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_GDT		; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址

	; 加载 GDTR
	lgdt	[GdtPtr]

	; 关中断
	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 装入 cs, 并跳转到 Code32Selector:0  处

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LABEL_REAL_ENTRY:		; 从保护模式跳回到实模式就到了这里
	mov	ax, cs          ;jmp 0:LABEL_REAL_ENTRY 跳转到此处,恢复cs
	mov	ds, ax          ;使用cs恢复ds,es,ss
	mov	es, ax
	mov	ss, ax

	mov	sp, [SPValueInRealMode] ;恢复实模式下的sp

	in	al, 92h		; `. 
	and	al, 11111101b	;  | 关闭 A20 地址线
	out	92h, al		; /

	sti			; 开中断

	mov	ax, 4c00h	; `.
	int	21h		; /  回到 DOS
; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]

LABEL_SEG_CODE32:
	mov	ax, SelectorData
	mov	ds, ax			; 数据段选择子
	mov	ax, SelectorTest
	mov	es, ax			; 测试段选择子
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子

	mov	ax, SelectorStack
	mov	ss, ax			; 堆栈段选择子

	mov	esp, TopOfStack


	; 下面显示一个字符串
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	xor	esi, esi                ; esi重置
	xor	edi, edi                ; edi重置
	mov	esi, OffsetPMMessage	; 源数据偏移
	mov	edi, (80 * 10 + 0) * 2	; 目的数据偏移。屏幕第 10 行, 第 0 列。
	cld
.1:
	lodsb                   ;逐个加载ds:esi对应的字符到al中,其中DS中的选择子SelectData指向LABEL_DATA
	test	al, al          ;判断al字符是否为0(只是为了测试,请看PMMeesage的定义最后是0)
	jz	.2              ;为0跳转.2显示完毕
	mov	[gs:edi], ax    ;非0将ax中的字符移到视频选择子对应的区域gs:edi
	add	edi, 2          ;设置gs:edi指向下个字符,edi加2(ax为16位)
	jmp	.1              ;跳转到.1处继续
.2:	; 显示完毕

	call	DispReturn      ;显示完毕后,换行显示

	call	TestRead        ;读5M地址内容
	call	TestWrite       ;写5M地址内容
	call	TestRead        ;在此读5M地址荣,测试写5M地址内容是否成功

	; 到此停止
	jmp	SelectorCode16:0 ;跳转到LABEL_SEG_CODE16处执行(调回实模式)

; ------------------------------------------------------------------------
TestRead:
	xor	esi, esi      ;esi重置
	mov	ecx, 8        ;设置读取字节数位8
.loop:
	mov	al, [es:esi]  ;将es:esi中的字符移到al中(5M地址)
	call	DispAL        ;显示al中的值
	inc	esi           ;增加esi的值,指向下个es:esi的下个字符
	loop	.loop         ;继续读取

	call	DispReturn    ;读取完毕显示回车

	ret                   ;返回调用处
; TestRead 结束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:
	push	esi                     ;esi入栈
	push	edi                     ;edi入栈
	xor	esi, esi                ;esi重置
	xor	edi, edi                ;edi重置
	mov	esi, OffsetStrTest	;源数据偏移
	cld
.1:
	lodsb                           ;将OffsetStrTest对应的字符串逐个加载到al中
	test	al, al                  ;是否为0
	jz	.2                      ;为0,跳转.2读取完毕
	mov	[es:edi], al            ;将al中的值写入测试大地址(5M)
	inc	edi                     ;增加EDI,下个字符的位置
	jmp	.1                      ;继续读取
.2:

	pop	edi                     ;恢复edi,esi
	pop	esi

	ret
; TestWrite 结束----------------------------------------------------------


; ------------------------------------------------------------------------
; 显示 AL 中的数字(将二进制转换为16进制显示)
; 默认地:
;	数字已经存在 AL 中
;	edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
;	ax, edi
; ------------------------------------------------------------------------
DispAL:
	push	ecx                     ;ecx,edx入栈
	push	edx

	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	dl, al                  ; al暂存入dl中
	shr	al, 4                   ; al右移4位
	mov	ecx, 2                  ;   
.begin:
	and	al, 01111b              ; 将al中的高4位清0,保留低四位(原高四位)
	cmp	al, 9                   ; 是否大于9
	ja	.1                      ; 大于则跳转.1
	add	al, '0'                 ; 
	jmp	.2
.1:
	sub	al, 0Ah                 ;大于9将数字扣除0Ah(10)
	add	al, 'A'                 ;加上'A'(65)转换为A到F  例如11 – 10 + 65 = 66 = 'B'
.2:
	mov	[gs:edi], ax            ;移入显存区域
	add	edi, 2                  ;edi+2指向下个位置
    
	mov	al, dl                  ;dl移入al中,进行低4位的16进制转换
	loop	.begin                  ;跳转.begin
	add	edi, 2                  ;edi加2

	pop	edx                     ;恢复edx,ecx
	pop	ecx

	ret
; DispAL 结束-------------------------------------------------------------


; ------------------------------------------------------------------------
DispReturn:
	push	eax             ;eax,ebx入栈
	push	ebx        
	mov	eax, edi        ;edi移入eax     
	mov	bl, 160         ;bl设置为160(设定每行显示为(80*10 + 0)*2)
	div	bl              ;al/bl = edi/bl   al位商   ah为余数
	and	eax, 0FFh       ;清除eax的高16位,
	inc	eax             ;eax+1
	mov	bl, 160         ;
	mul	bl              ;ah = al*160=(edi/160的商+1)*160-----达到换行的效果
	mov	edi, eax        ;
	pop	ebx
	pop	eax

	ret
; DispReturn 结束---------------------------------------------------------

SegCode32Len	equ	$ - LABEL_SEG_CODE32
; END of [SECTION .s32]


; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN	32
[BITS	16]
LABEL_SEG_CODE16:
	; 跳回实模式:
	mov	ax, SelectorNormal ;将SelectorNormal加载到ds,es,fs,gs,ss,去除在32保护模式中的段属性
                                   ;使描述符寄存器含有合适的段界限和属性(实模式下32段属性不符合实模式的要求)
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	gs, ax
	mov	ss, ax

	mov	eax, cr0           ;设置cr0的PE为0
	and	al, 11111110b
	mov	cr0, eax 

LABEL_GO_BACK_TO_REAL:
	jmp	0:LABEL_REAL_ENTRY	; 段地址会在程序开始处被设置成正确的值
                                              ;jmp segment:offset 根据段间转移(长转移),汇编指令为:
                                        ;0EAh Offset(2字节) Segment(2字节)   
                                        ;这就是代码mov [LABEL_GO_BACK_TO_REAL+3], ax的作用
                                        ;将实模式下的cs值,写入LABEL_GO_BACK_TO_REAL地址偏移+3的位置,
                                        ;刚好就是jmp指令EA的Segment处,请参看实模式下的长跳转指令图示          
                                        ;所以此的指令为JPM  cs(实模式):LABEL_REAL_ENRY
Code16Len	equ	$ - LABEL_SEG_CODE16

; END of [SECTION .s16code]
 
 
 

相关:org 0100h 请参看DOS加载.EXE过程《Orange’s 一个操作系统的实现》4.保护模式3----DOS加载.EXE过程

posted @ 2011-01-10 23:06  Aoysme  阅读(814)  评论(1编辑  收藏  举报