【发布时间】:2017-04-02 01:01:41
【问题描述】:
我正在开发一个简单的内核,我一直在尝试实现一个键盘中断处理程序来摆脱端口轮询。我一直在-kernel 模式下使用QEMU(以减少编译时间,因为使用grub-mkrescue 生成iso 需要相当长的时间)并且它工作得很好,但是当我想切换到-cdrom 模式时它突然启动了崩溃。我不知道为什么。
最终我意识到,当它从 iso 引导时,它还会在引导内核本身之前运行 GRUB 引导加载程序。我发现 GRUB 可能会将处理器切换到保护模式,这会导致 问题。
问题: 通常我会简单地初始化中断处理程序,只要我按下一个键,它就会被处理。但是,当我使用 iso 运行内核并按下一个键时,虚拟机就崩溃了。这发生在 qemu 和 VMWare 中,所以我认为我的中断一定有问题。
请记住,只要我不使用 GRUB,代码就可以正常工作。
interrupts_init()(见下文)是main() 内核函数中最先调用的东西之一。
本质上的问题是:有没有办法让它在保护模式下工作?。
我的内核的完整副本可以在我的GitHub repository 中找到。一些相关文件:
lowlevel.asm:
section .text
global keyboard_handler_int
global load_idt
extern keyboard_handler
keyboard_handler_int:
pushad
cld
call keyboard_handler
popad
iretd
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
interrupts.c:
#include <assembly.h> // defines inb() and outb()
#define IDT_SIZE 256
#define PIC_1_CTRL 0x20
#define PIC_2_CTRL 0xA0
#define PIC_1_DATA 0x21
#define PIC_2_DATA 0xA1
extern void keyboard_handler_int(void);
extern void load_idt(void*);
struct idt_entry
{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char flags;
unsigned short int offset_higherbits;
} __attribute__((packed));
struct idt_pointer
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct idt_entry idt_table[IDT_SIZE];
struct idt_pointer idt_ptr;
void load_idt_entry(int isr_number, unsigned long base, short int selector, unsigned char flags)
{
idt_table[isr_number].offset_lowerbits = base & 0xFFFF;
idt_table[isr_number].offset_higherbits = (base >> 16) & 0xFFFF;
idt_table[isr_number].selector = selector;
idt_table[isr_number].flags = flags;
idt_table[isr_number].zero = 0;
}
static void initialize_idt_pointer()
{
idt_ptr.limit = (sizeof(struct idt_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int)&idt_table;
}
static void initialize_pic()
{
/* ICW1 - begin initialization */
outb(PIC_1_CTRL, 0x11);
outb(PIC_2_CTRL, 0x11);
/* ICW2 - remap offset address of idt_table */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
outb(PIC_1_DATA, 0x20);
outb(PIC_2_DATA, 0x28);
/* ICW3 - setup cascading */
outb(PIC_1_DATA, 0x00);
outb(PIC_2_DATA, 0x00);
/* ICW4 - environment info */
outb(PIC_1_DATA, 0x01);
outb(PIC_2_DATA, 0x01);
/* Initialization finished */
/* mask interrupts */
outb(0x21 , 0xFF);
outb(0xA1 , 0xFF);
}
void idt_init(void)
{
initialize_pic();
initialize_idt_pointer();
load_idt(&idt_ptr);
}
void interrupts_init(void)
{
idt_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8E);
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
outb(0x21 , 0xFD);
}
kernel.c
#if defined(__linux__)
#error "You are not using a cross-compiler, you will most certainly run into trouble!"
#endif
#if !defined(__i386__)
#error "This kernel needs to be compiled with a ix86-elf compiler!"
#endif
#include <kernel.h>
// These _init() functions are not in their respective headers because
// they're supposed to be never called from anywhere else than from here
void term_init(void);
void mem_init(void);
void dev_init(void);
void interrupts_init(void);
void shell_init(void);
void kernel_main(void)
{
// Initialize basic components
term_init();
mem_init();
dev_init();
interrupts_init();
// Start the Shell module
shell_init();
// This should be unreachable code
kernel_panic("End of kernel reached!");
}
boot.asm:
bits 32
section .text
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kernel_main
start:
mov esp, stack_space ;set stack pointer
call kernel_main
; We shouldn't get to here, but just in case do an infinite loop
endloop:
hlt ;halt the CPU
jmp endloop
section .bss
resb 8192 ;8KB for stack
stack_space:
【问题讨论】:
-
我对你有感觉。这很可能是您自己的软件中的错误。问题是您显示的代码并没有真正使它成为一个最小的完整可验证示例。几乎不可能说(我们甚至看不到键盘处理程序,您如何设置 GDT/LIDT 等)。我希望能够提供帮助。例如,如果你在 Git 存储库中有这个,我就可以对其进行测试,看看我能找到什么。我假设有太多代码无法在一个问题中发布所有内容?
-
在我搜索本地文件并发现看起来非常熟悉的代码之前,您知道这段代码看起来有些熟悉。您是否尝试过此答案中的代码:stackoverflow.com/a/37635449/3857942。您的文件名似乎与我去年回答的问题中的文件名非常相似。我的答案包括最小的完整文件集。我想知道当您从 ISO 启动时该代码是否有效
-
是的,GRUB 将在引导 multibbot 兼容内核时切换到保护模式。如果您曾经使用兼容多重引导的内核重新加载任何选择器寄存器(CS、DS、ES),您需要确保您也设置了自己的 GDT。如果您不更改它们,那么您就可以了。
-
如果您的代码与我的答案类似,那么我怀疑我知道可能出了什么问题,甚至可能与我对 GDT 的最后评论有关。在 Multiboot 规范中,Multiboot docs 'GDTR' 中有这个重要说明即使段寄存器如上所述设置,'GDTR' 可能无效,因此 OS 映像不得加载任何段寄存器(甚至只是重新加载相同的值!),直到它设置自己的“GDT”。。可能是在 IRQ 触发时发生故障,因为 CS 选择器在中断发生时被加载。错误的 GDTR 可能会导致失败
-
这里是凌晨 1:30。我能够重现可能是您的问题。我最好的猜测是您需要在设置 IDT 之前手动设置自己的 GDT。看来 QEMU 多重引导加载程序(与
-kernel一起使用)对 GDTR/GDT 更加宽容。我能够破解一个 GDT 并且它似乎解决了这个问题。当天晚些时候,我将在另一个链接上更新我的答案。
标签: c x86 interrupt osdev multiboot