Multiboot 键盘驱动程序与 GRUB(与 QEMU 一起工作)出现三重故障 - 为什么?
Multiboot keyboard driver triple faults with GRUB (works with QEMU) - why?
我已经阅读了大量关于 OS x86 开发的教程,到目前为止一切顺利 - 直到现在。我无法弄清楚我一生的解决方案。
我的目标是为 x86 编写最简单的键盘驱动程序。 QEMU 运行良好,但 GRUB 运行不佳。
我尽力模仿 mkeykernel based on the article by Arjun Sreedharan。不幸的是,mkeykernel也存在这个问题。
当 运行 使用 qemu-system-i386 -kernel kernel.bin
编译我的内核时,一切都按预期工作:我输入,字母显示在屏幕上。
但是,当我使用 grub-mkrescue
创建并 运行 GRUB ISO 时,只要我按下一个键,系统就会重新启动。
当 运行 qemu-system-i386 -cdrom build/myos.iso -d int --no-reboot
时,我发现 CPU 异常是 0xd General Protection Fault。起初,我认为这是因为 GRUB 以一种意想不到的方式设置了 GDT。但是正如您将在下面看到的,我添加了自己的 GDT,但并没有解决问题。
我还在 Whosebug 上找到了一个接近的匹配项。我几乎遵循了那篇文章中的所有建议,尤其是关于打包结构的建议,但无济于事。
这是我第一次被难到写了一个 Whosebug 问题 :) 希望有人能在这里看到这个问题!
我已经包含了所有相关文件的源代码和构建它们/重现问题的说明。
第一个文件:kernel.asm
bits 32
section .multiboot
dd 0x1BADB002 ; Magic number
dd 0x0 ; Flags
dd - (0x1BADB002 + 0x0) ; Checksum
section .text
%include "gdt.asm"
; Make global anything that is used in main.c
global start
global print_char_with_asm
global load_gdt
global load_idt
global keyboard_handler
global ioport_in
global ioport_out
global enable_interrupts
extern main ; Defined in kernel.c
extern handle_keyboard_interrupt
load_gdt:
lgdt [gdt_descriptor] ; from gdt.asm
ret
load_idt:
mov edx, [esp + 4]
lidt [edx]
ret
enable_interrupts:
sti
ret
keyboard_handler:
pushad
cld
call handle_keyboard_interrupt
popad
iretd
ioport_in:
mov edx, [esp + 4]
in al, dx
ret
ioport_out:
mov edx, [esp + 4]
mov eax, [esp + 8]
out dx, al
ret
print_char_with_asm:
; OFFSET = (ROW * 80) + COL
mov eax, [esp + 8] ; eax = row
mov edx, 80 ; 80 (number of cols per row)
mul edx ; now eax = row * 80
add eax, [esp + 12] ; now eax = row * 80 + col
mov edx, 2 ; * 2 because 2 bytes per char on screen
mul edx
mov edx, 0xb8000 ; vid mem start in edx
add edx, eax ; Add our calculated offset
mov eax, [esp + 4] ; char c
mov [edx], al
ret
start:
cli ; Disable interrupts
mov esp, stack_space
call main
hlt
section .bss
resb 8192 ; 8KB for stack
stack_space:
第二个文件:kernel.c
// ----- Pre-processor constants -----
#define ROWS 25
#define COLS 80
// IDT_SIZE: Specific to x86 architecture
#define IDT_SIZE 256
// KERNEL_CODE_SEGMENT_OFFSET: the first segment after the null segment in gdt.asm
#define KERNEL_CODE_SEGMENT_OFFSET 0x8
// 32-bit Interrupt gate: 0x8E
// ( P=1, DPL=00b, S=0, type=1110b => type_attr=1000_1110b=0x8E) (thanks osdev.org)
#define IDT_INTERRUPT_GATE_32BIT 0x8e
// IO Ports for PICs
#define PIC1_COMMAND_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIC2_COMMAND_PORT 0xA0
#define PIC2_DATA_PORT 0xA1
// IO Ports for Keyboard
#define KEYBOARD_DATA_PORT 0x60
#define KEYBOARD_STATUS_PORT 0x64
// ----- Includes -----
#include "keyboard_map.h"
// ----- External functions -----
extern void print_char_with_asm(char c, int row, int col);
extern void load_gdt();
extern void keyboard_handler();
extern char ioport_in(unsigned short port);
extern void ioport_out(unsigned short port, unsigned char data);
extern void load_idt(unsigned int* idt_address);
extern void enable_interrupts();
// ----- Structs -----
struct IDT_pointer {
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct IDT_entry {
unsigned short offset_lowerbits; // 16 bits
unsigned short selector; // 16 bits
unsigned char zero; // 8 bits
unsigned char type_attr; // 8 bits
unsigned short offset_upperbits; // 16 bits
} __attribute__((packed));
// ----- Global variables -----
struct IDT_entry IDT[IDT_SIZE]; // This is our entire IDT. Room for 256 interrupts
int cursor_pos = 0;
void init_idt() {
// Get the address of the keyboard_handler code in kernel.asm as a number
unsigned int offset = (unsigned int)keyboard_handler;
// Populate the first entry of the IDT
// TODO why 0x21 and not 0x0?
// My guess: 0x0 to 0x2 are reserved for CPU, so we use the first avail
IDT[0x21].offset_lowerbits = offset & 0x0000FFFF; // lower 16 bits
IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
IDT[0x21].zero = 0;
IDT[0x21].type_attr = IDT_INTERRUPT_GATE_32BIT;
IDT[0x21].offset_upperbits = (offset & 0xFFFF0000) >> 16;
// Program the PICs - Programmable Interrupt Controllers
ioport_out(PIC1_COMMAND_PORT, 0x11);
ioport_out(PIC2_COMMAND_PORT, 0x11);
// ICW2: Vector Offset (this is what we are fixing)
ioport_out(PIC1_DATA_PORT, 0x20);
ioport_out(PIC2_DATA_PORT, 0x28);
// ICW3: Cascading (how master/slave PICs are wired/daisy chained)
ioport_out(PIC1_DATA_PORT, 0x0);
ioport_out(PIC2_DATA_PORT, 0x0);
// ICW4: "Gives additional information about the environemnt"
ioport_out(PIC1_DATA_PORT, 0x1);
ioport_out(PIC2_DATA_PORT, 0x1);
// Voila! PICs are initialized
// Mask all interrupts
ioport_out(PIC1_DATA_PORT, 0xff);
ioport_out(PIC2_DATA_PORT, 0xff);
struct IDT_pointer idt_ptr;
idt_ptr.limit = (sizeof(struct IDT_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int) &IDT;
// Now load this IDT
load_idt(&idt_ptr);
}
void kb_init() {
// 0xFD = 1111 1101 in binary. enables only IRQ1
ioport_out(PIC1_DATA_PORT, 0xFD);
}
void handle_keyboard_interrupt() {
// Write end of interrupt (EOI)
ioport_out(PIC1_COMMAND_PORT, 0x20);
unsigned char status = ioport_in(KEYBOARD_STATUS_PORT);
// Lowest bit of status will be set if buffer not empty
// (thanks mkeykernel)
if (status & 0x1) {
char keycode = ioport_in(KEYBOARD_DATA_PORT);
if (keycode < 0 || keycode >= 128) return;
print_char_with_asm(keyboard_map[keycode],0,cursor_pos);
cursor_pos++;
}
}
void clear_screen() {
int i, j;
for (i = 0; i < COLS; i++) {
for (j = 0; j < ROWS; j++) {
print_char_with_asm(' ',j,i);
}
}
}
// ----- Entry point -----
void main() {
clear_screen();
load_gdt();
init_idt();
kb_init();
enable_interrupts();
while(1);
}
第三个文件:gdt.asm
(主要基于 this handy guide)
; GDT - Global Descriptor Table
gdt_start:
gdt_null: ; Entry 1: Null entry must be included first (error check)
dd 0x0 ; double word = 4 bytes = 32 bits
dd 0x0
gdt_code: ; Entry 2: Code segment descriptor
; Structure:
; Segment Base Address (base) = 0x0
; Segment Limit (limit) = 0xfffff
dw 0xffff ; Limit bits 0-15
dw 0x0000 ; Base bits 0-15
db 0x00 ; Base bits 16-23
; Flag Set 1:
; Segment Present: 0b1
; Descriptor Privilege level: 0x00 (ring 0)
; Descriptor Type: 0b1 (code/data)
; Flag Set 2: Type Field
; Code: 0b1 (this is a code segment)
; Conforming: 0b0 (Code w/ lower privilege may not call this)
; Readable: 0b1 (Readable or execute only? Readable means we can read code constants)
; Accessed: 0b0 (Used for debugging and virtual memory. CPU sets bit when accessing segment)
db 10011010b ; Flag set 1 and 2
; Flag Set 3
; Granularity: 0b1 (Set to 1 multiplies limit by 4K. Shift 0xfffff 3 bytes left, allowing to span full 32G of memory)
; 32-bit default: 0b1
; 64-bit segment: 0b0
; AVL: 0b0
db 11001111b ; Flag set 3 and limit bits 16-19
db 0x00 ; Base bits 24-31
gdt_data:
; Same except for code flag:
; Code: 0b0
dw 0xfffff ; Limit bits 0-15
dw 0x0000 ; Base bits 0-15
db 0x00 ; Base bits 16-23
db 10010010b ; Flag set 1 and 2
db 11001111b ; 2nd flags and limit bits 16-19
db 0x00 ; Base bits 24-31
gdt_end: ; Needed to calculate GDT size for inclusion in GDT descriptor
; GDT Descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; Size of GDT, always less one
dd gdt_start
; Define constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
; In protected mode, set DS = INDEX to select GDT entries
; Then CPU knows to use segment at that offset
; Example: (0x0: NULL segment; 0x8: CODE segment; 0x10: DATA segment)
第四个文件:grub.cfg
menuentry "myos" {
multiboot /boot/grub/kernel.bin
}
第五个文件:linker.ld
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 1M;
.text BLOCK(4K) : ALIGN(4K)
{
*(.multiboot)
*(.text)
}
.data : { *(.data) }
.bss : { *(.bss) }
}
糟糕,遗漏了一个文件 - 这里是 keyboard_map.h
:
unsigned char keyboard_map[128] = {
// -------- 0 to 9 --------
' ',
' ', // escape key
'1','2','3','4','5','6','7','8',
// -------- 10 to 19 --------
'9','0','-','=',
' ', // Backspace
' ', // Tab
'q','w','e','r',
// -------- 20 to 29 --------
't','y','u','i','o','p','[',']',
' ', // Enter
' ', // left Ctrl
// -------- 30 to 39 --------
'a','s','d','f','g','h','j','k','l',';',
// -------- 40 to 49 --------
' ','`',
' ', // left Shift
' ','z','x','c','v','b','n',
// -------- 50 to 59 --------
'm',',','.',
'/', // slash, or numpad slash if preceded by keycode 224
' ', // right Shift
'*', // numpad asterisk
' ', // left Alt
' ', // Spacebar
' ',
' ', // F1
// -------- 60 to 69 --------
' ', // F2
' ', // F3
' ', // F4
' ', // F5
' ', // F6
' ', // F7
' ', // F8
' ', // F9
' ', // F10
' ',
// -------- 70 to 79 --------
' ', // scroll lock
'7', // numpad 7, HOME key if preceded by keycode 224
'8', // numpad 8, up arrow if preceded by keycode 224
'9', // numpad 9, PAGE UP key if preceded by keycode 224
'-', // numpad hyphen
'4', // numpad 4, left arrow if preceded by keycode 224
'5', // numpad 5
'6', // numpad 6, right arrow if preceded by keycode 224
' ',
'1', // numpad 1, END key if preceded by keycode 224
// -------- 80 to 89 --------
'2', // numpad 2, down arrow if preceded by keycode 224
'3', // numpad 3, PAGE DOWN key if preceded by keycode 224
'0', // numpad 0, INSERT key if preceded by keycode 224
'.', // numpad dot, DELETE key if preceded by keycode 224
' ',' ',' ',' ',' ',' ',
// -------- 90 to 99 --------
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
// -------- 100 to 109 --------
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
// -------- 110 to 119 --------
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
// -------- 120-127 --------
' ',' ',' ',' ',' ',' ',' ',' ',
};
// Right control, right alt seem to send
// keycode 224, then the left control/alt keycode
// Arrow keys also send two interrupts, one 224 and then their actual code
// Same for numpad enter
// 197: Num Lock
// 157: Pause|Break (followed by 197?)
// Clicking on screen appears to send keycodes 70, 198
// Is this the MARK command or something like that?
将上述所有文件一起粘贴到 Linux 上的一个目录中。那么...
编译内核:
mkdir build
nasm -f elf32 kernel.asm -o build/boot.o
gcc -m32 -ffreestanding -c kernel.c -o build/kernel.o
ld -m elf_i386 -T linker.ld -o build/kernel.bin build/boot.o build/kernel.o
到 运行 带有 QEMU 的内核(应该可以正常工作):
qemu-system-i386 -kernel build/kernel-bin
到 运行 使用 GRUB 的内核(不起作用):
mkdir -p build/iso/boot/grub
cp grub.cfg build/iso/boot/grub
cp build/kernel.bin build/iso/boot/grub
grub-mkrescue -o build/myos.iso build/iso
qemu-system-i386 -cdrom build/myos.iso
有没有人运行以前处理过这个问题?作为 x86 初学者,您是否有其他资源可以推荐获取键盘?我真的很想用我的小 mini-OS!
最终获得一些保护模式的用户输入
我应该使用除 GRUB 之外的其他引导加载程序吗?
TLDR:简单的键盘驱动程序与 QEMU -kernel
选项一起工作,但在使用 grub-mkrescue
.
创建 ISO 时失败
解决方案(感谢@MichaelPetch)是在加载 GDT 后设置段寄存器。我的新入口点:
start:
lgdt [gdt_descriptor]
jmp CODE_SEG:.setcs ; Set CS to our 32-bit flat code selector
.setcs:
mov ax, DATA_SEG ; Setup the segment registers with our flat data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, stack_space ; set stack pointer
cli ; Disable interrupts
mov esp, stack_space
call main
hlt
需要设置 GDT 和设置段寄存器,因为 Multiboot 规范不保证 GDT Record 有效,也不保证哪个选择器编号用于代码段,哪个选择器编号用于数据段分割。因此,您需要加载 GDT 并使用特定于 GDT 的选择器值。未能正确设置代码段 (CS) 选择器可能会在第一次中断发生时导致问题。
我还在主要方法中注释掉了load_gdt()
,这样我就不会重复了。
再次感谢你,迈克尔。如果你 post 作为答案,我一定会接受你的:)
我已经阅读了大量关于 OS x86 开发的教程,到目前为止一切顺利 - 直到现在。我无法弄清楚我一生的解决方案。
我的目标是为 x86 编写最简单的键盘驱动程序。 QEMU 运行良好,但 GRUB 运行不佳。
我尽力模仿 mkeykernel based on the article by Arjun Sreedharan。不幸的是,mkeykernel也存在这个问题。
当 运行 使用 qemu-system-i386 -kernel kernel.bin
编译我的内核时,一切都按预期工作:我输入,字母显示在屏幕上。
但是,当我使用 grub-mkrescue
创建并 运行 GRUB ISO 时,只要我按下一个键,系统就会重新启动。
当 运行 qemu-system-i386 -cdrom build/myos.iso -d int --no-reboot
时,我发现 CPU 异常是 0xd General Protection Fault。起初,我认为这是因为 GRUB 以一种意想不到的方式设置了 GDT。但是正如您将在下面看到的,我添加了自己的 GDT,但并没有解决问题。
我还在 Whosebug
这是我第一次被难到写了一个 Whosebug 问题 :) 希望有人能在这里看到这个问题!
我已经包含了所有相关文件的源代码和构建它们/重现问题的说明。
第一个文件:kernel.asm
bits 32
section .multiboot
dd 0x1BADB002 ; Magic number
dd 0x0 ; Flags
dd - (0x1BADB002 + 0x0) ; Checksum
section .text
%include "gdt.asm"
; Make global anything that is used in main.c
global start
global print_char_with_asm
global load_gdt
global load_idt
global keyboard_handler
global ioport_in
global ioport_out
global enable_interrupts
extern main ; Defined in kernel.c
extern handle_keyboard_interrupt
load_gdt:
lgdt [gdt_descriptor] ; from gdt.asm
ret
load_idt:
mov edx, [esp + 4]
lidt [edx]
ret
enable_interrupts:
sti
ret
keyboard_handler:
pushad
cld
call handle_keyboard_interrupt
popad
iretd
ioport_in:
mov edx, [esp + 4]
in al, dx
ret
ioport_out:
mov edx, [esp + 4]
mov eax, [esp + 8]
out dx, al
ret
print_char_with_asm:
; OFFSET = (ROW * 80) + COL
mov eax, [esp + 8] ; eax = row
mov edx, 80 ; 80 (number of cols per row)
mul edx ; now eax = row * 80
add eax, [esp + 12] ; now eax = row * 80 + col
mov edx, 2 ; * 2 because 2 bytes per char on screen
mul edx
mov edx, 0xb8000 ; vid mem start in edx
add edx, eax ; Add our calculated offset
mov eax, [esp + 4] ; char c
mov [edx], al
ret
start:
cli ; Disable interrupts
mov esp, stack_space
call main
hlt
section .bss
resb 8192 ; 8KB for stack
stack_space:
第二个文件:kernel.c
// ----- Pre-processor constants -----
#define ROWS 25
#define COLS 80
// IDT_SIZE: Specific to x86 architecture
#define IDT_SIZE 256
// KERNEL_CODE_SEGMENT_OFFSET: the first segment after the null segment in gdt.asm
#define KERNEL_CODE_SEGMENT_OFFSET 0x8
// 32-bit Interrupt gate: 0x8E
// ( P=1, DPL=00b, S=0, type=1110b => type_attr=1000_1110b=0x8E) (thanks osdev.org)
#define IDT_INTERRUPT_GATE_32BIT 0x8e
// IO Ports for PICs
#define PIC1_COMMAND_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIC2_COMMAND_PORT 0xA0
#define PIC2_DATA_PORT 0xA1
// IO Ports for Keyboard
#define KEYBOARD_DATA_PORT 0x60
#define KEYBOARD_STATUS_PORT 0x64
// ----- Includes -----
#include "keyboard_map.h"
// ----- External functions -----
extern void print_char_with_asm(char c, int row, int col);
extern void load_gdt();
extern void keyboard_handler();
extern char ioport_in(unsigned short port);
extern void ioport_out(unsigned short port, unsigned char data);
extern void load_idt(unsigned int* idt_address);
extern void enable_interrupts();
// ----- Structs -----
struct IDT_pointer {
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct IDT_entry {
unsigned short offset_lowerbits; // 16 bits
unsigned short selector; // 16 bits
unsigned char zero; // 8 bits
unsigned char type_attr; // 8 bits
unsigned short offset_upperbits; // 16 bits
} __attribute__((packed));
// ----- Global variables -----
struct IDT_entry IDT[IDT_SIZE]; // This is our entire IDT. Room for 256 interrupts
int cursor_pos = 0;
void init_idt() {
// Get the address of the keyboard_handler code in kernel.asm as a number
unsigned int offset = (unsigned int)keyboard_handler;
// Populate the first entry of the IDT
// TODO why 0x21 and not 0x0?
// My guess: 0x0 to 0x2 are reserved for CPU, so we use the first avail
IDT[0x21].offset_lowerbits = offset & 0x0000FFFF; // lower 16 bits
IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
IDT[0x21].zero = 0;
IDT[0x21].type_attr = IDT_INTERRUPT_GATE_32BIT;
IDT[0x21].offset_upperbits = (offset & 0xFFFF0000) >> 16;
// Program the PICs - Programmable Interrupt Controllers
ioport_out(PIC1_COMMAND_PORT, 0x11);
ioport_out(PIC2_COMMAND_PORT, 0x11);
// ICW2: Vector Offset (this is what we are fixing)
ioport_out(PIC1_DATA_PORT, 0x20);
ioport_out(PIC2_DATA_PORT, 0x28);
// ICW3: Cascading (how master/slave PICs are wired/daisy chained)
ioport_out(PIC1_DATA_PORT, 0x0);
ioport_out(PIC2_DATA_PORT, 0x0);
// ICW4: "Gives additional information about the environemnt"
ioport_out(PIC1_DATA_PORT, 0x1);
ioport_out(PIC2_DATA_PORT, 0x1);
// Voila! PICs are initialized
// Mask all interrupts
ioport_out(PIC1_DATA_PORT, 0xff);
ioport_out(PIC2_DATA_PORT, 0xff);
struct IDT_pointer idt_ptr;
idt_ptr.limit = (sizeof(struct IDT_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int) &IDT;
// Now load this IDT
load_idt(&idt_ptr);
}
void kb_init() {
// 0xFD = 1111 1101 in binary. enables only IRQ1
ioport_out(PIC1_DATA_PORT, 0xFD);
}
void handle_keyboard_interrupt() {
// Write end of interrupt (EOI)
ioport_out(PIC1_COMMAND_PORT, 0x20);
unsigned char status = ioport_in(KEYBOARD_STATUS_PORT);
// Lowest bit of status will be set if buffer not empty
// (thanks mkeykernel)
if (status & 0x1) {
char keycode = ioport_in(KEYBOARD_DATA_PORT);
if (keycode < 0 || keycode >= 128) return;
print_char_with_asm(keyboard_map[keycode],0,cursor_pos);
cursor_pos++;
}
}
void clear_screen() {
int i, j;
for (i = 0; i < COLS; i++) {
for (j = 0; j < ROWS; j++) {
print_char_with_asm(' ',j,i);
}
}
}
// ----- Entry point -----
void main() {
clear_screen();
load_gdt();
init_idt();
kb_init();
enable_interrupts();
while(1);
}
第三个文件:gdt.asm
(主要基于 this handy guide)
; GDT - Global Descriptor Table
gdt_start:
gdt_null: ; Entry 1: Null entry must be included first (error check)
dd 0x0 ; double word = 4 bytes = 32 bits
dd 0x0
gdt_code: ; Entry 2: Code segment descriptor
; Structure:
; Segment Base Address (base) = 0x0
; Segment Limit (limit) = 0xfffff
dw 0xffff ; Limit bits 0-15
dw 0x0000 ; Base bits 0-15
db 0x00 ; Base bits 16-23
; Flag Set 1:
; Segment Present: 0b1
; Descriptor Privilege level: 0x00 (ring 0)
; Descriptor Type: 0b1 (code/data)
; Flag Set 2: Type Field
; Code: 0b1 (this is a code segment)
; Conforming: 0b0 (Code w/ lower privilege may not call this)
; Readable: 0b1 (Readable or execute only? Readable means we can read code constants)
; Accessed: 0b0 (Used for debugging and virtual memory. CPU sets bit when accessing segment)
db 10011010b ; Flag set 1 and 2
; Flag Set 3
; Granularity: 0b1 (Set to 1 multiplies limit by 4K. Shift 0xfffff 3 bytes left, allowing to span full 32G of memory)
; 32-bit default: 0b1
; 64-bit segment: 0b0
; AVL: 0b0
db 11001111b ; Flag set 3 and limit bits 16-19
db 0x00 ; Base bits 24-31
gdt_data:
; Same except for code flag:
; Code: 0b0
dw 0xfffff ; Limit bits 0-15
dw 0x0000 ; Base bits 0-15
db 0x00 ; Base bits 16-23
db 10010010b ; Flag set 1 and 2
db 11001111b ; 2nd flags and limit bits 16-19
db 0x00 ; Base bits 24-31
gdt_end: ; Needed to calculate GDT size for inclusion in GDT descriptor
; GDT Descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; Size of GDT, always less one
dd gdt_start
; Define constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
; In protected mode, set DS = INDEX to select GDT entries
; Then CPU knows to use segment at that offset
; Example: (0x0: NULL segment; 0x8: CODE segment; 0x10: DATA segment)
第四个文件:grub.cfg
menuentry "myos" {
multiboot /boot/grub/kernel.bin
}
第五个文件:linker.ld
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 1M;
.text BLOCK(4K) : ALIGN(4K)
{
*(.multiboot)
*(.text)
}
.data : { *(.data) }
.bss : { *(.bss) }
}
糟糕,遗漏了一个文件 - 这里是 keyboard_map.h
:
unsigned char keyboard_map[128] = {
// -------- 0 to 9 --------
' ',
' ', // escape key
'1','2','3','4','5','6','7','8',
// -------- 10 to 19 --------
'9','0','-','=',
' ', // Backspace
' ', // Tab
'q','w','e','r',
// -------- 20 to 29 --------
't','y','u','i','o','p','[',']',
' ', // Enter
' ', // left Ctrl
// -------- 30 to 39 --------
'a','s','d','f','g','h','j','k','l',';',
// -------- 40 to 49 --------
' ','`',
' ', // left Shift
' ','z','x','c','v','b','n',
// -------- 50 to 59 --------
'm',',','.',
'/', // slash, or numpad slash if preceded by keycode 224
' ', // right Shift
'*', // numpad asterisk
' ', // left Alt
' ', // Spacebar
' ',
' ', // F1
// -------- 60 to 69 --------
' ', // F2
' ', // F3
' ', // F4
' ', // F5
' ', // F6
' ', // F7
' ', // F8
' ', // F9
' ', // F10
' ',
// -------- 70 to 79 --------
' ', // scroll lock
'7', // numpad 7, HOME key if preceded by keycode 224
'8', // numpad 8, up arrow if preceded by keycode 224
'9', // numpad 9, PAGE UP key if preceded by keycode 224
'-', // numpad hyphen
'4', // numpad 4, left arrow if preceded by keycode 224
'5', // numpad 5
'6', // numpad 6, right arrow if preceded by keycode 224
' ',
'1', // numpad 1, END key if preceded by keycode 224
// -------- 80 to 89 --------
'2', // numpad 2, down arrow if preceded by keycode 224
'3', // numpad 3, PAGE DOWN key if preceded by keycode 224
'0', // numpad 0, INSERT key if preceded by keycode 224
'.', // numpad dot, DELETE key if preceded by keycode 224
' ',' ',' ',' ',' ',' ',
// -------- 90 to 99 --------
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
// -------- 100 to 109 --------
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
// -------- 110 to 119 --------
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
// -------- 120-127 --------
' ',' ',' ',' ',' ',' ',' ',' ',
};
// Right control, right alt seem to send
// keycode 224, then the left control/alt keycode
// Arrow keys also send two interrupts, one 224 and then their actual code
// Same for numpad enter
// 197: Num Lock
// 157: Pause|Break (followed by 197?)
// Clicking on screen appears to send keycodes 70, 198
// Is this the MARK command or something like that?
将上述所有文件一起粘贴到 Linux 上的一个目录中。那么...
编译内核:
mkdir build
nasm -f elf32 kernel.asm -o build/boot.o
gcc -m32 -ffreestanding -c kernel.c -o build/kernel.o
ld -m elf_i386 -T linker.ld -o build/kernel.bin build/boot.o build/kernel.o
到 运行 带有 QEMU 的内核(应该可以正常工作):
qemu-system-i386 -kernel build/kernel-bin
到 运行 使用 GRUB 的内核(不起作用):
mkdir -p build/iso/boot/grub
cp grub.cfg build/iso/boot/grub
cp build/kernel.bin build/iso/boot/grub
grub-mkrescue -o build/myos.iso build/iso
qemu-system-i386 -cdrom build/myos.iso
有没有人运行以前处理过这个问题?作为 x86 初学者,您是否有其他资源可以推荐获取键盘?我真的很想用我的小 mini-OS!
最终获得一些保护模式的用户输入我应该使用除 GRUB 之外的其他引导加载程序吗?
TLDR:简单的键盘驱动程序与 QEMU -kernel
选项一起工作,但在使用 grub-mkrescue
.
解决方案(感谢@MichaelPetch)是在加载 GDT 后设置段寄存器。我的新入口点:
start:
lgdt [gdt_descriptor]
jmp CODE_SEG:.setcs ; Set CS to our 32-bit flat code selector
.setcs:
mov ax, DATA_SEG ; Setup the segment registers with our flat data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, stack_space ; set stack pointer
cli ; Disable interrupts
mov esp, stack_space
call main
hlt
需要设置 GDT 和设置段寄存器,因为 Multiboot 规范不保证 GDT Record 有效,也不保证哪个选择器编号用于代码段,哪个选择器编号用于数据段分割。因此,您需要加载 GDT 并使用特定于 GDT 的选择器值。未能正确设置代码段 (CS) 选择器可能会在第一次中断发生时导致问题。
我还在主要方法中注释掉了load_gdt()
,这样我就不会重复了。
再次感谢你,迈克尔。如果你 post 作为答案,我一定会接受你的:)