前几节课我们演示了从实模式进入到保护模式,那么从保护模式返回到实模式具体怎么操作呢?

  先将上一节的程序列出:

  1 %include "inc.asm"
  2 
  3 org 0x9000
  4 
  5 jmp CODE16_SEGMENT
  6 
  7 [section .gdt]
  8 ; GDT definition
  9 ;                                 段基址,       段界限,       段属性
 10 GDT_ENTRY       :     Descriptor    0,            0,           0
 11 CODE32_DESC     :     Descriptor    0,    Code32SegLen  - 1,   DA_C + DA_32
 12 VIDEO_DESC      :     Descriptor 0xB8000,       0x07FFF,       DA_DRWA + DA_32
 13 DATA32_DESC     :     Descriptor    0,    Data32SegLen  - 1,   DA_DR + DA_32
 14 STACK_DESC      :     Descriptor    0,      TopOfStackInit,    DA_DRW + DA_32        
 15 ; GDT end
 16 
 17 GdtLen    equ   $ - GDT_ENTRY
 18 
 19 GdtPtr:
 20           dw   GdtLen - 1
 21           dd   0
 22           
 23           
 24 ; GDT Selector
 25 
 26 Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
 27 VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
 28 Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
 29 StackSelector     equ (0x0004 << 3) + SA_TIG + SA_RPL0
 30 
 31 ; end of [section .gdt]
 32 
 33 TopOfStackInit    equ  0x7c00
 34 
 35 [section .dat]
 36 [bits 32]
 37 DATA32_SEGMENT:
 38     DTOS                 db    "D.T.OS!", 0
 39     DTOS_OFFSET          equ   DTOS - $$
 40     HELLO_WORLD          db    "Hello World!", 0
 41     HELLO_WORLD_OFFSET   equ  HELLO_WORLD - $$
 42 
 43 Data32SegLen  equ $ - DATA32_SEGMENT
 44 
 45 [section .s16]
 46 [bits 16]
 47 CODE16_SEGMENT:
 48     mov ax, cs
 49     mov ds, ax
 50     mov es, ax
 51     mov ss, ax
 52     mov sp, TopOfStackInit
 53     
 54     ; initialize GDT for 32 bits code segment
 55     mov esi, CODE32_SEGMENT
 56     mov edi, CODE32_DESC
 57     
 58     call InitDescItem
 59     
 60     mov esi, DATA32_SEGMENT
 61     mov edi, DATA32_DESC
 62     
 63     call InitDescItem
 64     
 65     ; initialize GDT pointer struct
 66     mov eax, 0
 67     mov ax, ds
 68     shl eax, 4
 69     add eax, GDT_ENTRY
 70     mov dword [GdtPtr + 2], eax
 71 
 72     ; 1. load GDT
 73     lgdt [GdtPtr]
 74     
 75     ; 2. close interrupt
 76     cli 
 77     
 78     ; 3. open A20
 79     in al, 0x92
 80     or al, 00000010b
 81     out 0x92, al
 82     
 83     ; 4. enter protect mode
 84     mov eax, cr0
 85     or eax, 0x01
 86     mov cr0, eax
 87     
 88     ; 5. jump to 32 bits code
 89     jmp dword Code32Selector : 0
 90 
 91     
 92 ; esi    --> code segment label
 93 ; edi    --> descriptor label
 94 InitDescItem:
 95     push eax
 96     
 97     mov eax, 0
 98     mov ax, cs
 99     shl eax, 4
100     add eax, esi
101     mov word [edi + 2], ax
102     shr eax, 16
103     mov byte [edi + 4], al
104     mov byte [edi + 7], ah
105     
106     pop eax
107     
108     ret
109     
110     
111 [section .s32]
112 [bits 32]
113 CODE32_SEGMENT:
114     mov ax, VideoSelector
115     mov gs, ax
116     
117     mov ax, StackSelector
118     mov ss, ax
119     
120     mov ax, Data32Selector
121     mov ds, ax
122     
123     mov ebp, DTOS_OFFSET
124     mov bx, 0x0C
125     mov dh, 12
126     mov dl, 33
127     
128     call PrintString
129     
130     mov ebp, HELLO_WORLD_OFFSET
131     mov bx, 0x0C
132     mov dh, 13
133     mov dl, 30
134     
135     call PrintString
136     
137     jmp $
138 
139 ; ds:ebp   --> string address
140 ; bx       --> attribute
141 ; dx       --> dh : row, dl : col
142 PrintString:
143     push ebp
144     push eax
145     push edi 
146     push cx
147     push dx
148     
149 print:
150     mov cl, [ds:ebp]
151     cmp cl, 0
152     je end
153     mov eax, 80
154     mul dh
155     add al, dl
156     shl eax, 1
157     mov edi, eax
158     mov ah, bl
159     mov al, cl
160     mov [gs:edi], ax
161     inc ebp
162     inc dl
163     jmp print
164     
165 end:
166     pop dx
167     pop cx
168     pop edi
169     pop eax
170     pop ebp
171     
172     ret
173 
174 Code32SegLen    equ    $ - CODE32_SEGMENT

上一节中,我们跳到32位保护模式后,并没有设置栈顶指针esp,但是程序依然可以正常运行,这时怎么回事呢?原因是我们在第52行设置了栈顶指针,而我们的程序中,16位的实模式和32位的保护模式使用的栈是一样的,因此,无需重新设置程序也可以正常运行。第14行的段描述符描述了32位保护模式下的栈的信息,在保护模式下即使我们将这个段的选择子,赋值给ss,那么由于段基址是0,得到最终的栈顶指针依然是 段基址+esp=0+esp,所以不给ss赋值和给ss赋值的结果是一样的。如果在32位保护时使用的栈和16位实模式使用的栈不一样的话,就不能这样操作了,而必须在进入32位保护模式后设置ss段寄存和esp栈顶指针。

 保护模式下的栈段,我们一般要进行以下步骤的设置:

1、指定一段空间,并为其定义段描述符

2、根据段描述表中的位置定义段选择子

3、初始化栈段寄存器(ss <- StackSelector)

4、初始化栈顶指针(esp <- TopOfStack )

下面定义32位保护模式下的专用栈:

 

  1 %include "inc.asm"
  2 
  3 org 0x9000
  4 
  5 jmp CODE16_SEGMENT
  6 
  7 [section .gdt]
  8 ; GDT definition
  9 ;                                 段基址,       段界限,       段属性
 10 GDT_ENTRY       :     Descriptor    0,            0,           0
 11 CODE32_DESC     :     Descriptor    0,    Code32SegLen  - 1,   DA_C + DA_32
 12 VIDEO_DESC      :     Descriptor 0xB8000,       0x07FFF,       DA_DRWA + DA_32
 13 DATA32_DESC     :     Descriptor    0,    Data32SegLen  - 1,   DA_DR + DA_32
 14 STACK32_DESC    :     Descriptor    0,      TopOfStack32,      DA_DRW + DA_32        
 15 ; GDT end
 16 
 17 GdtLen    equ   $ - GDT_ENTRY
 18 
 19 GdtPtr:
 20           dw   GdtLen - 1
 21           dd   0
 22           
 23           
 24 ; GDT Selector
 25 
 26 Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
 27 VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
 28 Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
 29 Stack32Selector   equ (0x0004 << 3) + SA_TIG + SA_RPL0
 30 
 31 ; end of [section .gdt]
 32 
 33 TopOfStack16    equ  0x7c00
 34 
 35 [section .dat]
 36 [bits 32]
 37 DATA32_SEGMENT:
 38     DTOS                 db    "D.T.OS!", 0
 39     DTOS_OFFSET          equ   DTOS - $$
 40     HELLO_WORLD          db    "Hello World!", 0
 41     HELLO_WORLD_OFFSET   equ  HELLO_WORLD - $$
 42 
 43 Data32SegLen  equ $ - DATA32_SEGMENT
 44 
 45 [section .s16]
 46 [bits 16]
 47 CODE16_SEGMENT:
 48     mov ax, cs
 49     mov ds, ax
 50     mov es, ax
 51     mov ss, ax
 52     mov sp, TopOfStack16
 53     
 54     ; initialize GDT for 32 bits code segment
 55     mov esi, CODE32_SEGMENT
 56     mov edi, CODE32_DESC
 57     
 58     call InitDescItem
 59     
 60     mov esi, DATA32_SEGMENT
 61     mov edi, DATA32_DESC
 62     
 63     call InitDescItem
 64     
 65     mov esi, DATA32_SEGMENT
 66     mov edi, STACK32_DESC
 67     
 68     call InitDescItem
 69     
 70     ; initialize GDT pointer struct
 71     mov eax, 0
 72     mov ax, ds
 73     shl eax, 4
 74     add eax, GDT_ENTRY
 75     mov dword [GdtPtr + 2], eax
 76 
 77     ; 1. load GDT
 78     lgdt [GdtPtr]
 79     
 80     ; 2. close interrupt
 81     cli 
 82     
 83     ; 3. open A20
 84     in al, 0x92
 85     or al, 00000010b
 86     out 0x92, al
 87     
 88     ; 4. enter protect mode
 89     mov eax, cr0
 90     or eax, 0x01
 91     mov cr0, eax
 92     
 93     ; 5. jump to 32 bits code
 94     jmp dword Code32Selector : 0
 95 
 96     
 97 ; esi    --> code segment label
 98 ; edi    --> descriptor label
 99 InitDescItem:
100     push eax
101     
102     mov eax, 0
103     mov ax, cs
104     shl eax, 4
105     add eax, esi
106     mov word [edi + 2], ax
107     shr eax, 16
108     mov byte [edi + 4], al
109     mov byte [edi + 7], ah
110     
111     pop eax
112     
113     ret
114     
115     
116 [section .s32]
117 [bits 32]
118 CODE32_SEGMENT:
119     mov ax, VideoSelector
120     mov gs, ax
121     
122     mov ax, Stack32Selector
123     mov ss, ax
124     
125     mov eax, TopOfStack32
126     mov esp, eax
127     
128     mov ax, Data32Selector
129     mov ds, ax
130     
131     mov ebp, DTOS_OFFSET
132     mov bx, 0x0C
133     mov dh, 12
134     mov dl, 33
135     
136     call PrintString
137     
138     mov ebp, HELLO_WORLD_OFFSET
139     mov bx, 0x0C
140     mov dh, 13
141     mov dl, 30
142     
143     call PrintString
144     
145     jmp $
146 
147 ; ds:ebp   --> string address
148 ; bx       --> attribute
149 ; dx       --> dh : row, dl : col
150 PrintString:
151     push ebp
152     push eax
153     push edi 
154     push cx
155     push dx
156     
157 print:
158     mov cl, [ds:ebp]
159     cmp cl, 0
160     je end
161     mov eax, 80
162     mul dh
163     add al, dl
164     shl eax, 1
165     mov edi, eax
166     mov ah, bl
167     mov al, cl
168     mov [gs:edi], ax
169     inc ebp
170     inc dl
171     jmp print
172     
173 end:
174     pop dx
175     pop cx
176     pop edi
177     pop eax
178     pop ebp
179     
180     ret
181 
182 Code32SegLen    equ    $ - CODE32_SEGMENT
183 
184 [section .gs]
185 [bits 32]
186 STACK32_SEGMENT:
187     times 1024 * 4 db 0
188     
189 Stack32SegLen    equ $ - STACK32_SEGMENT
190 TopOfStack32    equ Stack32SegLen - 1

184-190行我们重新定义了32位保护模式下的栈段。并在14行和19行为其填充了段描述符表项和段选择子。我们在94行打上断点,看看程序执行到这里时栈顶指针寄存器的值是多少。启动bochs开始运行,结果如下:

 

可以看到这时的esp是0x7c00。

122-126行,我们在32位保护模式中设置了栈的段基址和栈顶指针。继续单步执行程序,如下:

图中可以看出,我们将段选择子赋值给了ss,将栈的段界限赋值给了esp,因为栈是向下生长的,所以就应该将段界限赋值给esp。

继续执行程序,最终结果如下:

 

从保护模式返回时模式:

8086中的一个神秘限制:

  无法直接从32位代码段回到实模式

  只能从16位代码段间接返回实模式

  在返回前必须用合适的选择子对段寄存器赋值

可以从16位实模式代码段跳到32位保护模式代码段,但是返回的话不能直接进行。

返回流程:先从32位保护模式的代码段返回16位保护模式的代码段(保护模式下也可以定义16位的代码段),然后从16位保护模式代码段跳到16位实模式代码段。

16位保护模式的代码段在这里作为一个中间过渡过程,我们在这个段只干一件事,就是用合适的段选择子对段寄存器进行赋值。除此之外不做其他的逻辑上的操作。

在操作之前,我们先介绍一下处理器中的设计:

80286之后的处理器都提供兼容8086的实模式

然而,绝大多数时候处理器都运行于保护模式

因此,保护模式的运行效率至关重要

那么,处理器如何高效的访问内存中的段描述符呢?

运行于保护模式时,性能瓶颈在于:段描述符定义在内存中,如果每次都要访问内存,效率会比较低。如何快速高效的访问内存中的段描述符呢?解决方案如下:

使用高速缓冲存储器

当使用选择子设置段寄存器时,会触发处理器的内部操作:

  根据选择子访问内存中的段描述符

  将段描述符加载到段寄存器的高速缓冲存储器

  需要段描述符信息时,直接从高速缓冲器中获得

处于实模式时也会用到这个段寄存器高速缓冲存储器。会用到其中的段基地址和段界限。

注意事项:

  在实模式 下,高速缓冲存储器仍然发挥着作用

  段基址是32位,其值是相应段寄存器的值乘以16

  实模式下段基址有效位为20位(高速缓存中的32段基址足以容纳),段界限固定为0xFFFF(64K)

  段属性的值不可设置,只能继续沿用保护方式下所设置的值

 高速缓冲存储器不可以直接访问设置值。只能通过特殊的方法:

  通过加载一个合适的描述符选择子到有关段寄存器,以使得对应的段描述符高速缓冲寄存器中含有合适的段界限和段属性。

 跳到16位实模式的具体流程:

  32位保护模式代码段 -> 16位保护模式代码段(刷新段寄存器,退出保护模式) -> 16位实模式代码段(设置段寄存器的值,关闭A20地址线,启用硬件中断)

汇编小知识:深入理解jmp指令

段内跳转: 指令是三个字节,操作码(E9)为1个字节(低地址),操作数是两个字节(高地址)(也就是段内偏移地址)。

段间跳转:指令时5个字节,操作码(EA)为1个字节(低地址),操作数是四个字节(偏移地址、段基址)(高地址)

 段间跳转时,我们可以修改指令中的偏移地址和段基址就可以跳转到另一个期望的段中去了。修改指令是运行时修改内存中的指令,而不是在源程序中修改。

 从保护模式返回到实模式的程序如下:

  1 %include "inc.asm"
  2 
  3 org 0x9000
  4 
  5 jmp ENTRY_SEGMENT
  6 
  7 [section .gdt]
  8 ; GDT definition
  9 ;                                 段基址,       段界限,       段属性
 10 GDT_ENTRY       :     Descriptor    0,            0,           0
 11 CODE32_DESC     :     Descriptor    0,    Code32SegLen  - 1,   DA_C + DA_32
 12 VIDEO_DESC      :     Descriptor 0xB8000,       0x07FFF,       DA_DRWA + DA_32
 13 DATA32_DESC     :     Descriptor    0,    Data32SegLen  - 1,   DA_DR + DA_32
 14 STACK32_DESC    :     Descriptor    0,      TopOfStack32,      DA_DRW + DA_32    
 15 CODE16_DESC     :     Descriptor    0,           0xFFFF,       DA_C
 16 UPDATE_DESC     :     Descriptor    0,           0xFFFF,       DA_DRW
 17 ; GDT end
 18 
 19 GdtLen    equ   $ - GDT_ENTRY
 20 
 21 GdtPtr:
 22           dw   GdtLen - 1
 23           dd   0
 24           
 25           
 26 ; GDT Selector
 27 
 28 Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
 29 VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
 30 Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
 31 Stack32Selector   equ (0x0004 << 3) + SA_TIG + SA_RPL0
 32 Code16Selector    equ (0x0005 << 3) + SA_TIG + SA_RPL0
 33 UpdateSelector    equ (0x0006 << 3) + SA_TIG + SA_RPL0
 34 ; end of [section .gdt]
 35 
 36 TopOfStack16    equ  0x7c00
 37 
 38 [section .dat]
 39 [bits 32]
 40 DATA32_SEGMENT:
 41     DTOS                 db    "D.T.OS!", 0
 42     DTOS_OFFSET          equ   DTOS - $$
 43     HELLO_WORLD          db    "Hello World!", 0
 44     HELLO_WORLD_OFFSET   equ  HELLO_WORLD - $$
 45 
 46 Data32SegLen  equ $ - DATA32_SEGMENT
 47 
 48 [section .s16]
 49 [bits 16]
 50 ENTRY_SEGMENT:
 51     mov ax, cs
 52     mov ds, ax
 53     mov es, ax
 54     mov ss, ax
 55     mov sp, TopOfStack16
 56     
 57     mov [BACK_TO_REAL_MODE + 3], ax 
 58     
 59     ; initialize GDT for 32 bits code segment
 60     mov esi, CODE32_SEGMENT
 61     mov edi, CODE32_DESC
 62     
 63     call InitDescItem
 64     
 65     mov esi, DATA32_SEGMENT
 66     mov edi, DATA32_DESC
 67     
 68     call InitDescItem
 69     
 70     mov esi, DATA32_SEGMENT
 71     mov edi, STACK32_DESC
 72     
 73     call InitDescItem
 74     
 75     mov esi, CODE16_SEGMENT
 76     mov edi, CODE16_DESC
 77     
 78     call InitDescItem
 79     
 80     ; initialize GDT pointer struct
 81     mov eax, 0
 82     mov ax, ds
 83     shl eax, 4
 84     add eax, GDT_ENTRY
 85     mov dword [GdtPtr + 2], eax
 86 
 87     ; 1. load GDT
 88     lgdt [GdtPtr]
 89     
 90     ; 2. close interrupt
 91     cli 
 92     
 93     ; 3. open A20
 94     in al, 0x92
 95     or al, 00000010b
 96     out 0x92, al
 97     
 98     ; 4. enter protect mode
 99     mov eax, cr0
100     or eax, 0x01
101     mov cr0, eax
102     
103     ; 5. jump to 32 bits code
104     jmp dword Code32Selector : 0
105 
106 BACK_ENTRY_SEGMENT:
107         mov ax, cs
108         mov ds, ax
109         mov es, ax
110         mov ss, ax
111         mov sp, TopOfStack16
112         
113         in al, 0x92
114         and al, 11111101b
115         out 0x92, al
116         
117         sti 
118         
119         mov bp, HELLO_WORLD
120         mov cx, 12
121         mov dx, 0
122         mov ax, 0x1301
123         mov bx, 0x0007
124         int 0x10
125         
126         jmp $
127 
128 ; esi    --> code segment label
129 ; edi    --> descriptor label
130 InitDescItem:
131     push eax
132     
133     mov eax, 0
134     mov ax, cs
135     shl eax, 4
136     add eax, esi
137     mov word [edi + 2], ax
138     shr eax, 16
139     mov byte [edi + 4], al
140     mov byte [edi + 7], ah
141     
142     pop eax
143     
144     ret
145     
146 
147 [section .16]
148 [bits 16]
149 CODE16_SEGMENT:
150     mov ax, UpdateSelector
151     mov ds, ax
152     mov es, ax
153     mov fs, ax
154     mov gs, ax
155     mov ss, ax
156     
157     mov eax, cr0
158     and al, 11111110b
159     mov cr0, eax
160     
161 BACK_TO_REAL_MODE:
162     jmp 0 : BACK_ENTRY_SEGMENT
163     
164 Code16SegLen    equ $ - CODE16_SEGMENT
165 
166     
167 [section .s32]
168 [bits 32]
169 CODE32_SEGMENT:
170     mov ax, VideoSelector
171     mov gs, ax
172     
173     mov ax, Stack32Selector
174     mov ss, ax
175     
176     mov eax, TopOfStack32
177     mov esp, eax
178     
179     mov ax, Data32Selector
180     mov ds, ax
181     
182     mov ebp, DTOS_OFFSET
183     mov bx, 0x0C
184     mov dh, 12
185     mov dl, 33
186     
187     call PrintString
188     
189     mov ebp, HELLO_WORLD_OFFSET
190     mov bx, 0x0C
191     mov dh, 13
192     mov dl, 30
193     
194     call PrintString
195     
196     jmp Code16Selector : 0
197 
198 ; ds:ebp   --> string address
199 ; bx       --> attribute
200 ; dx       --> dh : row, dl : col
201 PrintString:
202     push ebp
203     push eax
204     push edi 
205     push cx
206     push dx
207     
208 print:
209     mov cl, [ds:ebp]
210     cmp cl, 0
211     je end
212     mov eax, 80
213     mul dh
214     add al, dl
215     shl eax, 1
216     mov edi, eax
217     mov ah, bl
218     mov al, cl
219     mov [gs:edi], ax
220     inc ebp
221     inc dl
222     jmp print
223     
224 end:
225     pop dx
226     pop cx
227     pop edi
228     pop eax
229     pop ebp
230     
231     ret
232 
233 Code32SegLen    equ    $ - CODE32_SEGMENT
234 
235 [section .gs]
236 [bits 32]
237 STACK32_SEGMENT:
238     times 1014 * 4 db 0
239     
240 Stack32SegLen    equ $ - STACK32_SEGMENT
241 TopOfStack32    equ Stack32SegLen - 1

  147-164行定义了16位的保护模式代码,106-126行定义了另一个16位实模式代码段。15、16行定义了新的段描述符,15行的段描述符是描述16位保护模式的代码段的。16行的段描述符是描述16位实模式的代码段的。75-78行我们初始化了16位保护模式下的段描述符。程序从32位保护模式的196行跳转到16位保护模式的代码段,然后将16位实模式代码段的段选择子分别赋给ds、es、fs、gs、ss段寄存器(赋值的意义就是刷新对应的段描述符对应的高速缓冲存储器),赋值的同时,处理器的内部机制会读取内存,并初始化段寄存器高速缓存。这样这些寄存器高速缓存中保存的就是16位实模式代码段的信息了。然后,157-159行使处理器进入实模式。当执行162行跳转时,处理器已经处于16位实模式。注意,在16位保护模式的代码中,我们没有给cs赋值,因为这时程序还处于16位保护模式,如果这时候我们给cs赋值16位实模式,那么程序会出错,因为这时代码还在16位保护模式执行中。

  162行的跳转我们要跳到16位的实模式代码段处,这是一个段间跳转,因此使用jmp 0 : BACK_ENTRY_SEGMENT(按照16位实模式进行跳转,偏移地址是16位的,寻址范围是64kb),这里的0我们应该填入cs的值,这个值是程序执行到第50行处cs的值,这个cs的值是代表16位实模式代码的基地址,因此我们在57行加了mov [BACK_TO_REAL_MODE + 3], ax,标签BACK_TO_REAL_MODE是在161行定义的,这句代码的意思是,我们直接修改内存中的指令,使得跳转指令中的基地址变为cs的值。这样就实现了运行时动态的修改指令。因此,当第162行我们执行跳转时,jmp 0 : BACK_ENTRY_SEGMENT指令完成跳转的同时,也会改变cs的值和ip的值,程序可以正确的跳转到16位实模式 BACK_ENTRY_SEGMENT代码处,在BACK_ENTRY_SEGMENT处,cs已经处于16位实模式下正确的值。程序执行到第162行时,cs段寄存器对应的高速缓存中存储的还是16位保护模式的信息,也就是在第15行的16位保护模式代码段描述符中如果我们填入的段界限是Code16SegLen - 1,那么执行到162行时,cs高速缓存中的段界限就是这个值,而这个值比较小(因为16位保护模式的代码我们写的比较小),因此,执行第162行时,会发生越界访问异常(虽然这时候是处于实模式,但是跳转时段界限依旧起作用),因为BACK_ENTRY_SEGMENT一般是大于Code16SegLen - 1那个值的,因此,15行中的段界限我们要按16位实模式的段界限64k来填,也就是0xFFFF。

  最终进入到16位实模式代码段BACK_ENTRY_SEGMENT处,在这个段中,cs的值已经是16位实模式代码段的地址了,将cs的值一次赋值给其他的段寄存器,在这里已经进入了16位实模式代码段,对段寄存器的赋值只会改变相应高速缓冲存储器中的段基址,段界限和段属性沿用UPDATE_DESC中定义的值。然后设置栈,然后打开A20地址线,然后开中断,最后执行一个打印。

 

 执行结果如下:

posted on 2018-08-12 12:43  周伯通789  阅读(570)  评论(0编辑  收藏  举报