为了兼容, 386的保护模式可以在16位代码段和32位代码段之间随意转换, 用于标示是32位或者16位代码段的是段描述符内第6字节的第6位标示出来的. 叫做D/B位.

这个D/B位还有些郁闷, 如果是描述代码段时, 该位被称为D位, 如果标志位1, 那么模式使用32位地址, 所有的16位指令都会加前缀, 如果为0则默认是16位地址, 和指令. 指令前缀66H可以指定操作符的长度而不使用缺省长度, 67H可以用来指定地址值的长度.

下面这个实例就做到了在16位代码段和32位代码段之间的切换. 当然这个代码在逻辑上的含义是先显示线性地址100000h处16个字节的内容, 然后再显示一个数据段中的一个字符串.. 相对上一篇进入保护模式并没有复杂太多..

代码功能首先是为切换到保护模式做准备, 初始化VGDTR描述符, 还有各个段描述符, 然后将PE置位, 开启分段切换到保护模式. 然后将线性地址100000h处的16个字节内容. 写入到B80000H处, 也就是显示在屏幕上. 然后切换到16位段. 又显示了一个字符串. 然后再切换回实模式.

你可以从这里下载这个源码, 也是MASM9+link5写的..
        CodeSwitch.rar

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
;============================================================================
	;保护模式下32位段和16位段的转换
	;编译方法参加makefile, MASM9 + link5.6 TAB=8
;============================================================================
	.686p
	Include pm.inc		;重要头文件定义  
	option casemap:none
;============================================================================
	;16位数据段, 存储了一些非常重要的数据
;============================================================================
DataSeg		Segment		use16
GDT	label	byte				;全局描述符表
Dummy:		Descriptor	0, 0, 0		;空的描述符
;----------------------------------------------------------------------------		
;				;段基址	  	;段界限		;属性
Normal:		Descriptor	0, 		0ffffh,		DA_DRW	;规范选择字
Code16Desc:	Descriptor 	0,		0ffffh, 	DA_C	;非一致代码段16位
Code32Desc:	Descriptor 	0,		Code32Len, DA_C or DA_32;非一致代码段32位
VideoDesc:	Descriptor	0b8000h,	0ffffh, 	DA_DRW	;显存段(可读写)
DataSDesc:	Descriptor	100000h,	16,		DA_DR	;只读数据段
DataMessDesc:	Descriptor	0,	sizeof SzMessage, 	DA_DRW	;保护模式下使用的数据段
StackDesc:	Descriptor	0, 		256,		DA_DRW	;堆栈段(可读写)
;----------------------------------------------------------------------------
GDT_Len		equ	$ - 	GDT		;GDT长度
GDT_Ptr		word	GDT_Len-1		;VGDT
		dword	0
		
_RegSs		word	0			;用于保存旧的SS:SP
_RegSp		word	0
;----------------------------------------------------------------------------
NormalSelector	equ	Normal - GDT		;规范段描述符选择子
Code16Selector	equ	Code16Desc - GDT	;16位代码段选择子
Code32Selector	equ	Code32Desc - GDT	;32位代码段选种子
DataSrcSelector	equ	DataSDesc- GDT		;源数据段选择子
VideoSelector	equ	VideoDesc - GDT		;视频段选择子
DataMessSelector equ	DataMessDesc - GDT	;显示消息段的选择子
StackSelector	equ	StackDesc - GDT		;堆栈段选择子
DataSeg		Ends

DataMess	Segment	use16
	SzMessage	byte	"I entered a protected mode I'm Joen", 0
DataMess	Ends

;============================================================================
	;保护模式下使用的堆栈段
;============================================================================
StackSeg	Segment	para Stack use16	;堆栈段
	byte	256 dup( 0 )	
StackSeg	Ends

;============================================================================
	;16位代码段, 由保护模式跳入, 然后返回到实模式
;============================================================================
Code16Seg	Segment	use16
	
_RetReal	Proc

;----------------------------------------------------------------------------
	;在屏幕上显示一个字符串	
	mov	ax, DataMessSelector
	mov	ds, ax				;ds-->消息段
	
	mov	si, offset SzMessage
	
	mov	ax, VideoSelector
	mov	es, ax				;es-->视频段
	mov	di, 80*2*11+10*2		;15行10列
	
	mov	cx, sizeof SzMessage
@@:	lodsb
	mov	ah, 07h				;属性
	stosw
	loop	@b
	
;----------------------------------------------------------------------------
	;关闭保护模式, 准备返回实模式
	mov	ax, NormalSelector
	mov	ds, ax
	mov	es, ax
	mov	ss, ax				;规范选择子
	
	mov	eax, cr0
	and	al, 0feh
	mov	cr0, eax			;关闭分段标记
	
	;通过这个跳转刷新段选择子影子寄存器, 真正进入实模式
	jmp	far ptr _ToReal			
	
_RetReal 	Endp	
		
Code16Seg	Ends

;============================================================================
	;32位代码段, 由实模式跳入
;============================================================================
Code32Seg	Segment	use32 
;============================================================================
	;将_dwValue中的值最低位转换成ASCII并填写上属性, 在EAX中返回
;============================================================================
_ToAscii	Proc  _dwValue
	local	_byBuf[4]:byte
	
	mov	eax, _dwValue
	and	al, 0fh
	.if	al > 9 
		add	al, 'A'			;转换低位
	.Else
		add	al, '0'
	.Endif
	
	mov	byte ptr [_byBuf], al
	mov	byte ptr [_byBuf+1], 7		;属性位
	
	xchg	ah, al
	.if	al > 9
		add	al, 'A'			;转换高位
	.Else
		add	al, '0'
	.Endif
	mov	byte ptr [_byBuf+2], al
	mov	byte ptr [_byBuf+3], 7		;属性
	mov	eax, dword ptr [_byBuf]
	ret
_ToAscii 	Endp


_Entry		Proc				;由实模式跳入
	
	mov	ax, StackSelector
	mov	ss, ax
	mov	esp, 256			;设置ss->esp
	
	mov	ax, DataSrcSelector
	mov	ds, ax				;设置源数据段
	
	mov	ax, VideoSelector
	mov	es, ax				;设置目标视频段
	
	xor	esi, esi
	mov	edi, 80*2*10+10*2		;第10行10列
	mov	ecx, 16				;限长是16
	cld
;----------------------------------------------------------------------------	
@@:	lodsb					;转换100000h开始的20个字节数据
	Invoke	_ToAscii, eax
	stosd
	mov	ah, 7				;属性
	mov	al, 20h				;空格
	stosw
	loop	@b
;----------------------------------------------------------------------------
	;跳到16位代码段, 准备返回Dos	
	Jmp32	Code16Selector, <offset _RetReal>
_Entry 		Endp
Code32Seg_Entry	= _Entry - Code32Seg		;定义段内入口	
Code32Len	= $ -  Code32Seg		;段长度
Code32Seg	Ends
;============================================================================
	;16位段, 由这里进入保护模式
;============================================================================
StartCode	Segment	use16 

Jmain	Proc
;----------------------------------------------------------------------------	
	mov	eax, DataSeg
	mov	ds, ax
	shl	eax, 4
	add	eax, offset GDT
	mov	dword ptr ds:[GDT_Ptr+2], eax	;初始化VGDT描述符
;----------------------------------------------------------------------------	
	mov	eax, Code32Seg			;初始化32位代码段
	shl	eax, 4
	mov	word ptr ds:[Code32Desc+2], ax	;段基址低地址
	shr	eax, 16
	mov	byte ptr ds:[Code32Desc+4], al	;段基址高地址低位
	mov	byte ptr ds:[Code32Desc+7], ah	;段基址高地址高位
;----------------------------------------------------------------------------
	mov	eax, Code16Seg			;初始化16位代码段
	shl	eax, 4
	mov	word ptr ds:[Code16Desc+2], ax	;段基址低地址
	shr	eax, 16
	mov	byte ptr ds:[Code16Desc+4], al	;段基址高地址低位
	mov	byte ptr ds:[Code16Desc+7], ah	;段基址高地址高位
;----------------------------------------------------------------------------
	mov	eax, DataMess			;初始化一个数据段, 用于显示消息
	shl	eax, 4
	mov	word ptr ds:[DataMessDesc+2], ax;段基址低地址
	shr	eax, 16
	mov	byte ptr ds:[DataMessDesc+4], al;段基址高地址低位
	mov	byte ptr ds:[DataMessDesc+7], ah;段基址高地址高位
;----------------------------------------------------------------------------
	mov	word ptr ds:[_RegSs], ss
	mov	word ptr ds:[_RegSp], sp	;保存下SS:SP
	
	mov	eax, StackSeg			;初始化堆栈段
	shl	eax, 4
	mov	word ptr ds:[StackDesc+2], ax	;段基址低地址
	shr	eax, 16
	mov	byte ptr ds:[StackDesc+4],al	;段基址高地址低位
	mov	byte ptr ds:[StackDesc+7], ah	;段基址高地址高位
;----------------------------------------------------------------------------
	lgdt	fword ptr ds:[GDT_Ptr]		;装在VGDTR
	cli
	_EnableA20				;关中断 开A20地址线
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax			;开启分段进入保护模式
;----------------------------------------------------------------------------
	;刷新指令预读取序列, 真正进入保护模式
	Jmp16	Code32Selector,  < Code32Seg_Entry >
	
_ToReal::;从保护模式又回来了
	mov	ax, DataSeg
	mov	ds, ax
	mov	sp, ds:[_RegSp]
	mov	ss, ds:[_RegSs]			;恢复SS:SP
	_DisableA20				;关闭A20地址线
	sti
	mov	ax, 4c00h
	int	21h
Jmain 	Endp


	
StartCode	Ends

End	Jmain