自定义OS键盘触发中断时内核崩溃问题求助
键盘中断未触发导致内核崩溃的排查与修复
我从零开发了一款支持键盘中断的操作系统,但按下键盘时内核会停止工作。排查后推测按下键盘时_asm_inthandler21并未被调用,以下是相关代码,求解决建议:
BootLoader.asm
[BITS 16] [ORG 0x7C00] READSECTOR EQU 0x64 READMEMORY EQU 0x7E00 Init: CLI XOR AX, AX MOV DS, AX MOV ES, AX MOV SS, AX MOV SP, 0x7C00 MOV BP, SP STI Main: CALL LoadKernel CALL SetVBE CALL DisableInt CALL EnableA20 CALL SetupGDT CALL EnterProtectedMode LoadKernel: MOV BYTE[BootDrive], DL MOV BX, 0x02 MOV CX, READSECTOR MOV DX, READMEMORY MOV AH, 0x02 MOV AL, CL MOV CL, BL MOV BX, DX MOV CH, 0x00 MOV DH, 0x00 MOV DL, BYTE[BootDrive] INT 0x13 JC LoadFailed RET LoadFailed: MOV SI, MsgBootFailed CALL Print CLI HLT Print: MOV AH, 0x0e MOV BL, 0x07 MOV BH, 0x00 PrintLoop: LODSB TEST AL, AL JZ PrintEnd INT 0x10 JMP PrintLoop PrintEnd: RET SetVBE: MOV AL, 0x13 MOV AH, 0x00 INT 0x10 RET DisableInt: MOV AL, 0xff OUT 0x21, AL NOP OUT 0xa1, AL CLI RET EnableA20: IN AL, 0x92 OR AL, 2 OUT 0x92, AL RET SetupGDT: CLI PUSHA LGDT [GDT_TOC] STI POPA RET GDT_TOC: DW 8 * 3 DW GDT, 0x0000 GDT: DW 0x0000, 0x0000, 0x0000, 0x0000 DW 0xFFFF, 0x0000, 0x9A00, 0x00CF DW 0xFFFF, 0x0000, 0x9200, 0x00CF EnterProtectedMode: MOV EAX, CR0 AND EAX, 0x7fffffff OR EAX, 1 MOV CR0, EAX JMP 08h:Main32 [BITS 32] Main32: MOV AX, 0x10 MOV DS, AX MOV ES, AX MOV FS, AX MOV GS, AX MOV SS, AX MOV ESP, 0xffff CALL RelocKernel JMP ExecuteKernel RelocKernel: MOV ESI, READMEMORY MOV EDI, 0x200000 MOV ECX, 512 * READSECTOR CALL memcpy RET memcpy: MOV EAX, [ESI] ADD ESI, 4 MOV [EDI], EAX ADD EDI, 4 SUB ECX, 1 JNZ memcpy RET ExecuteKernel: CLI MOV EBP, 0x200000 ;ADD EBP, [EBP + 0x18] CALL EBP HLT MsgBootFailed: DB "Kernel Load Failed.", 0x0D, 0x0A, 0x00 BootDrive: DB 0 TIMES 510 - ($ - $$) DB 0 DW 0xAA55
KernelMain.c
#include "KernelMain.h" int main() { InitGDTIDT(); InitPIC(); ASM_STI(); IoWrite8(0x0021, 0xf9); IoWrite8(0x00a1, 0xef); for (;;) { ASM_HLT(); } return 0; }
KernelMain.h
typedef unsigned long size_t; typedef signed int int32_t; typedef unsigned int uint32_t; typedef unsigned short uint16_t; typedef signed short int16_t; typedef unsigned char uint8_t; typedef signed char int8_t; typedef unsigned long long uint64_t; #define ADR_IDT 0x0010f800 #define LIMIT_IDT 0x000007ff #define ADR_GDT 0x00110000 #define LIMIT_GDT 0x0000ffff #define ADR_KERNEL 0x00200000 #define LIMIT_KERNEL 0x0007ffff #define AR_DATA32_RW 0x4092 #define AR_CODE32_ER 0x409a #define AR_INTGATE32 0x008e typedef struct { uint16_t LimitLow; uint16_t BaseLow; uint8_t BaseMid; uint8_t AccessRight; uint8_t LimitHigh; uint8_t BaseHigh; } SEGMENT_DESCRIPTOR; typedef struct { uint16_t OffsetLow; uint16_t Selector; uint8_t DwCount; uint8_t AccessRight; uint16_t OffsetHigh; } GATE_DESCRIPTOR; void InitGDTIDT(); void SetSegmentDescriptor(SEGMENT_DESCRIPTOR *sd, uint32_t limit, int base, int ar); void SetGateDescriptor(GATE_DESCRIPTOR *gd, int offset, int selector, int ar); void InitPIC(); void _inthandler21(int *esp); void _inthandler27(int *esp); void _inthandler2c(int *esp); void _asm_inthandler21(void); void _asm_inthandler27(void); void _asm_inthandler2c(void);
Segment.c
#include "KernelMain.h" void InitGDTIDT() { SEGMENT_DESCRIPTOR *gdt = (SEGMENT_DESCRIPTOR *)ADR_GDT; GATE_DESCRIPTOR *idt = (GATE_DESCRIPTOR *)ADR_IDT; int i; for (i = 0; i <= LIMIT_GDT / 8; i++) { SetSegmentDescriptor(gdt + i, 0, 0, 0); } SetSegmentDescriptor(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW); SetSegmentDescriptor(gdt + 2, LIMIT_KERNEL, ADR_KERNEL, AR_CODE32_ER); LoadGDT(LIMIT_GDT, ADR_GDT); for (i = 0; i <= LIMIT_IDT / 8; i++) { SetGateDescriptor(idt + i, 0, 0, 0); } LoadIDT(LIMIT_IDT, ADR_IDT); SetGateDescriptor(idt + 0x21, (int)_asm_inthandler21, 2 * 8, AR_INTGATE32); SetGateDescriptor(idt + 0x27, (int)_asm_inthandler27, 2 * 8, AR_INTGATE32); SetGateDescriptor(idt + 0x2c, (int)_asm_inthandler2c, 2 * 8, AR_INTGATE32); } void SetSegmentDescriptor(SEGMENT_DESCRIPTOR *sd, uint32_t limit, int base, int ar) { if (limit > 0xfffff) { ar |= 0x8000; limit /= 0x1000; } sd->LimitLow = limit & 0xffff; sd->BaseLow = base & 0xffff; sd->BaseMid = (base >> 16) & 0xff; sd->AccessRight = ar & 0xff; sd->LimitHigh = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->BaseHigh = (base >> 24) & 0xff; } void SetGateDescriptor(GATE_DESCRIPTOR *gd, int offset, int selector, int ar) { gd->OffsetLow = offset & 0xffff; gd->Selector = selector; gd->DwCount = (ar >> 8) & 0xff; gd->AccessRight = ar & 0xff; gd->OffsetHigh = (offset >> 16) & 0xffff; }
Interrupts.asm
[BITS 32] GLOBAL _asm_inthandler21, _asm_inthandler27, _asm_inthandler2c EXTERN _inthandler21, _inthandler27, _inthandler2c [SECTION .text] _asm_inthandler21: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler21 POP EAX POPAD POP DS POP ES IRETD _asm_inthandler27: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler27 POP EAX POPAD POP DS POP ES IRETD _asm_inthandler2c: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler2c POP EAX POPAD POP DS POP ES IRETD
Interrupts.c
#include "KernelMain.h" #define PIC0_ICW1 0x0020 #define PIC0_OCW2 0x0020 #define PIC0_IMR 0x0021 #define PIC0_ICW2 0x0021 #define PIC0_ICW3 0x0021 #define PIC0_ICW4 0x0021 #define PIC1_ICW1 0x00a0 #define PIC1_OCW2 0x00a0 #define PIC1_IMR 0x00a1 #define PIC1_ICW2 0x00a1 #define PIC1_ICW3 0x00a1 #define PIC1_ICW4 0x00a1 void InitPIC() { IoWrite8(PIC0_IMR, 0xff); IoWrite8(PIC1_IMR, 0xff); IoWrite8(PIC0_ICW1, 0x11); IoWrite8(PIC0_ICW2, 0x20); IoWrite8(PIC0_ICW3, 1 << 2); IoWrite8(PIC0_ICW4, 0x01); IoWrite8(PIC1_ICW1, 0x11); IoWrite8(PIC1_ICW2, 0x28); IoWrite8(PIC1_ICW3, 2); IoWrite8(PIC1_ICW4, 0x01); IoWrite8(PIC0_IMR, 0xfb); IoWrite8(PIC1_IMR, 0xff); return; } void _inthandler21(int *esp) { for (int i = 0; i < 320 * 200; i++) { *((uint8_t *)i) = 1; } for (;;) { ASM_HLT(); } } void _inthandler2c(int *esp) { for (int i = 0; i < 320 * 200; i++) { *((uint8_t *)i) = 2; } for (;;) { ASM_HLT(); } } void _inthandler27(int *esp) { for (int i = 0; i < 320 * 200; i++) { *((uint8_t *)i) = 3; } IoWrite8(PIC0_OCW2, 0x67); return; }
asm.c
#include "KernelMain.h" void IoWrite8(uint32_t port, uint8_t data) { asm volatile("outb %b0, %w1" : : "a"(data), "Nd"(port)); } uint8_t IoRead8(uint32_t port) { uint8_t data; asm volatile("inb %w1, %b0" : "=a"(data) : "Nd"(port)); return data; } void LoadGDT(int Limit, int Addr) { asm volatile("mov 0x4(%esp),%ax"); asm volatile("mov %ax,0x6(%esp)"); asm volatile("lgdtl 0x6(%esp)"); return; } void LoadIDT(int Limit, int Addr) { asm volatile("mov 0x4(%esp),%ax"); asm volatile("mov %ax,0x6(%esp)"); asm volatile("lidtl 0x6(%esp)"); return; } void ASM_HLT() { asm volatile("HLT"); } void ASM_CLI() { asm volatile("CLI"); } void ASM_STI() { asm volatile("STI"); }
排查与修复建议
修正IDT设置顺序
当前代码先调用LoadIDT再设置门描述符,导致加载的IDT为空,后续设置的门描述符不会生效。修改InitGDTIDT函数顺序:// 先清空并设置所有门描述符,再加载IDT for (i = 0; i <= LIMIT_IDT / 8; i++) { SetGateDescriptor(idt + i, 0, 0, 0); } SetGateDescriptor(idt + 0x21, (int)_asm_inthandler21, 2 * 8, AR_INTGATE32); SetGateDescriptor(idt + 0x27, (int)_asm_inthandler27, 2 * 8, AR_INTGATE32); SetGateDescriptor(idt + 0x2c, (int)_asm_inthandler2c, 2 * 8, AR_INTGATE32); LoadIDT(LIMIT_IDT, ADR_IDT);添加中断结束信号(EOI)
键盘中断处理函数_inthandler21未发送EOI,导致PIC阻塞后续中断。在函数末尾添加:IoWrite8(PIC0_OCW2, 0x61); // 0x61对应IRQ1的EOI信号清除键盘中断状态
键盘触发中断后,必须从端口0x60读取扫描码以清除中断标志,否则键盘会持续发送中断。在_inthandler21中添加:IoRead8(0x60); // 读取键盘扫描码,清除中断状态修复memcpy逻辑错误
BootLoader中的memcpy每次拷贝4字节但仅将ECX减1,导致实际拷贝字节数是预期的4倍,
相关产品推荐
相关产品推荐

