(第三章 12)中断

 

一、中断和8259A中断控制器

    1. 中断的基本概念

      中断 :在计算机科学中,中断是指由于接收到来自外围硬件(相对于中央处理器和内存)的异步信号或来自软件的同步信号,而进行相应的硬件/软件处理。发出这样的信号称为进行中断请求(interrupt request,IRQ)。硬件中断导致处理器通过一个上下文切换(context switch)来保存执行状态(以程序计数器和程序状态字等寄存器信息为主);软件中断则通常作为CPU指令集中的一个指令,以可编程的方式直接指示这种上下文切换,并将处理导向一段中断处理代码。中断在计算机多任务处理,尤其是实时系统中尤为有用。这样的系统,包括运行于其上的操作系统,也被称为“中断驱动的”(interrupt-driven)。
      中断是用以提高计算机工作效率、增强计算机功能的一项重要技术。最初引入硬件中断,只是出于性能上的考量。如果计算机系统没有中断,则处理器与外部设备通信时,它必须在向该设备发出指令后进行忙等待(Busy waiting),反复轮询该设备是否完成了动作并返回结果。这就造成了大量处理器周期被浪费。引入中断以后,当处理器发出设备请求后就可以立即返回以处理其他任务,而当设备完成动作后,发送中断信号给处理器,后者就可以再回过头获取处理结果。这样,在设备进行处理的周期内,处理器可以执行其他一些有意义的工作,而只付出一些很小的、切换上下文所引发的时间代价。后来被用于CPU外部与内部紧急事件的处理、机器故障的处理、时间控制等多个方面,并产生通过软件方式进入中断处理(软中断)的概念。
      在硬件实现上,中断可以是一个包含控制线路的独立系统,也可以被整合进存储器子系统中。对于前者,在IBM个人机上,广泛使用可编程中断控制器(Programmable Interrupt Controller,PIC)来负责中断响应和处理。PIC被连接在若干中断请求设备和处理器的中断引脚之间,从而实现对处理器中断请求线路(多为一针或两针)的复用。作为另一种中断实现的形式,即存储器子系统实现方式,可以将中断端口映射到存储器的地址空间,这样对特定存储器地址的访问实际上是中断请求。

中断可分为如下几种

    (1) 硬件中断
    可屏蔽中断 (maskable interrupt)。硬件中断的一类,可通过在中断屏蔽寄存器中设定位掩码来关闭。
    非可屏蔽中断 (non-maskable interrupt,NMI)。硬件中断的一类,无法通过在中断屏蔽寄存器中设定位掩码来关闭。典型例子是时钟中断 (一个硬件时钟以恒定频率—如100Hz即10ms一次发出的中断)。
    处理器间中断 (interprocessor interrupt)。一种特殊的硬件中断。由处理器发出,被其它处理器接收。仅见于多处理器系统,以便于处理器间通信或同步。

    伪中断 (spurious interrupt)。一类不希望被产生的硬件中断。发生的原因有很多种,如中断线路上电气信号异常,或是中断请求设备本身有问题。

 

     注: 硬件中断又分为内部中断和外部中断两类

外部中断一般是指由计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断等。外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备 的中断请求。
内部中断是指因硬件出错(如突然掉电、奇偶校验错等)或运算出错(除数为零、运算 溢出、单步中断等)所引起的中断。内部中断是不可屏蔽的中断。

 

    (2) 软件中断

    软件中断。是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。

    处理器通常含有一个内部中断屏蔽位 (Sam: 在处理器中),并允许通过软件来设定。一旦被设定,所有外部中断都将被系统忽略。这个屏蔽位的存取速度显然快于中断控制器上的中断屏蔽寄存器 (Sam: 在中断控制器上),因此可提供更快速地中断屏蔽控制。
    中断尽管可以提高计算机处理性能,但过于密集的中断请求/响应反而会影响系统性能。这类情形被称作中断风暴 (interrupt storm)。

 

    2. 实例:8086A的中断系统
    8086A处理器是INTEL公司于20世纪70年代末推出的一款16位处理器。该处理器在中断处理上有如下特色,这些特色也为后续INTEL处理器所共有。

 

引脚NMI和INTR
    8086A提供两个中断引脚:第17引脚NMI,和第18引脚INTR。前者用于接收非可屏蔽中断,后者则接收可屏蔽中断。通常,INTR引脚与中断控制器(如8259A)相连,后者再分别与各设备的中断请求引脚连接。除了INTR外,8086A还将自身的16位地址总线(配合M/IO引脚并通过译码器)及8位数据总线与8259A连接,并将INTA引脚与8259A的同名引脚相连。

 


(第三章 12)中断_第1张图片
图1、8259A的内部结构及外部引脚

 


(第三章 12)中断_第2张图片


图2、中断控制器8259A引脚

 


(第三章 12)中断_第3张图片


图3、IBM PC/AT  中的 2片8259A 的连接结构

 

    图“2片8259A 的连接结构”中的引脚,说明如下:
INTA:中断响应输入引脚,接8086CPU的INTA
INT:中断请求输出引脚,接8086CPU的INTR
IR7~IR0: 外部中断源输入引脚
SP/EN:级联控制线
CAS0~CAS2:级联选择线

    8259A其他的引脚说明如下:
CS:片选控制线
WR、RD:读写控制线
D7~D0:8位数据传送线,输出中断类型码到CPU
 


中断向量表和中断描述符表(IDT)
    在存储器地址空间中,规定最低的1K空间,即00000H到003FFH为中断向量表。全表共含256个中断向量,每个向量的长度为4字节,包含中断处理程序的起始地址。共有从0到255共256个中断类型码,每个中断类型码对应的中断向量所在地址为该类型码乘以4。举例而言,如果中断类型码为1,则对应中断向量所在地址为00004H;如果中断类型码为33,则对应中断向量所在地址为00084H。这样,如果已知一个中断类型码,则需要通过两次地址转换(中断类型码到中断向量表地址;中断向量表地址到中断处理程序地址)才能到达中断处理程序。另外应注意每一个中断向量所包含的地址是以低二字节存储偏移量,高二字节存储段地址的形式存储目标地址值的。
    在全部256个中断中,前32个(0—31)为硬件系统所预留。后224个可由用户设定。在初始化8259A时,可设定其上各中断引脚(共8条)对应的中断类型码。同时,将对应此中断之处理程序的起始地址保存在该中断类型码乘4的地址位中,作为中断向量。
    在INTEL后续的32位CPU中,使用中断描述符表来代替中断向量表。中断描述符表的起始地址由中断描述符表寄存器(IDTR)来定位,因此不再限于底部1K位置。另一方面,中断描述符表的每一个项目——称作门描述符——除了含有中断处理程序地址信息外,还包括许多属性/类型位。门描述符分为三类:任务门、中断门和自陷门。CPU对不同的门有不同的调用(处理)方式。
中断处理过程
    在实际运行中,一旦设备通过某引脚N向8259A发出中断指令,后者便向8086A的INTR引脚发送中断信号。8086A通过INTA引脚通知8259A中断有效(这个过程实际上还包括对此8259A的选址),后者即通过地址总线将对应引脚N的中断类型码(已预先存好,见上节)发送给CPU。CPU得到中断类型码后,先进行现场保护,主要包括:
    状态寄存器FLAGS压栈(同时堆栈寄存器SP-2);
    关闭中断(将FLAGS寄存器的IF位置零);
    将当前代码段寄存器CS和程序计数器IP压栈(同时堆栈寄存器SP-4)。


    现场保护完成后,CPU开始按照前述的两步骤翻译中断程序入口地址。在得到中断处理程序地址之后但调用中断处理程序之前,CPU会再检查一下NMI引脚是否有信号,以防在刚才的处理过程中忽略了可能的NMI中断。NMI的优先级始终高于INTR。

    注:INTR接收可屏蔽中断,NMI接收非可屏蔽中断。他们都是硬件中断。


    中断处理程序虽然是由程序员编写,但须循一定规范。作为例程,中断处理程序应该先将各寄存器信息(除了IP和CS,此二寄存器现已指向当前中断程序)压入堆栈予以保存,这样才能在中断处理程序内部使用这些寄存器。在程序结束时,应该按与压栈保护时相反的顺序弹出各寄存器的值。中断程序的最后一句始终是IRET指令,这条指令将栈顶6个字节分别弹出并存入IP、CS和FLAGS寄存器,完成了现场的还原。
    当然,如果是操作系统的中断处理程序,则未必——通常不会——还原中断前的状态。这样的中断处理程序通常会在调用完寄存器保存例程后,调用进程调度程序(多由高级语言编写),并决定下一个运行的进程。随后将此进程的寄存器信息(上次中断时保存下来的)存入寄存器并返回。在中断程序结束之后,主程序也发生了改变。

 

一些与中断控制相关的指令包括:
    CLI 关闭中断(实为将FLAGS的IF位置0)。
    STI 开启中断(实为将FLAGS的IF位置1)。
    INT n 调用中断子程序,n为中断类型码。DOS系统有一个系统调用API即为INT 21H。
    INT0 先判别FLAGS的OF位是否为1,如是则直接调用类型为4的中断子程序,用以处理溢出中断。
    IRET 将栈顶6个字节分别弹出并存入IP、CS和FLAGS寄存器,用以返回中断现场。

 

二、中断编程

    通过几个问答来理解这段程序:

question 1:
    如何将“系统的时钟中断”和我自己编写中断处理程序“ClockHandler”联系起来的?

answer 1:
    (1)设置IF=1(sti),并且将中断屏蔽寄存器IMR对应IR0的位置设置为0(向01h端口写入1111 1110b) ,当系统的时钟中断时就可以顺利地完成请求和响应。
    (2)Init8259A中将主片IR0端口对应到了中断向量号0x20,因此也就对应到了第0x20个中断处理程序
    (3)中断向量和中断处理程序的对应关系就是IDT(自己写的),代码如下:
[SECTION .idt]
ALIGN    32
[BITS    32]
LABEL_IDT:
%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        ; 基地址

    (4)中断处理程序ClockHandler也是自己写的,代码如下:
_ClockHandler:
ClockHandler    equ    _ClockHandler - $$
    inc    byte [gs:((80 * 0 + 70) * 2)]    ; 屏幕第 0 行, 第 70 列。
    mov    al, 20h
    out    20h, al                ; 发送 EOI
    iretd

    这样,就将串成一条线了:时钟信号IR0 -> 中断向量号0x20 -> 查询IDT找到对应中断处理程序入口ClockHandler -> 跳到ClockHandler开始执行。
    注:时钟中断是一个外部中断(硬件中断)

 

question 2:
    为什么在进入保护模式前先要保存实模式下的寄存器IDTR和IMR的值,然后再保护模式返回实模式时要回复原来实模式下她们的值,如下

    sidt    [_SavedIDTR]

    in    al, 21h
    mov    [_SavedIMREG], al
    ...
    lidt    [_SavedIDTR]

    mov    al, [_SavedIMREG]
    out    21h, al

    并且,在保护模式下使用中断时要这样做:
    call Init8259A
   
    int 080h
    sti ;响应时钟中断
    jmp $
    ...
    call SetRealmode8259A

answer 2:
    书P95有解释,但是我还是没有怎么看懂...(???)

 

*********************************************************************************************************************************

代码如下:

 

chapter3/i/pmtest9.asm

 

%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寄存器的内容保存到[_SavedIDTR]
    sidt    [_SavedIDTR]

    ; 保存中断屏蔽寄存器(IMREG)值,中断屏蔽寄存器(IMR)的I/O端口地址是21H
    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

 

    ; 初始化8259A:

    ; (1) 将两片8259A中断控制器级联起来

    ; (2) 将主片“IRQ0~IRQ7请求线”对应到“中断向量号20h~27h”

    ; (3) 将从片“IRQ8~IRQ15请求线”对应到“中断向量号28h~2Fh”
    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

 

    ; 下面屏蔽主/从8259a中断的两段代码实际上是写入了中断屏蔽寄存器IMR(Interrupt Mask Register)中。

    ; 如上面图中所示,“IR0”对应的是“时钟信号”,因此时钟信号被打开
    ;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]

 

 

你可能感兴趣的:((第三章 12)中断)