32位代码段和16位代码段切换的实例

实例功能是:以十六进制数和ASCII字符两种形式显示从内存地址100000H开始的16个字节的内容。 
从功能上看,本实例类似于上个实例,但在实现方法上却有了改变,它更能反映出实模式和保护模式切换的情况。具体实现步骤是:(1)作切换到保护方式的准备;
(2)切换到保护方式的一个32位代码段;
(3)把指定内存区域的内容以字节为单位,转换成对应的十六进制数的ASCII码,并直接填入显示缓冲区实现显示;
(4)再变换到保护方式下的一个16位代码段;
(5)把指定内存区域的内容直接作为ASCII码填入显示缓冲区中实现显示;
(6)切换回实模式。

jmp_to32 MACRO selector,offsetv
	db 0EAH		;操作码
	dw offsetv	;16位偏移
	dw selector	;段值或者选择子
	endm
jmp_to16 MACRO selector,offsetv
	db 0EAH     ;操作码
	dw offsetv	;32位偏移(低16位)
	dw 0        ;32位偏移(高16位)
	dw selector	;选择子
	endm
;存储段描述符结构类型的定义
Descriptor struc
	limitl		dw 0	;段界限(0-15)
	basel  	    dw 0	;段基地址(0-15)
	basem   	db 0	;段基地址(16-23)
	attributes 	dw 0	;段属性
	baseh      	db 0	;段基地址(24-31)
Descriptor ends
;伪描述符结构类型的定义
PDESC struc
	limit dw 0	;16界限
	base  dd 0	;32位基地址
PDESC ends
;常量定义
ATDR =90h		;存在的只读数据段属性
ATDW =92h 		;存在的可读写数据段属性
ATDWA =93h 		;存在的已访问可读写数据段属性值
ATCE  =98h		;存在的只执行代码段属性值
ATCE32 = 4098h	;存在的只执行32位代码段属性值
DATALEN =16
.386p
DSEG SEGMENT USE16 		;16位段
GDT label byte			;全局描述符表
DUMMY Descriptor<>		;空描述符
CODE32_SEL =08h   		;32位代码段描述符选择子
CODE32  Descriptor <CODE32LEN-1,,,ATCE32,>
CODE16_SEL =10h			;16位代码段描述符选择子
CODE16 Descriptor <0ffffh,,,ATCE,>
DATAS_SEL=18h			;源数据段描述符选择子
DATAS Descriptor <DATALEN-1,0fff0h,0fh,ATDR,0>
DATAD_SEL=20h			;目标数据段描述符选择子
DATAD Descriptor <DATALEN*20-1,80a0h,0bh,ATDW,0> ;显存偏移0B80A0h(第二行开始),注意段界限
STACKS_SEL=28h			;堆栈段描述符选择子
STACKS Descriptor<0ffffh,,,ATDWA,>
NORMAL_SEL =30h 		;规范段描述符选择子
NORMAL Descriptor <0ffffh,0,0,ATDW,0>
GDTLEN=$-GDT
VGDTR PDESC<GDTLEN-1,0>	;GDT伪描述符
VARSS dw ? 				;用于保存实模式下SS的变量
DSEG ends

CSEG1 SEGMENT USE16 'REAL'
	ASSUME CS:CSEG1,DS:DSEG
START:
	;初始化GDT表
	mov ax,DSEG
	mov ds,ax
	mov bx,16
	mul bx ;段值*16
	add ax,offset GDT ;GDT表偏移
	adc dx,0
	mov word ptr VGDTR.base,ax 	  ;32位基地址偏移低16位
	mov word ptr VGDTR.base+2,dx  ;32位基地址偏移高16位
	;16位界限在定义是已设置完毕
	
	;初始化32位代码段描述符
	mov ax,CSEG2
	mul bx ;段值*16
	mov CODE32.basel,ax
	mov CODE32.basem,dl
	mov CODE32.baseh,dh
	
	;初始化32位代码段描述符
	mov ax,CSEG3
	mul bx
	mov CODE16.basel,ax
	mov CODE16.basem,dl
	mov CODE16.baseh,dh
	
	;初始化32位堆栈段描述符
	mov ax,ss   	;设置堆栈基地址
	mul bx      	;段值*16
	mov STACKS.basel,ax
	mov STACKS.basem,dl
	mov STACKS.baseh,dh
	mov VARSS,ss 	;保存实模式下的SS
	lgdt fword ptr VGDTR
	cli				;关中断
	call EA20  		;打开地址线A20
	;准备切换到保护模式
	mov eax,cr0	
	or eax,1
	mov cr0,eax
	;进入32位代码段
	jmp_to32 <CODE32_SEL>,<offset SPM32>
TOREAL:
	mov ax,DSEG
	mov ds,ax
	mov ss,VARSS	;恢复实模式下的SS
	call DA20		;关闭地址线A20
	sti				;开中断
	mov ax,4c00h
	int 21h

  
;打开地址线A20  
EA20 proc  
    push ax  
    in al,92h  
    or al,2  
    out 92h,al  
    pop ax  
    ret  
EA20 endp  
  
;关闭地址线A20  
DA20 proc  
    push ax  
    in al,92h  
    and al,0fdh  ;0fdh=not 20h  
    out 92h,al  
    pop ax  
    ret  
DA20 endp 
CSEG1 ends

CSEG2 SEGMENT USE32 'PM32'
	ASSUME CS:CSEG2
SPM32:
	mov ax,STACKS_SEL
	mov ss,ax		;装载堆栈段寄存器SS
	mov ax,DATAS_SEL
	mov ds,ax		;装载源数据段寄存器DS
	mov ax,DATAD_SEL
	mov es,ax		;装载目标数据段寄存器ES
	
	xor esi,esi		;设置指针和计数器
	xor edi,edi
	mov ecx,DATALEN
	cld
NEXT:
	lodsb			;取一字节
	push ax
	call TOASCII    ;低4位转换成ASCII
	mov ah,7		;显示属性为黑底白字
	shl eax,16
	pop ax
	shr al,4
	call TOASCII	;高4位转换成ASCII
	mov ah,7
	stosd			;显示
	mov al,' '
	stosw 			;显示空格
	loop NEXT
	jmp_to16 CODE16_SEL,<offset SPM16>
;子程序功能:  
;把al低4位的十六进制数转换成对应的ASCII,保存在AL    
TOASCII  proc  
    and al,0fh  
    add al,'0'  
    cmp al,'9'  
    seta dl          ;如果大于'9',则设置dl=1,否则dl=0  
    movzx dx,dl  
    imul dx,7  
    add al,dl        ;如果dl=1则al+37,否则+30  
    ret  
TOASCII endp  
CODE32LEN =$-CSEG2
CSEG2 ends


CSEG3 SEGMENT USE16 'PM16'
	ASSUME CS:CSEG3
SPM16:
	xor si,si		;设置指针和计数器
	call DispReturn
	mov ah,7
	mov cx,DATALEN
AGAIN:
	lodsb			;把指定区域内容直接作为ASCII码显示
	stosw	
	loop AGAIN
	mov ax,NORMAL_SEL
	mov ds,ax		;把NORMAL段选择子装入DS和ES
	mov es,ax
	mov eax,cr0		;切换到实模式
	and eax,0fffffffeh
	mov cr0 ,eax
	;回到实模式
	jmp far ptr TOREAL
	
	;换行函数  
DispReturn proc
    push    ax  
    push    bx  
    mov ax, di 		;获取当前光标的显存偏移  
    mov bl, 160  	;每行80字符,每个字符占两个字节(字符ASCII码+字符属性),所以一行共80*2=160个字节  
    div bl       	;获取当前行数(在显存中行数从0开始编号的)  
    and ax, 0FFh	;取低8位(在当前页,否则有可能输出到其它页了,显示器不会显示的)  
    inc ax      	;下一行  
    mov bl, 160    
    mul bl       	;确定下一行开始的字节数  
    mov di, ax 		;更新edi寄存器  
    pop bx  
    pop ax  
  
    ret  
 DispReturn endp  
; DispReturn 结束--------------------------------------------------------- 
CSEG3 ends
	end START

从本实例的 GDT 中可见,两个数据段的界限都是根据实际大小而设置的。从源程序代码段 CSEG3 可见,在切换到实模式之前,把

一个指向似乎没有用的数据段的描述符 Normal 的选择子装载到 DS ES。这是为什么呢


在分段管理机制一文中已介绍过,每个段寄存器都配有段描述符高速缓冲寄存器,这些高速缓冲寄存器在实方式下仍发挥作用,只是内容上与保护模式下有所不同。如上表所示,其中“Y”表示; “N”表示;“B”表示字节;“U”表示向上扩展,“W”表示以字方式操作堆栈。段基地址仍是 32 位,其值是相应段寄存器值(段值)乘以 16,在把段值装载到段寄存器时刷新。由于其值是 16 位段值乘上 16,所以在实模式下基地址实际上有效位只有 20 位。每个段的 32 位段界限都固定为 0FFFFH,段属性的许多位也是固定的。所谓固定是指在实方式下不可设置这些属性值,只能继续沿用保护方式下所设置的值。因此,在准备结束保护模式回到实模式之前,要通过加载一个合适的描述符选择子到有关段寄存器,以使得对应段描述符高速缓冲寄存器中含有合适的段界限和属性。本实例 GDT 中的描述符 Normal就是这样一个描述符,在返回实模式之前把对应选择子 Normal_Sel 加载到 DS ES 就是此目的。由于 SS 段描述符中的内容已符合实模式的需要,所以尽管也改变了 SS,但不需要重新加载 SS(本实例中重新加载了 SS,这除了稍增加运行时间外,并没有什么坏处)16 位代码段描述符中的内容也符合实模式的需要,所以在通过 16 位代码段返回实模式时,CS 段描述符中的内容也符合实模式的要求。需要注意的是,不能从 32 位代码段返回实模式,这是因为无法实现从 32 位代码段返回时 CS 高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性

16位下的偏移是16位的,32位下的偏移是32位 的,所以可以看到在jmp_to16和jmp_to32中两个偏移是不一样。
CODE32_SEL =08h   		;32位代码段描述符选择子
CODE16_SEL =10h			;16位代码段描述符选择子
DATAS_SEL=18h			;源数据段描述符选择子
DATAD_SEL=20h			;目标数据段描述符选择子
DATAD Descriptor <DATALEN*20-1,80a0h,0bh,ATDW,0> ;显存偏移0B80A0h(第二行开始),注意段界限
STACKS_SEL=28h			;堆栈段描述符选择子
NORMAL_SEL =30h 		;规范段描述符选择子
关于这几个数字不知有没有没有看懂的,通过存储段描述符结构我们可以知道每个描述结构占8个字节所以第一个空描述符偏移为08H,第二个描述符偏移为10H,第三个描述符偏移为18H以此类推。当然也可以不用这样直接写出来,也可以这样定义:
DSEG SEGMENT USE16 		;16位段
GDT label byte			;全局描述符表
DUMMY Descriptor<>		;空描述符

;CODE32_SEL =08h   		;32位代码段描述符选择子
CODE32  Descriptor <CODE32LEN-1,,,ATCE32,>
CODE32_SEL =CODE32-GDT

;CODE16_SEL =$-offset CODE		;16位代码段描述符选择子
CODE16 Descriptor <0ffffh,,,ATCE,>
CODE16_SEL =CODE16-GDT

;DATAS_SEL=18h			;源数据段描述符选择子
DATAS Descriptor <DATALEN-1,0fff0h,0fh,ATDR,0>
DATAS_SEL=DATAS-GDT

;DATAD_SEL=20h			;目标数据段描述符选择子
DATAD Descriptor <DATALEN*20-1,80a0h,0bh,ATDW,0> ;显存偏移0B80A0h(第二行开始),注意段界限
DATAD_SEL=DATAD-GDT

;STACKS_SEL=28h			;堆栈段描述符选择子
STACKS Descriptor<0ffffh,,,ATDWA,>
STACKS_SEL= STACKS-GDT

;NORMAL_SEL =30h 		;规范段描述符选择子
NORMAL Descriptor <0ffffh,0,0,ATDW,0>
NORMAL_SEL=NORMAL-GDT

GDTLEN=$-GDT
VGDTR PDESC<GDTLEN-1,0>	;GDT伪描述符
VARSS dw ? 				;用于保存实模式下SS的变量
DSEG ends
我说一下我开始忽略的细节问题:
DATAD Descriptor <DATALEN*20-1,80a0h,0bh,ATDW,0> ;显存偏移0B80A0h(第二行开始),注意段界限
最开始的时候DATAD段界限不是DATALEN*20-1而是 DATALEN*8-1,代码中也没有换行函数,但是两次输出是一起的,为了好看,我增加了换行函数,但是这一增加就出问题了,在纯DOS 下直接崩溃了,被处理器重置了,反复来了几次,后来发现原来是DATAD段界限的问题,当在显存中输入数据时,因为我换行了,所以超过了段界限,导致了执行错误,后来改成了现在这个样子,才可以正确换行,当时换两次行却错误,还是超出了段界限(每一行有160个字节,每个字符占2个字节,故每行可以输出80个字符,16个字节+每个字节后一个空格=32个字符+直接输出16个字节,范围是在一行内的)。
运行结果:



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值