通过 8042 PS/2 控制器重置后,QEMU 不会重启我的 OS

QEMU doesn't reboot my OS after resetting via the 8042 PS/2 controller

@MichaelPetch 重写了整个问题,将其简化为一个易于重现的特定问题。 original question focussed on a problem encountered doing OS development in 64-bit long mode. The code was attempting to use 8042 PS/2 controller to reboot a machine but it failed to work on QEMU, although it did work in BOCHS. The original code can be found in this Github project.

迈克尔确定问题不是长模式特定的。问题 space 已大幅减少以更好地说明核心问题。


对于这个演示,我是:

本次演示代码如下:

bootloader.asm:

[BITS 32]

section .mboot
mboot_header_start:
    dd 0xe85250d6
    dd 0
    dd mboot_header_end - mboot_header_start
    dd 0x100000000 - (0xe85250d6 + 0 +(mboot_header_end - mboot_header_start))

    align 8
mboot_inforeq_start:
    dw 1
    dw 0
    dd mboot_inforeq_end - mboot_inforeq_start
    dd 2
    dd 6
    dd 8
mboot_inforeq_end:

    align 8
mboot_end_start:
    dw 0
    dw 0
    dd mboot_end_end - mboot_end_start
mboot_end_end:

mboot_header_end:

section .text
global _start
_start:
    mov word [0xb8000], (0x5f << 8) | 'B'
    mov word [0xb8002], (0x5f << 8) | 'O'
    mov word [0xb8004], (0x5f << 8) | 'O'
    mov word [0xb8006], (0x5f << 8) | 'T'

    ; Delay after writing to the screen so it appears for a bit of time before reboot
    mov ecx, 0xfffff
delay:
    loop delay

    ; Wait until the 8042 PS/2 Controller is ready to be sent a command
wait_cmd_ready:
    in al, 0x64
    test al, 00000010b
    jne  wait_cmd_ready

    ; Use 8042 PS/2 Controller to reboot the machine
    mov al, 0xfe
    out 0x64, al

    ; If this is displayed the reboot wasn't successful. Shouldn't get this far
    mov word [0xb8000+160], (0x5f << 8) | 'N'
    mov word [0xb8002+160], (0x5f << 8) | 'O'
    mov word [0xb8004+160], (0x5f << 8) | 'R'
    mov word [0xb8006+160], (0x5f << 8) | 'E'
    mov word [0xb8006+160], (0x5f << 8) | 'B'

    ; Infinite loop to end
hltloop:
    hlt
    jmp hltloop

link.ld:

ENTRY(_start);

kern_vma = 0x100000;

SECTIONS
{
    . = 0x500;
    .boot :
    {
        *(*.mboot*)
    }
    . = kern_vma;
    .text ALIGN(4K) :
    {
         *(*.text*)
    }
    .bss ALIGN(4K) :
    {
         *(.bss)
    }
}

我的 Linux 构建脚本是:

#!/bin/sh

ISO_DIR="isodir"
ISO_NAME="myos"
GRUB_CFG="grub.cfg"
KERNEL_NAME="bootloader"

nasm -f elf32 bootloader.asm -o bootloader.o
ld -m elf_i386 -T link.ld bootloader.o -o $KERNEL_NAME.elf

mkdir -p $ISO_DIR/boot/grub
cp $KERNEL_NAME.elf $ISO_DIR/boot/

echo 'set timeout=2'                          >  $ISO_DIR/boot/grub/$GRUB_CFG
echo 'set default=0'                          >> $ISO_DIR/boot/grub/$GRUB_CFG
echo 'menuentry "My Kernel" {'                >> $ISO_DIR/boot/grub/$GRUB_CFG
echo '  multiboot2 /boot/'$KERNEL_NAME'.elf'  >> $ISO_DIR/boot/grub/$GRUB_CFG
echo '}'                                      >> $ISO_DIR/boot/grub/$GRUB_CFG

# build iso image
grub-mkrescue -o $ISO_NAME.iso $ISO_DIR/

问题

当我 运行 构建脚本并在 QEMU 中使用此命令 运行 它时:

qemu-system-i386 -cdrom myos.iso

GRUB 引导内核,BOOT 在 window 的左上角正确显示洋红色属性为白色。它应该在重新引导机器加载 GRUB 并重复循环之前短暂等待。

它没有达到预期的效果。它按应有的方式显示 BOOT,但 QEMU 似乎坐着不动,这不是我所期望的。

如果我 运行 QEMU 带有附加选项 -d int 我发现机器似乎处于由无效操作码异常 (v=06) 组成的永久循环中,一般保护错误(v=0d),并以双重错误 (v=08) 结束。输出通常类似于:

     0: v=06 e=0000 i=0 cpl=0 IP=0008:000f0000 pc=000f0000 SP=0010:00000fc0 env->regs[R_EAX]=00000000
EAX=00000000 EBX=00000000 ECX=00000010 EDX=000f171d
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000fc0
EIP=000f0000 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6080 00000037
IDT=     000f60be 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=0000007f CCO=ADDB
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     1: v=0d e=0032 i=0 cpl=0 IP=0008:000f0000 pc=000f0000 SP=0010:00000fc0 env->regs[R_EAX]=0000
0000
EAX=00000000 EBX=00000000 ECX=00000010 EDX=000f171d
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000fc0
EIP=000f0000 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6080 00000037
IDT=     000f60be 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=0000007f CCO=ADDB
EFER=0000000000000000
check_exception old: 0xd new 0xd
     2: v=08 e=0000 i=0 cpl=0 IP=0008:000f0000 pc=000f0000 SP=0010:00000fc0 env->regs[R_EAX]=0000
0000
EAX=00000000 EBX=00000000 ECX=00000010 EDX=000f171d
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000fc0
EIP=000f0000 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6080 00000037
IDT=     000f60be 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=0000007f CCO=ADDB
EFER=0000000000000000
check_exception old: 0x8 new 0xd
check_exception old: 0xffffffff new 0x6

它不断重复类似的模式。不寻常的是,它似乎陷入了这个异常循环:

0: v=06 e=0000 i=0 cpl=0 IP=0008:000f0000 pc=000f0000 SP=0010:00000fc0 env->regs[R_EAX]=00000000
1: v=0d e=0032 i=0 cpl=0 IP=0008:000f0000 pc=000f0000 SP=0010:00000fc0 env->regs[R_EAX]=0000
2: v=08 e=0000 i=0 cpl=0 IP=0008:000f0000 pc=000f0000 SP=0010:00000fc0 env->regs[R_EAX]=0000

导致此问题的原因是什么?我该如何解决才能使 QEMU 正确重启并再次启动 GRUB?

original Operating System code 需要付出努力才能确定此问题的根本原因。该示例使人们更容易发现潜在的罪魁祸首。在原代码中发现这个问题费了一番功夫


主要问题是您已将多重引导部分(带有多重引导2 header)放置在链接描述文件中的虚拟内存地址 (VMA) 0x500 处:

SECTIONS
{
    . = 0x500;
    .boot :
    {
        *(*.mboot*)
    }
    . = kern_vma;
    .text ALIGN(4K) :
    {
         *(*.text*)
    }
    .bss ALIGN(4K) :
    {
         *(.bss)
    }
}

当链接器发出 .boot 部分时,VMA 和加载内存地址 (LMA) 均设置为 0x500。问题是 GRUB 在读取您的 ELF 可执行文件时会尝试在内存地址 0x500 处加载此部分。将任何代码和数据放在 0x100000 以下是一个非常糟糕的主意。 GRUB 将使用此内存区域来执行所有与引导相关的任务,包括加载您的内核。您可能会无意中覆盖 GRUB 正在使用的内存,从而可能使机器处于未定义状态。它可能适用于某些机器 运行 GRUB 而不是其他机器。问题可能会以其他方式表现出来。

Multiboot2 规范关于 Multiboot2 位置的唯一规则 header 是它在四字字边界(64 位)上对齐并位于 ELF 文件的前 32,768 字节内。您不需要将 Multiboot2 header 放在 0x100000 以下。那是不必要的。将它放在 0x100000 以上的代码和数据之前。这应该有效:

ENTRY(_start);

kern_vma = 0x100000;

SECTIONS
{
    . = kern_vma;
    .boot :
    {
        *(*.mboot*)
    }
    .text ALIGN(4K) :
    {
         *(*.text*)
    }
    .bss ALIGN(4K) :
    {
         *(.bss)
    }
}

在你的例子中,在 0x500 处写入 0x100000 以下的内存会阻止 QEMU 正确重新启动,从而阻止 GRUB 重新启动。我不确定失败的确切性质,但在这种情况下结果非常明显。

如果使用原始 Multiboot specification 引导,同样适用,尽管多重引导 header 只需要在双字边界(32 位)上对齐并且在前 8192 字节内ELF 可执行文件。