标签:
这节讲了中断与异常的一些基本概念。然后通过代码实现一个显示字符的中断和时钟中断。
保护模式下的中断与实模式下的中断有几点不同。
这里面出现了一个新的名词IDT,接下来就介绍什么是IDT。
与GDT和LDT一样,IDT也是一个描述符表,IDT的描述符可以是下面三种之一:
IDT作用可以用下面的图来描述:
上图是指令int n产生中断时的情形。n即为向量号,它类似于调用门的使用。
从上图可以看出,IDT的作用是将每一个中断向量和一个中断描述符对应起来。虽然形式上跟实模式下的向量表非常不同,但是从某种意义上来说,IDT也是一种向量表。
接下来看看上面的那几个门。中断门和陷阱门是特殊的调用门。他们的作用机理基本相同。只是调用门使用call指令,而中断门和陷阱门使用的是int指令。任务门这里不做描述,并不是每个操作系统都有任务门,Linux系统就没有。
接下来看看IDT中的中断门与陷阱门的结构:
中断门和陷阱门的结构如下:
保留位:与调用们相比,中断门和陷阱门结构中的BYTE4中的低5位为保留位,而不是调用门中的Param Count
S 位:指明描述符是数据段/代码段描述符(S=1)还是系统段/门描述符(S=0)。这里S=0
P 位 存在(Present)位。P=1表示段在内存中存在;P=0表示段在内存中不存在。
D P L 描述符特权级(Descriptor Privilege Level)。特权级可以是0、1、2或者3。数字越小特权级越大。
TYPE的4位:为0xE(中断门)或0xF(陷阱门)。
其他的一些书上3.1.4有详细介绍。接下来介绍中断与异常。
中断与异常通常在一起讨论。实际上,他们都是程序执行过程中的强制性转移,转移到相应的处理程序。
中断通常在程序执行时因为硬件而随机发生,它们通常是用来处理处理器外部的事件,如外围设备的请求。软件通过执行int n指令也可以产生中断。
再来看看异常,异常通常是在处理器执行指令时检查到错误时发生,比如遇到零除的情况。处理器检测的错误条件有很多,比如保护违例,页错误等。
不管中断还是异常,通俗来讲,都是软件或者硬件发生了某种情形而通知处理器的行为。于是,由此引出两个问题:一是处理器可以对何种类型的通知做出反应;二是当接到某种通知时做出何种处理。
第二个问题是通过中断向量解决的,中断向量对应着中断处理程序。
对于第一个问题,处理器能处理的中断和异常如下图所示:
Fault、Trap和Abort是异常的三种类型,它们的具体解释如下:
上面说的是异常,接下来看看中断:
中断产生的原因有两种,一种是外部中断,也就是由硬件产生的中断,另一种是由指令int n产生的中断。
指令int n产生中断时的情形上面已经说过了。下面来看外部中断。
外部中断需要建立硬件中断与向量号之间的对应关系。外部中断分为不可屏蔽中断(NMI)和可屏蔽中断两种,分别由CPU的两根引脚NMI和INTR来接收。如下图所示:
NMI不可屏蔽,因为它与IF是否被设置无关。NMI中断对应的中断向量号为2,这在上面处理器能处理的中断和异常的表中已经有所说明。
可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的。你可以认为它是中断机制中所有外围设备的一个代理,这个代理不但可以根据优先级在同时发生中断的设备中选择应该处理的请求,而且可以通过对其寄存器的设置来屏蔽或打开相应的中断。
由上图我们知道,与CPU相连的是两片级联的8259A,每个8259A有8根中断信号线,于是两片级联总共可以挂接15个不同的外部设备。那么,这些设备发出的中断请求如何与中断向量对应起来呢?就是通过对8259A的设置完成的。在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,而通过表我们知道,在保护模式下向量号08h-0Fh已经被占用了,所以我们不得不重新设置主从8259A。
还好,8259A是可编程中断控制器,对它的设置并不复杂,是通过向相应的端口写入特定的ICW(Initialization Command Word)来实现的。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。ICW共有4个,每一个都是具有特定格式的字节。为了先对初始化8259A的过程有一个概括的了解,我们过一会儿再来关注每一个ICW的格式,现在,先来看一下初始化过程:
这四步的顺序是不能颠倒的。
接下来看看ICW的格式:
能够看到在写入ICW2涉及与中断向量号的对应,这就是问题的所在了。
所以接下来就来开启中断的实验。所做的工作主要就是两个:设置8259A和建立IDT。
283 Init8259A:
284 mov al, 011h
285 out 020h, al ; 主8259, ICW1.
286 call io_delay
287
288 out 0A0h, al ; 从8259, ICW1.
289 call io_delay
290
291 mov al, 020h ; IRQ0 对应中断向量 0x20
292 out 021h, al ; 主8259, ICW2.
293 call io_delay
294
295 mov al, 028h ; IRQ8 对应中断向量 0x28
296 out 0A1h, al ; 从8259, ICW2.
297 call io_delay
298
299 mov al, 004h ; IR2 对应从8259
300 out 021h, al ; 主8259, ICW3.
301 call io_delay
302
303 mov al, 002h ; 对应主8259的 IR2
304 out 0A1h, al ; 从8259, ICW3.
305 call io_delay
306
307 mov al, 001h
308 out 021h, al ; 主8259, ICW4.
309 call io_delay
310
311 out 0A1h, al ; 从8259, ICW4.
312 call io_delay
313
314 mov al, 11111110b ; 仅仅开启定时器中断
315 ;mov al, 11111111b ; 屏蔽主8259所有中断
316 out 021h, al ; 主8259, OCW1.
317 call io_delay
318
319 mov al, 11111111b ; 屏蔽从8259所有中断
320 out 0A1h, al ; 从8259, OCW1.
321 call io_delay
322
323 ret
324 ; Init8259A ----------------------------------------------------
这段代码分别往主、从两个8259A各写入了4个ICW。在往主8259A写入ICW2时,我们看到IRQ0对应了中断向量号20h,于是,IRQ0~IRQ7就对应中断向量20h~27h;类似地,IRQ8~IRQ15对应中断向量28h~2Fh。20h~2Fh处于用户定义中断的范围内。
在这段代码的后半部分,我们通过对端口21h和A1h的操作屏蔽了所有的外部中断,这一次写入的不再是ICW了,而是OCW(Operation Control Word)。OCW共有3个,OCW1、OCW2和OCW3。由于我们只在两种情况下用到它,因此并不需要了解所有的内容。这两种情况是:
若想屏蔽或打开外部中断,只需要往8259A写入OCW1就可以了,OCW1的格式如下:
可见,若想屏蔽某一个中断,将对应那一位设成1就可以了。实际上,OCW1是被写入了中断屏蔽寄存器(IMR,全称Interrupt Mask Register)中,当一个中断到达,IMR会判断此中断是否应被丢弃。
对于EOI。当每一次中断处理结束,需要发送一个EOI给8259A,以便继续接收中断。而发送EOI是通过往端口20h或A0h写OCW2来实现的。OCW2的格式如下图所示。
发送EOI给8295A可由如下代码完成:
mov al, 20h
out 20h或A0h, al
对于EOI的其他各位,暂时不关注。
对于初始化8259A的代码中。每一次I/O操作之后都调用了一个延迟函数io_delay以等待操作的完成。函数io_delay很简单,调用了4个nop指令
351 io_delay:
352 nop
353 nop
354 nop
355 nop
356 ret
在相应的位置添加调用Init8259A的指令之后,对8259A的操作就结束了,我们下面就来建立一个IDT。
为了操作方便,我们把IDT放进一个单独的段中
96 ; IDT
97 [SECTION .idt]
98 ALIGN 32
99 [BITS 32]
100 LABEL_IDT:
101 ; 门 目标选择子, 偏移, DCount, 属性
102 %rep 255
103 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
104 %endrep
105
106 IdtLen equ $ - LABEL_IDT
107 IdtPtr dw IdtLen - 1 ; 段界限
108 dd 0 ; 基地址
109 ; END of [SECTION .idt]
看得出,这个IDT真的是不能再简单了,全部的255个描述符完全相同。这里利用了NASM的%rep预处理指令,将每一个描述符都设置为指向SelectorCode32:SpuriousHandler的中断门。SpuriousHandler也很简单,在屏幕的右上角打印红色的字符“!”,然后进入死循环。代码如下:
358 _SpuriousHandler:
359 SpuriousHandler equ _SpuriousHandler - $$
360 mov ah, 0Ch ; 0000: 黑底 1100: 红字
361 mov al, ‘!‘
362 mov [gs:((80 * 0 + 75) * 2)], ax ; 屏幕第 0 行, 第 75 列。
363 jmp $
364 iretd
接下来加载IDT,与加载GDT的代码很相似:
201 ; 为加载 IDTR 作准备
202 xor eax, eax
203 mov ax, ds
204 shl eax, 4
205 add eax, LABEL_IDT ; eax <- idt 基地址
206 mov dword [IdtPtr + 2], eax ; [IdtPtr + 2] <- idt 基地址
...
211 ; 关中断
212 cli
213
214 ; 加载 IDTR
215 lidt [IdtPtr]
在执行lidt之前用cli指令清IF位,暂时不响应可屏蔽中断。
其实,到这里为止,我们的中断机制已经初始化完毕了,不过此时运行的话,你会发现程序无法正常回到实模式。因为IDTR以及8259A等内容已经被我们改变,要想顺利跳回实模式还要将它们恢复原样才行。
不过,即便添加了回到实模式的代码,我们仍然看不出任何效果。虽然我们已经完成了保护模式下中断异常处理机制的初始化,但并没有利用中断来做任何事。下面就继续修改代码。
接下来通过int n实现一个中断,代码如下:
265 call Init8259A
266 int 080h
由于IDT中所有的描述符都指向SelectorCode32:SpuriousHandler处。SelectorCode32:SpuriousHandler处的代码如下:
361 _SpuriousHandler:
362 SpuriousHandler equ _SpuriousHandler - $$
363 mov ah, 0Ch ; 0000: 黑底 1100: 红字
364 mov al, ‘!‘
365 mov [gs:((80 * 0 + 75) * 2)], ax ; 屏幕第 0 行, 第 75 列。
366 jmp $
367 iretd
所以,无论我们添加的代码调用几号中断,都应该在屏幕的右上角打印出红色的字符。运行一下,你会看到,屏幕右上角出现红色的“!”,并且程序进入死循环。结果图如下:
接下来,让程序变得优雅一些:
修改一下IDT,把第80h号中断单独列出来,并新增加一个函数来处理这个断:UserIntHandler。UserIntHandler与SpuriousHandler类似,只是在函数末尾通过iretd指令返回,而不是进入死循环。代码如下:
96 ; IDT
97 [SECTION .idt]
98 ALIGN 32
99 [BITS 32]
100 LABEL_IDT:
101 ; 门 目标选择子, 偏移, DCount, 属性
102 %rep 128
103 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
104 %endrep
105 .080h: Gate SelectorCode32, UserIntHandler, 0, DA_386IGate
106
107 IdtLen equ $ - LABEL_IDT
108 IdtPtr dw IdtLen - 1 ; 段界限
109 dd 0 ; 基地址
110 ; END of [SECTION .idt]
...
266 call Init8259A
267 int 080h
268 jmp $
...
363 _UserIntHandler:
364 UserIntHandler equ _UserIntHandler - $$
365 mov ah, 0Ch ; 0000: 黑底 1100: 红字
366 mov al, ‘I‘
367 mov [gs:((80 * 0 + 70) * 2)], ax ; 屏幕第 0 行, 第 70 列。
368 iretd
结果图如下:
因为在代码268行有jmp $
,所以现在还是进入死循环,但这次的死循环与上面的有所不同,这次已经从中断处理函数中返回了。
上面的过程实现了一个中断。使用起来欲调用门差不多。而且没有用到8259A。下面来一点新鲜的体验:打开时钟中断(IRQ0)。
我们提到过,可屏蔽中断与NMI的区别在于是否受到IF位的影响,而8259A的中断屏蔽寄存器(IMR)也影响着中断是否会被响应。所以,外部可屏蔽中断的发生就受到两个因素的影响,只有当IF位为1,并且IMR相应位为0时才会发生。
那么,如果我们想打开时钟中断的话,一方面不仅要设计一个中断处理程序,另一方面还要设置IMR,并且设置IF位。设置IMR可以通过写OCW2来完成,而设置IF可以通过指令sti来完成。
接下来写一个最简单的时钟中断处理程序
387 _ClockHandler:
388 ClockHandler equ _ClockHandler - $$
389 inc byte [gs:((80 * 0 + 70) * 2)] ; 屏幕第 0 行, 第 70 列。
390 mov al, 20h
391 out 20h, al ; 发送 EOI
392 iretd
这段代码的功能很简单:把屏幕第0行第70列的字符增一,变成ASCII码表中位于它后面的字符。然后发送EOI并iretd返回。如果我们调用80h号中断之后打开中断的话,由于第0行第70列已经被写入字符I
,所以第一次中断发生时哪里会变成字符J
,再一次中断则变成K
。每次中断,字符就会变化一次,所以能够看到不断变化的字符。
时钟中断处理函数已经写好了。接下来修改初始化8259A的代码,使时钟中断不再被屏蔽。代码如下:
342 ;mov al, 11111111b ; 屏蔽主8259所有中断
343 mov al, 11111110b ; 仅仅开启定时器中断
344 out 021h, al ; 主8259, OCW1.
345 call io_delay
346
347 mov al, 11111111b ; 屏蔽从8259所有中断
348 out 0A1h, al ; 从8259, OCW1.
349 call io_delay
350
351 ret
然后把IDT修改成如下的样子:
101 ; IDT
102 [SECTION .idt]
103 ALIGN 32
104 [BITS 32]
105 LABEL_IDT:
106 ; 门 目标选择子, 偏移, DCount, 属性
107 %rep 32
108 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
109 %endrep
110 .020h: Gate SelectorCode32, ClockHandler, 0, DA_386IGate
111 %rep 95
112 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
113 %endrep
114 .080h: Gate SelectorCode32, UserIntHandler, 0, DA_386IGate
115
116 IdtLen equ $ - LABEL_IDT
117 IdtPtr dw IdtLen - 1 ; 段界限
118 dd 0 ; 基地址
119 ; END of [SECTION .idt]
按理说,现在在调用80h号中断之后执行sti来打开中断,效果就应该可以看到了。可是有一个问题:程序马上会继续执行,可能没等第一个中断发生程序已经执行完并退出了。所以,我们需要让程序停留在某个地方,干脆让它死循环吧,这样虽然不雅,却简单易行:
288 int 080h
289 sti
290 jmp $
接下来运行,查看效果,能够发现右上角字符在跳动。这儿截图某一个瞬间如下:
接下来是程序的完整源代码
; ==========================================
; pmtest9.asm
; 编译方法:nasm pmtest9.asm -o pmtest9.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
PageDirBase0 equ 200000h ; 页目录开始地址: 2M
PageTblBase0 equ 201000h ; 页表开始地址: 2M + 4K
PageDirBase1 equ 210000h ; 页目录开始地址: 2M + 64K
PageTblBase1 equ 211000h ; 页表开始地址: 2M + 64K + 4K
LinearAddrDemo equ 00401000h
ProcFoo equ 00401000h
ProcBar equ 00501000h
ProcPagingDemo equ 00301000h
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K; 0 ~ 4G
LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_LIMIT_4K ; 0 ~ 4G
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_CR | 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_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
SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT
SelectorFlatRW equ LABEL_DESC_FLAT_RW - 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
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
; 实模式下使用这些符号
; 字符串
_szPMMessage: db "In Protect Mode now. ^-^", 0Ah, 0Ah, 0 ; 进入保护模式后显示此字符串
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 ; 进入保护模式后显示此字符串
_szRAMSize db "RAM size:", 0
_szReturn db 0Ah, 0
; 变量
_wSPValueInRealMode dw 0
_dwMCRNumber: dd 0 ; Memory Check Result
_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_dwMemSize: dd 0
_ARDStruct: ; Address Range Descriptor Structure
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
_PageTableNumber: dd 0
_SavedIDTR: dd 0 ; 用于保存 IDTR
dd 0
_SavedIMREG: db 0 ; 中断屏蔽寄存器值
_MemChkBuf: times 256 db 0
; 保护模式下使用这些符号
szPMMessage equ _szPMMessage - $$
szMemChkTitle equ _szMemChkTitle - $$
szRAMSize equ _szRAMSize - $$
szReturn equ _szReturn - $$
dwDispPos equ _dwDispPos - $$
dwMemSize equ _dwMemSize - $$
dwMCRNumber equ _dwMCRNumber - $$
ARDStruct equ _ARDStruct - $$
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
MemChkBuf equ _MemChkBuf - $$
SavedIDTR equ _SavedIDTR - $$
SavedIMREG equ _SavedIMREG - $$
PageTableNumber equ _PageTableNumber- $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; IDT
[SECTION .idt]
ALIGN 32
[BITS 32]
LABEL_IDT:
; 门 目标选择子, 偏移, DCount, 属性
%rep 32
Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
%endrep
.020h: Gate SelectorCode32, ClockHandler, 0, DA_386IGate
%rep 95
Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
%endrep
.080h: Gate SelectorCode32, UserIntHandler, 0, DA_386IGate
IdtLen equ $ - LABEL_IDT
IdtPtr dw IdtLen - 1 ; 段界限
dd 0 ; 基地址
; END of [SECTION .idt]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
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
mov [_wSPValueInRealMode], sp
; 得到内存数
mov ebx, 0
mov di, _MemChkBuf
.loop:
mov eax, 0E820h
mov ecx, 20
mov edx, 0534D4150h
int 15h
jc LABEL_MEM_CHK_FAIL
add di, 20
inc dword [_dwMCRNumber]
cmp ebx, 0
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:
; 初始化 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 基地址
; 为加载 IDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_IDT ; eax <- idt 基地址
mov dword [IdtPtr + 2], eax ; [IdtPtr + 2] <- idt 基地址
; 保存 IDTR
sidt [_SavedIDTR]
; 保存中断屏蔽寄存器(IMREG)值
in al, 21h
mov [_SavedIMREG], al
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
;cli
; 加载 IDTR
lidt [IdtPtr]
; 打开地址线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
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [_wSPValueInRealMode]
lidt [_SavedIDTR] ; 恢复 IDTR 的原值
mov al, [_SavedIMREG] ; ┓恢复中断屏蔽寄存器(IMREG)的原值
out 21h, al ; ┛
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 es, ax
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
call Init8259A
int 080h
sti
jmp $
; 下面显示一个字符串
push szPMMessage
call DispStr
add esp, 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ; 显示内存信息
call PagingDemo ; 演示改变页目录的效果
call SetRealmode8259A
; 到此停止
jmp SelectorCode16:0
; Init8259A ----------------------------------------------
Init8259A:
mov al, 011h
out 020h, al ; 主8259, ICW1.
call io_delay
out 0A0h, al ; 从8259, ICW1.
call io_delay
mov al, 020h ; IRQ0 对应中断向量 0x20
out 021h, al ; 主8259, ICW2.
call io_delay
mov al, 028h ; IRQ8 对应中断向量 0x28
out 0A1h, al ; 从8259, ICW2.
call io_delay
mov al, 004h ; IR2 对应从8259
out 021h, al ; 主8259, ICW3.
call io_delay
mov al, 002h ; 对应主8259的 IR2
out 0A1h, al ; 从8259, ICW3.
call io_delay
mov al, 001h
out 021h, al ; 主8259, ICW4.
call io_delay
out 0A1h, al ; 从8259, ICW4.
call io_delay
;mov al, 11111111b ; 屏蔽主8259所有中断
mov al, 11111110b ; 仅仅开启定时器中断
out 021h, al ; 主8259, OCW1.
call io_delay
mov al, 11111111b ; 屏蔽从8259所有中断
out 0A1h, al ; 从8259, OCW1.
call io_delay
ret
; Init8259A ---------------------------------------------
; SetRealmode8259A --------------------------------------
SetRealmode8259A:
mov ax, SelectorData
mov fs, ax
mov al, 017h
out 020h, al ; 主8259, ICW1.
call io_delay
mov al, 008h ; IRQ0 对应中断向量 0x8
out 021h, al ; 主8259, ICW2.
call io_delay
mov al, 001h
out 021h, al ; 主8259, ICW4.
call io_delay
mov al, [fs:SavedIMREG] ; ┓恢复中断屏蔽寄存器(IMREG)的原值
out 021h, al ; ┛
call io_delay
ret
; SetRealmode8259A -----------------------------------
io_delay:
nop
nop
nop
nop
ret
; int handler ----------------------------------------
_ClockHandler:
ClockHandler equ _ClockHandler - $$
inc byte [gs:((80 * 0 + 70) * 2)] ; 屏幕第 0 行, 第 70 列。
mov al, 20h
out 20h, al ; 发送 EOI
iretd
_UserIntHandler:
UserIntHandler equ _UserIntHandler - $$
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, ‘I‘
mov [gs:((80 * 0 + 70) * 2)], ax ; 屏幕第 0 行, 第 70 列。
iretd
_SpuriousHandler:
SpuriousHandler equ _SpuriousHandler - $$
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, ‘!‘
mov [gs:((80 * 0 + 75) * 2)], ax ; 屏幕第 0 行, 第 75 列。
jmp $
iretd
; ---------------------------------------------------
; 启动分页机制 ----------------------------------------
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
div ebx
mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
test edx, edx
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加一个页表
.no_remainder:
mov [PageTableNumber], ecx ; 暂存页表个数
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 首先初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase0 ; 此段首地址为 PageDirBase
xor eax, eax
mov eax, PageTblBase0 | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
mov edi, PageTblBase0 ; 此段首地址为 PageTblBase
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
mov eax, PageDirBase0
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分页机制启动完毕 --------------------------------------
; 测试分页机制 -----------------------------------------
PagingDemo:
mov ax, cs
mov ds, ax
mov ax, SelectorFlatRW
mov es, ax
push LenFoo
push OffsetFoo
push ProcFoo
call MemCpy
add esp, 12
push LenBar
push OffsetBar
push ProcBar
call MemCpy
add esp, 12
push LenPagingDemoAll
push OffsetPagingDemoProc
push ProcPagingDemo
call MemCpy
add esp, 12
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov es, ax
call SetupPaging ; 启动分页
call SelectorFlatC:ProcPagingDemo
call PSwitch ; 切换页目录,改变地址映射关系
call SelectorFlatC:ProcPagingDemo
ret
; -----------------------------------------------------
; 切换页表 ---------------------------------------------
PSwitch:
; 初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase1 ; 此段首地址为 PageDirBase
xor eax, eax
mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW
mov ecx, [PageTableNumber]
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
mov edi, PageTblBase1 ; 此段首地址为 PageTblBase
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
; 在此假设内存是大于 8M 的
mov eax, LinearAddrDemo
shr eax, 22
mov ebx, 4096
mul ebx
mov ecx, eax
mov eax, LinearAddrDemo
shr eax, 12
and eax, 03FFh ; 1111111111b (10 bits)
mov ebx, 4
mul ebx
add eax, ecx
add eax, PageTblBase1
mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
mov eax, PageDirBase1
mov cr3, eax
jmp short .3
.3:
nop
ret
; ------------------------------------------------------
; PagingDemoProc ---------------------------------------
PagingDemoProc:
OffsetPagingDemoProc equ PagingDemoProc - $$
mov eax, LinearAddrDemo
call eax
retf
; ------------------------------------------------------
LenPagingDemoAll equ $ - PagingDemoProc
; ------------------------------------------------------
; foo --------------------------------------------------
foo:
OffsetFoo equ foo - $$
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, ‘F‘
mov [gs:((80 * 17 + 0) * 2)], ax ; 屏幕第 17 行, 第 0 列。
mov al, ‘o‘
mov [gs:((80 * 17 + 1) * 2)], ax ; 屏幕第 17 行, 第 1 列。
mov [gs:((80 * 17 + 2) * 2)], ax ; 屏幕第 17 行, 第 2 列。
ret
LenFoo equ $ - foo
; -------------------------------------------------------
; bar ---------------------------------------------------
bar:
OffsetBar equ bar - $$
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, ‘B‘
mov [gs:((80 * 18 + 0) * 2)], ax ; 屏幕第 18 行, 第 0 列。
mov al, ‘a‘
mov [gs:((80 * 18 + 1) * 2)], ax ; 屏幕第 18 行, 第 1 列。
mov al, ‘r‘
mov [gs:((80 * 18 + 2) * 2)], ax ; 屏幕第 18 行, 第 2 列。
ret
LenBar equ $ - bar
; -------------------------------------------------------
; 显示内存信息 --------------------------------------------
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构
.loop: ;{
mov edx, 5 ;for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员,共5个成员
mov edi, ARDStruct ; {// 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1: ;
push dword [esi] ;
call DispInt ;DispInt(MemChkBuf[j*4]); // 显示一个成员
pop eax ;
stosd ;ARDStruct[j*4] = MemChkBuf[j*4];
add esi, 4 ;
dec edx ;
cmp edx, 0 ;
jnz .1 ; }
call DispReturn ; printf("\n");
cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
jne .2 ; {
mov eax, [dwBaseAddrLow] ;
add eax, [dwLengthLow] ;
cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
jb .2 ;
mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
.2: ; }
loop .loop ;}
;
call DispReturn ;printf("\n");
push szRAMSize ;
call DispStr ;printf("RAM size:");
add esp, 4 ;
;
push dword [dwMemSize] ;
call DispInt ;DispInt(MemSize);
add esp, 4 ;
pop ecx
pop edi
pop esi
ret
; -----------------------------------------------
%include "lib.inc" ; 库函数
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
标签:
原文地址:http://blog.csdn.net/friendley/article/details/51620476