一、中断和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的同名引脚相连。
图2、中断控制器8259A引脚
图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]