接下来,我们就通过实际的代码,来看看如何在程序中应用中断门和陷阱门吧
既然中断有两种方式触发 -- 硬件随机触发和 int n 手动触发,我们就编写两个函数,分别用来响应硬件中断和我们手动 int n 触发的中断
最方便的硬件中断就是时钟中断,因为他会以固定间隔自动触发中断
8254 时钟中断与 8284A 实时时钟中断的区别
在本系列第一篇文章中,我们曾经介绍过,硬件启动之初,当电源供电稳定时,会自动向级联在 CPU 上的 8284A 时钟发生器发送 PowerGood 信号,8284A 芯片会向 CPU 发送 RESET 信号
事实上,8284A 芯片作为时钟发生器,最为常用的功能就是在加电晶振的带动下产生每秒 1024 次时钟中断信号供硬件使用,因此他一般不被我们的软件所使用
而主 8259A 的 IR0 引脚上级联的 8254 时钟芯片则是通常我们所说的操作系统时钟中断的信号源,因此,在本次的程序中,我们也要使用 IRQ0 中断作为中断信号源
中断响应函数的编写
中断响应函数与普通的函数在编写上并没有很大的区别,他通常包含两部分逻辑:
- 函数处理逻辑
- 发送 EOI 信号表示中断处理完成并返回
众所周知,我们使用 ret
指令完成一个函数的调用并跳转回函数调用位置继续执行,与此类似,中断响应函数则通过 iret
与 iretd
两个指令来实现中断处理完成后的跳转工作,他们分别应用于 16 位系统与 32 位系统中
手动触发的中断响应函数
我们只要在手动触发的中断响应函数中实现一个字符串的显示,就可以证明中断正常的被触发:
DefaultMessage: db "Default Interrupt Handler", 0
OffsetDefaultMessage equ DefaultMessage - $$
_PrintDefaultText:
PrintDefaultText equ _PrintDefaultText - $$
mov eax, 0Ch ; 黑底红字,不闪烁
push eax
mov eax, 80 * 3 * 2
push eax
push OffsetDefaultMessage
call DisplayString
add esp, 12
; 发送 EOI
mov al, 20h
out 20h, al
iretd
这里我们定义了一个 dword 标签 PrintDefaultText,他用来记录当前的段偏移,从而稍后用来初始化我们的中断门描述符
自动触发的时钟中断响应函数
既然是时钟中断,我们当然要做一些周期触发的事情,最简单的就是我们通过周期性的切换两个字符串展示,来模拟字符串的闪烁功能:
BootMessage: db "Hello World my OS, techlog.cn!", 0
OffsetBootMessage equ BootMessage - $$
DispeareMessage: db " ", 0
OffsetDispeareMessage equ DispeareMessage - $$
_clockNum dd 0
clockNum equ _clockNum - $$
_PrintText:
PrintText equ _PrintText - $$
mov eax, 0Ch ; 黑底红字,不闪烁
push eax
mov eax, 80 * 2 * 2
push eax
mov eax, dword [clockNum]
mov ebx, 2
xor edx, edx
div ebx
cmp edx, 0
jne print_dispear
push OffsetBootMessage
jmp print_text
print_dispear:
push OffsetDispeareMessage
print_text:
call DisplayString
add esp, 12
add dword [clockNum], 1
; 发送 EOI
mov al, 20h
out 20h, al
iretd
下面我们就来创建中断描述符表,并添加中断描述符
中断描述符宏
和代码段数据段描述符一样,我们通过一个宏来实现描述符的定义:
; ---------------- 门描述符宏 -------------
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro
在 IDT 中周期填充中断描述符
保护模式下,中断向量号就是中断描述符表中描述符的 index,因此我们要定义我们指定向量号的中断门,就需要跳过前面 N 个描述符
你可以通过复制粘贴的方式生成大量的代码来实现,但 NASM 提供了更为简单的预处理方式 -- %rep
下面的代码实现了 inc word [table + 2*i]
语句的 100 次循环添加:
%assign i 0
%rep 100
inc word [table+2*i]
%assign i i+1
%endrep
这样我们就可以非常简单的定义 IDT 中的所有中断门了:
; -------------------- IDT ---------------------
[SECTION .idt]
ALIGN 32
[BITS 32]
LABEL_IDT:
; 目标选择子, 偏移, DCount, 属性
%rep 32
Gate SelectorCode32, PrintDefaultText, 0, 8Eh
%endrep
.020h: Gate SelectorCode32, PrintText, 0, 8Eh ; 时钟中断描述符
%rep 95
Gate SelectorCode32, PrintDefaultText, 0, 8Eh
%endrep
.080h: Gate SelectorCode32, PrintDefaultText, 0, 8Eh
; ------------------ END OF IDT ----------------
IdtLen equ $ - LABEL_IDT
IdtPtr dw IdtLen - 1 ; 段界限
dd 0 ; 段基址
上述代码中,首先通过循环 32 次,用默认的响应函数初始化了 32 个系统保留的中断向量,然后,我们将 PrintText 函数注册为了中断向量号为 20h 的中断响应函数(这里 .020h 标签的声明实际上并没有什么作用,只是为了便于理解)
虽然,通过同样的方式跳过 95 个中断门,便来到了 080h 向量号,于是我们将 PrintDefaultText 设置为对应的中断响应函数
上一篇文章中,我们已经详细介绍了可编程中断控制器 8259A 的初始化和使用方法,这里我们就可以直接使用了:
; ------------------ 初始化可编程中断控制器 ----------------------
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, 11111110b ; 仅开启定时器中断
out 021h, al ; 主8259, OCW1
call io_delay
mov al, 11111111b ; 屏蔽从8259所有中断
out 0A1h, al ; 从8259, OCW1
call io_delay
; 发送 EOI
mov al, 20h
out 20h, al
ret
io_delay:
nop
nop
nop
nop
ret
可以看到,我们把 IRQ0 中断向量指向了 020h,并且屏蔽了除此之外的其他中断
当我们的程序运行完成,我们当然是希望他能够正常的返回到实地址模式下了,因此我们有几件事要做
等待时钟中断的周期性触发
我们要观察时钟中断的效果,就要等待一段时间,上面的文章中,为了能够实现周期变换,我们定义了一个触发次数字段 clockNum,他正好可以作为等待的循环依据:
wait_loop:
cmp dword [clockNum], 4096
jb wait_loop
cli
恢复 IDTR 寄存器与 IMREG 寄存器
; 恢复 IDTR 与中断屏蔽寄存器 IMREG
lidt [_SavedIDTR]
mov al, [_SavedIMREG]
out 21h, al
恢复 8259A 设置
; ------------------ 恢复可编程中断控制器 ----------------------
SetRealmode8259A:
mov ax, SelectorData
mov fs, ax
mov al, 015h ; 4 字节中断向量
out 020h, al ; 主8259, ICW1.
call io_delay
out 0A0h, al ; 从8259, ICW1
call io_delay
mov al, 008h ; IRQ0 对应中断向量 0x8
out 021h, al ; 主8259, ICW2.
call io_delay
mov al, 0F0h ; 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, 0 ; 开启主 8259A 所有中断
out 021h, al ; 主8259, OCW1
call io_delay
mov al, 0 ; 开启从 8259A 所有中断
out 0A1h, al ; 从8259, OCW1
call io_delay
; 发送 EOI
mov al, 20h
out 20h, al
; 恢复中断屏蔽寄存器(IMREG)
mov al, [fs:SavedIMREG]
out 021h, al
call io_delay
ret
这里的代码与 8259A 的初始化代码略有不同,区别在于,16位实地址模式下,中断向量长度是4字节
另外,虽然在实地址模式的默认情况下,程序只使用主 8259A 芯片,但仍然必须设置为级联模式,而不能将级联位设置为 1,虽然我在一些书中看到,在回跳时,主 ICW1 设置为了 17h,即 single 模式,然后省去了 ICW3 以及所有从 8259A 的设置,但如果这么做,系统会触发异常:
master: ICW1: single mode not supported
lib.asm
; ------------------------------------------------------------------------
; 显示一个字节中的数字
;
; params:
; db property
; dw display_index
; db number
;
; return:
; eax display_index
; ------------------------------------------------------------------------
DisplayByteNumber:
push ebp
mov ebp, esp
push edx
push edi
push esi
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov al, [ebp + 8]
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .letter
add al, '0'
jmp .number
.letter:
sub al, 0Ah
add al, 'A'
.number:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
mov eax, edi
pop esi
pop edi
pop edx
pop ebp
ret
; ------------------------------------------------------------------------
; 显示一个整形数
;
; params:
; db property
; dw display_index
; db number
;
; return:
; eax display_index
; ------------------------------------------------------------------------
DisplayInt:
push ebp
mov ebp, esp
push edx
push edi
push esi
push ebx
push ecx
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov ecx, 4
mov ebx, 24
push eax
.loop:
mov eax, [ebp + 8]
shr eax, 4
push edi
push eax
call DisplayByteNumber
add esp, 8
add edi, 2
sub ebx, 8
loop .loop
mov al, 'h'
push edi
mov [gs:edi], ax
add edi, 4
mov eax, edi
pop ecx
pop ebx
pop esi
pop edi
pop edx
pop ebp
ret
; ------------------------------------------------------------------------
; 显示一个字符串
;
; params:
; db property
; dw display_index
; dw string_offset
; ------------------------------------------------------------------------
DisplayString:
push ebp
mov ebp, esp
push eax
push edi
push esi
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov esi, [ebp + 8]
cld
.loop_label:
lodsb
test al, al
jz .over_print
mov [gs:edi], ax
add edi, 2
jmp .loop_label
.over_print:
pop esi
pop edi
pop eax
pop ebp
ret
; ------------------------------------------------------------------------
; 显示 AL 中的数字
; ------------------------------------------------------------------------
DispAL:
push ecx
push edx
push edi
mov edi, [dwDispPos]
mov ah, 0Fh ; 0000b: 黑底 1111b: 白字
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .1
add al, '0'
jmp .2
.1:
sub al, 0Ah
add al, 'A'
.2:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
;add edi, 2
mov [dwDispPos], edi
pop edi
pop edx
pop ecx
ret
; DispAL 结束-------------------------------------------------------------
; ------------------------------------------------------------------------
; 显示一个整形数
; ------------------------------------------------------------------------
DispInt:
mov eax, [esp + 4]
shr eax, 24
call DispAL
mov eax, [esp + 4]
shr eax, 16
call DispAL
mov eax, [esp + 4]
shr eax, 8
call DispAL
mov eax, [esp + 4]
call DispAL
mov ah, 07h ; 0000b: 黑底 0111b: 灰字
mov al, 'h'
push edi
mov edi, [dwDispPos]
mov [gs:edi], ax
add edi, 4
mov [dwDispPos], edi
pop edi
ret
; DispInt 结束------------------------------------------------------------
; ------------------------------------------------------------------------
; 显示一个字符串
; ------------------------------------------------------------------------
DispStr:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov esi, [ebp + 8] ; pszInfo
mov edi, [dwDispPos]
mov ah, 0Fh
.1:
lodsb
test al, al
jz .2
cmp al, 0Ah ; 是回车吗?
jnz .3
push eax
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop eax
jmp .1
.3:
mov [gs:edi], ax
add edi, 2
jmp .1
.2:
mov [dwDispPos], edi
pop edi
pop esi
pop ebx
pop ebp
ret
; DispStr 结束------------------------------------------------------------
; ------------------------------------------------------------------------
; 换行
; ------------------------------------------------------------------------
DispReturn:
push szReturn
call DispStr ;printf("\n");
add esp, 4
ret
; DispReturn 结束---------------------------------------------------------
main.asm
; ---------------- 内存段描述符宏 -------------
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro
; ---------------- 门描述符宏 -------------
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M+4K
; ------------ DOS 加载初始内存地址 -----------
org 0100h
jmp LABEL_BEGIN
; ------------------- GDT ---------------------
[SECTION .gdt]
ALIGN 32
[BITS 32]
; GDT
; 段基址, 段界限, 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, 92h ; Normal 描述符
LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, 8092h ; 段界限为 1023 * 4096 字节
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, 92h ; Page Directory,可读写
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, 8092h ; Page Tables,段界限为 1023 * 4096 字节
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, 4098h ; 非一致代码段
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, 98h ; 非一致代码段, 用于跳回 16 BITS 模式
LABEL_DESC_DATA: Descriptor 0, DataLen-1, 92h ; 可读写数据段,界限 64KB
LABEL_DESC_STACK: Descriptor 0, TopOfStack, 4093h ; 32 位全局堆栈段,可读写数据段,且栈指针默认使用 esp 寄存器
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, 92h ; 显存首地址
; ------------------ END OF GDT ----------------
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; ------------------ GDT 选择子 -----------------
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT
SelectorPageTbl equ LABEL_DESC_PAGE_TBL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorFlatRW equ LABEL_DESC_FLAT_RW - 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 段选择子 ----------------
; -------------------- IDT ---------------------
[SECTION .idt]
ALIGN 32
[BITS 32]
LABEL_IDT:
; 目标选择子, 偏移, DCount, 属性
%rep 32
Gate SelectorCode32, PrintDefaultText, 0, 8Eh
%endrep
.020h: Gate SelectorCode32, PrintText, 0, 8Eh ; 时钟中断描述符
%rep 95
Gate SelectorCode32, PrintDefaultText, 0, 8Eh
%endrep
.080h: Gate SelectorCode32, PrintDefaultText, 0, 8Eh
; ------------------ END OF IDT ----------------
IdtLen equ $ - LABEL_IDT
IdtPtr dw IdtLen - 1 ; 段界限
dd 0 ; 段基址
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
BootMessage: db "Hello World my OS, techlog.cn!", 0
OffsetBootMessage equ BootMessage - $$
DispeareMessage: db " ", 0
OffsetDispeareMessage equ DispeareMessage - $$
DefaultMessage: db "Default Interrupt Handler", 0
OffsetDefaultMessage equ DefaultMessage - $$
_szReturn db 0Ah, 0
szReturn equ _szReturn - $$
_clockNum dd 0
clockNum equ _clockNum - $$
_MemChkBuf: times 256 db 0 ; ARDS 缓冲区
_dwMCRNumber: dd 0 ; ARDS 个数
_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_PageTableNumber dd 0 ; 页表个数
_dwMemSize: dd 0 ; 最大连续内存大小
_SavedIMREG: db 0 ; 中断屏蔽寄存器值
_SavedIDTR: dd 0 ; 用于保存修改前的 IDTR
dd 0
_ARDStruct: ; Address Range Descriptor Structure
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
MemChkBuf equ _MemChkBuf - $$
dwMCRNumber equ _dwMCRNumber - $$
PageTableNumber equ _PageTableNumber - $$
dwMemSize equ _dwMemSize - $$
dwDispPos equ _dwDispPos - $$
SavedIMREG equ _SavedIMREG - $$
ARDStruct equ _ARDStruct - $$
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
DataLen equ $ - LABEL_DATA
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
[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 [SPValueInRealMode], sp
; 循环获取 ARDS
xor ebx, ebx ; 清零 EBX
mov di, _MemChkBuf ; DI 寄存器中保存写入地址偏移
.loop:
mov eax, 0E820h ; 初始化 EAX 为固定值
mov ecx, 20 ; ARDS 字节数
mov edx, 0534D4150h ; 初始化 EDX 为固定值
int 15h ; 触发 15H 中断
jc LABEL_MEM_CHK_FAIL ; EFLAGS 寄存器 CF 位为 1 则跳转,表示失败
add di, 20 ; DI 寄存器指向下一个待写入位置偏移
inc dword [_dwMCRNumber] ; 计数变量 + 1
cmp ebx, 0 ; 比较 EBX 判断是否完成 ARDS 获取
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
; 初始化非一致代码段描述符
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 ; 清空 eax 寄存器
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; 计算出 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 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关闭硬件中断
cli
; 保存 IDTR 与中断屏蔽寄存器原值
sidt [_SavedIDTR]
in al, 21h
mov [_SavedIMREG], al
; 加载 IDTR
lidt [IdtPtr]
; 打开 A20 地址总线
in al, 92h
or al, 00000010b
out 92h, al
; 置位 PE 标志位,打开保护模式
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, [SPValueInRealMode]
; 恢复 IDTR 与中断屏蔽寄存器 IMREG
lidt [_SavedIDTR]
mov al, [_SavedIMREG]
out 21h, al
; 关闭 A20 地址线
in al, 92h
and al, 0fdh
out 92h, al
; 打开硬件中断
sti
; 触发 BIOS int 21h 中断,回到实地址模式
mov ax, 4c00h
int 21h
[SECTION .s32] ; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorData
mov es, ax
mov ax, SelectorVideo
mov gs, ax ; 赋值视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
call SetupPaging ; 启动分页机制
call Init8259A
int 080h
sti
wait_loop:
cmp dword [clockNum], 4096
jb wait_loop
cli
call SetRealmode8259A
jmp SelectorCode16:0
; ------------------ 初始化可编程中断控制器 ----------------------
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, 0FEh ; 仅开启定时器中断
out 021h, al ; 主8259, OCW1
call io_delay
mov al, 0FFh ; 屏蔽从8259所有中断
out 0A1h, al ; 从8259, OCW1
call io_delay
; 发送 EOI
mov al, 20h
out 20h, al
ret
; ------------------ 恢复可编程中断控制器 ----------------------
SetRealmode8259A:
mov ax, SelectorData
mov fs, ax
mov al, 015h ; 4 字节中断向量
out 020h, al ; 主8259, ICW1.
call io_delay
out 0A0h, al ; 从8259, ICW1
call io_delay
mov al, 008h ; IRQ0 对应中断向量 0x8
out 021h, al ; 主8259, ICW2.
call io_delay
mov al, 0F0h ; 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, 0 ; 开启主 8259A 所有中断
out 021h, al ; 主8259, OCW1
call io_delay
mov al, 0 ; 开启从 8259A 所有中断
out 0A1h, al ; 从8259, OCW1
call io_delay
; 发送 EOI
mov al, 20h
out 20h, al
; 恢复中断屏蔽寄存器(IMREG)
mov al, [fs:SavedIMREG]
out 021h, al
call io_delay
ret
io_delay:
nop
nop
nop
nop
ret
; ---------------------- 获取内存信息 ---------------------------
GetMemInfos:
push esi
push edi
push ecx
; 循环获取 ARDS 4 个成员
mov esi, MemChkBuf ; 寻址缓存区
mov ecx, [dwMCRNumber] ; 获取循环次数 ARDS 个数
.loop:
mov edx, 5 ; 循环遍历 ARDS 的 4 个成员
mov edi, ARDStruct
.1:
; 将缓冲区中成员赋值给 ARDStruct
mov eax, dword [esi]
stosd
add esi, 4
dec edx
cmp edx, 0
jnz .1
; Type 是 AddressRangeMemory 赋值 dwMemSize
cmp dword [dwType], 1
jne .2
mov eax, [dwBaseAddrLow]
add eax, [dwLengthLow]
cmp eax, [dwMemSize]
jb .2
mov [dwMemSize], eax
.2:
loop .loop
pop ecx
pop edi
pop esi
ret
; ---------------------- 分页机制启动 ---------------------------
SetupPaging:
; 获取内存信息
call GetMemInfos
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 一个页表对应的内存 4MB
div ebx
mov ecx, eax ; ecx 保存页表个数
test edx, edx
jz .no_remainder
inc ecx ; 余数不为 0 则增加一个页表
.no_remainder:
mov [PageTableNumber], ecx ; 暂存页表个数
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase ; 此段首地址为 PageDirBase
xor eax, eax
mov eax, 201000h | 7 ; 用户级,存在于内存,可读写
.filter_pde:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .filter_pde
; 初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE 个数 = 页表个数 * 1024
mov edi, 201000h
xor eax, eax
mov eax, 7 ; 用户级、存在于内存、可读写
.filter_pte:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .filter_pte
; 设置页目录表起始地址
mov eax, PageDirBase
mov cr3, eax
; 开启分页机制
mov eax, cr0
or eax, 80000000h
mov cr0, eax
ret
_PrintText:
PrintText equ _PrintText - $$
mov eax, 0Ch ; 黑底红字,不闪烁
push eax
mov eax, 80 * 2 * 2
push eax
mov eax, dword [clockNum]
mov ebx, 2
xor edx, edx
div ebx
cmp edx, 0
jne print_dispear
push OffsetBootMessage
jmp print_text
print_dispear:
push OffsetDispeareMessage
print_text:
call DisplayString
add esp, 12
add dword [clockNum], 1
; 发送 EOI
mov al, 20h
out 20h, al
iretd
_PrintDefaultText:
PrintDefaultText equ _PrintDefaultText - $$
mov eax, 0Ch ; 黑底红字,不闪烁
push eax
mov eax, 80 * 3 * 2
push eax
push OffsetDefaultMessage
call DisplayString
add esp, 12
; 发送 EOI
mov al, 20h
out 20h, al
iretd
%include "lib.asm" ; 库函数
SegCode32Len equ $ - LABEL_SEG_CODE32
; 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 eax, 7FFFFFFEh ; PE=0, PG=0
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp word 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
cpu
interrupt
8086
中断
时钟
idt
idtr
描述符
门描述符
中断门