代码
- kernel.asm
%include "pm.inc"
org 0x9000
VRAM_ADDRESS equ 0x000a0000
jmp LABEL_BEGIN
[SECTION .gdt]
; 段基址 段界限 属性
LABEL_GDT: Descriptor 0, 0, 0
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW
LABEL_DESC_VRAM: Descriptor 0, 0ffffffffh, DA_DRW
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32
GdtLen equ $ - LABEL_GDT
GdtPtr dw GdtLen - 1
dd 0
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVram equ LABEL_DESC_VRAM - LABEL_GDT
LABEL_IDT:
%rep 255
Gate SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep
IdtLen equ $ - LABEL_IDT
IdtPtr dw IdtLen - 1
dd 0
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov al, 0x13
mov ah, 0
int 0x10
call init8259A
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_GDT
mov dword [GdtPtr + 2], eax
lgdt [GdtPtr]
cli ;关中断
;prepare for loading IDT
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_IDT
mov dword [IdtPtr + 2], eax
lidt [IdtPtr]
in al, 92h
or al, 00000010b
out 92h, al
mov eax, cr0
or eax , 1
mov cr0, eax
jmp dword SelectorCode32: 0
init8259A:
mov al, 011h
out 02h, al
call io_delay
out 0A0h, al
call io_delay
mov al, 020h
out 021h, al
call io_delay
mov al, 028h
out 0A1h, al
call io_delay
mov al, 004h
out 021h, al
call io_delay
mov al, 002h
out 0A1h, al
call io_delay
mov al, 003h
out 021h, al
call io_delay
out 0A1h, al
call io_delay
mov al, 11111101b ;允许键盘中断
out 21h, al
call io_delay
mov al, 11111111b
out 0A1h, al
call io_delay
ret
io_delay:
nop
nop
nop
nop
ret
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
;initialize stack for c code
mov ax, SelectorStack
mov ss, ax
mov esp, TopOfStack
mov ax, SelectorVram
mov ds, ax
mov ax, SelectorVideo
mov gs, ax
sti
%include "write_vga_desktop.asm"
jmp $
_SpuriousHandler:
SpuriousHandler equ _SpuriousHandler - $$
call intHandlerFromC
iretd
io_hlt: ;void io_hlt(void);
HLT
RET
io_cli:
CLI
RET
io_sti:
STI
RET
io_stihlt:
STI
HLT
RET
io_in8:
mov edx, [esp + 4]
mov eax, 0
in al, dx
io_in16:
mov edx, [esp + 4]
mov eax, 0
in ax, dx
io_in32:
mov edx, [esp + 4]
in eax, dx
ret
io_out8:
mov edx, [esp + 4]
mov al, [esp + 8]
out dx, al
ret
io_out16:
mov edx, [esp + 4]
mov eax, [esp + 8]
out dx, ax
ret
io_out32:
mov edx, [esp + 4]
mov eax, [esp + 8]
out dx, eax
ret
io_load_eflags:
pushfd
pop eax
ret
io_store_eflags:
mov eax, [esp + 4]
push eax
popfd
ret
show_char:
mov ah, 0Ch
mov al, 'U'
mov [gs:((80 * 0 + 67) * 2)], ax
ret
%include "fontData.inc"
SegCode32Len equ $ - LABEL_SEG_CODE32
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK
2.write_vga_desktop.c
void intHandlerFromC(char* esp) {
char*vram = bootInfo.vgaRam;
int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
boxfill8(vram, xsize, COL8_000000, 0,0,32*8 -1, 15);
showString(vram, xsize, 0, 0, COL8_FFFFFF, "PS/2 keyboard");
for (;;) {
io_hlt();
}
show_char();
}
分析
中断产生的原因有两种:一种是外部中断,也就是由硬件产生的中断,另一种是由指令int n产生的中断。
指令int n产生的中断的机制如下图:
这里引入了IDT,IDT的作用是将每一个中断向量和一个描述符对应起来。
IDT的结构如下图:
由硬件产生的中断是随机发生的,它们通常用来处理外部的事件,比如外围设备的请求。每一种中断都会对应一个中断向量号,而这个向量号通过IDT就与相应的中断处理程序对应起来了。那么硬件是如何和向量号建立关系的。外部中断分为不可屏蔽中断和可屏蔽中断。
不可屏蔽中断,因为它与IF是否被设置无关。
可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的。在BIOS初始化的时候,IRQ0~IRQ7被设置为对应向量号08h ~0Fh,但是在保护模式下向量号08h ~0Fh已经被占用了,因为8259A是可编程中断控制器,向对应的端口地址写入特定的ICW来实现。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。对应代码是init8259A
,这段代码的后两个,通过向端口21h和A1h写入OCW屏蔽了所有的外部中断,OCW其中的两种情况(1)屏蔽或打开外部中断。(2)发送EOI给8259A已通知它中断处理结束。每一次中断处理结束,需要发送一个EOI给8259A,以便继续接收中断。每一次I/O操作之后都调用一个延迟函数io_delay以等待操作的完成。
建立IDT
通过%rep预处理指令,将每一个描述符都设置为指向SelectorCode32:SpuriousHandler。SpuriousHandler函数调用了c函数,然后进入死循环。关中断之后是对IDT的初始化,这个和GDT初始化差不多一样。
运行结果:
当键盘任意按下某个键时发生中断,屏幕上显示c语言里面写的东西。