【发布时间】:2018-06-04 06:26:27
【问题描述】:
我最近在从头开发操作系统时进入了保护模式。我已经设法进入 C 语言并制作了将字符打印到屏幕上的功能(感谢 Michael Petch 帮助我达到这个阶段)。无论如何,每当我尝试创建一个循环遍历字符串文字并打印其中的每个字符的例程时,都会出现一些问题。 QEMU 只是进入一个引导循环,一次又一次地重新启动,我永远无法看到我漂亮的黑底绿视频模式。如果我将其从例程中移出并在kmain() 函数(我已删除的部分)中逐个字符地打印它,一切正常且花花公子。这是我尝试实现字符串打印功能的文件:
vga.c -
#include <vga.h>
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t *terminal_buffer;
volatile uint16_t * const VIDMEM = (volatile uint16_t *) 0xB8000;
size_t strlen(const char *s)
{
size_t len = 0;
while(s[len]) {
len++;
}
return len;
}
void terminal_init(void)
{
terminal_row = 0;
terminal_column = 0;
terminal_color = vga_entry_color(LGREEN, BLACK);
for(size_t y = 0; y < VGA_HEIGHT; y++) {
for(size_t x = 0; x < VGA_WIDTH; x++) {
const size_t index = y * VGA_WIDTH + x;
VIDMEM[index] = vga_entry(' ', terminal_color);
}
}
}
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
const size_t index = y * VGA_WIDTH + x;
VIDMEM[index] = vga_entry(c, color);
}
void terminal_putchar(char c)
{
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
if(++terminal_column == VGA_WIDTH) {
terminal_column = 0;
if(++terminal_row == VGA_HEIGHT) {
terminal_row = 0;
}
}
}
void terminal_puts(const char *s)
{
size_t n = strlen(s);
for (size_t i=0; i < n; i++) {
terminal_putchar(s[i]);
}
}
我使用这个引导加载程序代码将我的内核读入内存:
extern kernel_start ; External label for start of kernel
global boot_start ; Make this global to suppress linker warning
bits 16
boot_start:
xor ax, ax ; Set DS to 0. xor register to itselfzeroes register
mov ds, ax
mov ss, ax ; Stack just below bootloader SS:SP=0x0000:0x7c00
mov sp, 0x7c00
mov ah, 0x00
mov al, 0x03
int 0x10
load_kernel:
mov ah, 0x02 ; call function 0x02 of int 13h (read sectors)
mov al, 0x01 ; read one sector (512 bytes)
mov ch, 0x00 ; track 0
mov cl, 0x02 ; sector 2
mov dh, 0x00 ; head 0
; mov dl, 0x00 ; drive 0, floppy 1. Comment out DL passed to bootloader
xor bx, bx ; segment 0x0000
mov es, bx ; segments must be loaded from non immediate data
mov bx, 0x7E00 ; load the kernel right after the bootloader in memory
.readsector:
int 13h ; call int 13h
jc .readsector ; error? try again
jmp 0x0000:kernel_start ; jump to the kernel at 0x0000:0x7e00
我的内核开始时有一个程序集存根,它进入保护模式,将 BSS 部分归零,发出 CLD 并调用我的 C 代码:
; These symbols are defined by the linker. We use them to zero BSS section
extern __bss_start
extern __bss_sizel
; Export kernel entry point
global kernel_start
; This is the C entry point defined in kmain.c
extern kmain ; kmain is C entry point
bits 16
section .text
kernel_start:
cli
in al, 0x92
or al, 2
out 0x92, al
lgdt[toc]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:start32 ; The FAR JMP is simplified since our segment is 0
section .rodata
gdt32:
dd 0
dd 0
dw 0x0FFFF
dw 0
db 0
db 0x9A
db 0xCF
db 0
dw 0x0FFFF
dw 0
db 0
db 0x92
db 0xCF
db 0
gdt_end:
toc:
dw gdt_end - gdt32 - 1
dd gdt32 ; The GDT base is simplified since our segment is now 0
bits 32
section .text
start32:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9c000 ; Set the stack to grow down from area under BDA/Video memory
; We need to zero out the BSS section. We'll do it a DWORD at a time
cld
lea edi, [__bss_start] ; Start address of BSS
lea ecx, [__bss_sizel] ; Lenght of BSS in DWORDS
xor eax, eax ; Set to 0x00000000
rep stosd ; Do clear using string store instruction
call kmain
我有一个专门的链接器脚本,它将引导加载程序放在 0x7c00,内核放在 0x7e00。
有什么问题,我该如何解决?如果需要更多信息,我已经提供了我的git repo。
【问题讨论】:
-
代码有问题,请不要存储库。
-
@Martin 对不起!我现在只能使用手机,所以我无法正确格式化它
-
您曾经安装过 IDT 吗?!现在是个好时机。这看起来像一个 三重错误,当 双重错误 处理程序 cr*ps 事情时会发生这种情况。
-
@AnttiHaapala IDT ... 中断描述符表? (根据 de.wikipedia.org,Internationale Deutschlehrertagung 也会匹配。)
-
@Scheff 当然,但我们不关心这里的德国人:)
标签: c string x86 osdev protected-mode