码迷,mamicode.com
首页 > 其他好文 > 详细

一个操作系统的实现(9)-中断和异常

时间:2016-06-10 11:09:30      阅读:316      评论:0      收藏:0      [点我收藏+]

标签:

这节讲了中断与异常的一些基本概念。然后通过代码实现一个显示字符的中断和时钟中断。

实模式与保护模式下的中断有区别

保护模式下的中断与实模式下的中断有几点不同。

  • 实模式下的中断向量表在保护模式下被IDT取代
  • 实模式下可以使用BIOS中断,而保护模式下不能用

这里面出现了一个新的名词IDT,接下来就介绍什么是IDT。

中断描述符表(IDT,Interrupt Descriptor Table)

中断描述符表的作用

与GDT和LDT一样,IDT也是一个描述符表,IDT的描述符可以是下面三种之一:

  • 中断门描述符
  • 陷阱门描述符
  • 任务门描述符

IDT作用可以用下面的图来描述:

技术分享

上图是指令int n产生中断时的情形。n即为向量号,它类似于调用门的使用。

从上图可以看出,IDT的作用是将每一个中断向量和一个中断描述符对应起来。虽然形式上跟实模式下的向量表非常不同,但是从某种意义上来说,IDT也是一种向量表。

接下来看看上面的那几个门。中断门和陷阱门是特殊的调用门。他们的作用机理基本相同。只是调用门使用call指令,而中断门和陷阱门使用的是int指令。任务门这里不做描述,并不是每个操作系统都有任务门,Linux系统就没有。

接下来看看IDT中的中断门与陷阱门的结构:

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是异常的三种类型,它们的具体解释如下:

  • Fault是一种可被更正的异常,而且一旦被更正,程序可以不失连续性地继续执行。当一个fault发生时,处理器会把产生fault的指令之前的状态保存起来。异常处理程序的返回地址将会是产成fault的指令,而不是其后的那条指令。
  • Trap是一种在发生trap的指令执行之后立即被报告的异常,它也允许程序或任务不失连续性地继续执行。异常处理程序的返回地址将会是产成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的格式,现在,先来看一下初始化过程:

  1. 往端口20h(主片)或A0h(从片)写入ICW1。
  2. 往端口21h(主片)或A1h(从片)写入ICW2。
  3. 往端口21h(主片)或A1h(从片)写入ICW3。
  4. 往端口21h(主片)或A1h(从片)写入ICW4。

这四步的顺序是不能颠倒的。

接下来看看ICW的格式:

技术分享

能够看到在写入ICW2涉及与中断向量号的对应,这就是问题的所在了。

所以接下来就来开启中断的实验。所做的工作主要就是两个:设置8259A和建立IDT。

开启中断的实验

编程操作8259A

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。由于我们只在两种情况下用到它,因此并不需要了解所有的内容。这两种情况是:

  • 屏蔽或打开外部中断。
  • 发送EOI给8259A以通知它中断处理结束。

若想屏蔽或打开外部中断,只需要往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

为了操作方便,我们把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]

一个操作系统的实现(9)-中断和异常

标签:

原文地址:http://blog.csdn.net/friendley/article/details/51620476

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!