引导加载程序后如何从实模式切换到保护模式?

时间:2016-05-01 16:10:44

标签: x86 operating-system bootloader protected-mode real-mode

我刚刚为我的操作系统完成了一个非常简单的启动程序,现在我试图切换到保护模式并跳转到内核。

内核存在于第二个扇区(紧跟引导加载程序之后)并且开启。

任何人都可以帮我解决我的代码吗?我添加了评论以显示我的困惑。

谢谢。

BITS 16 

global start
start:
    ; initialize bootloader and stack
    mov     ax, 0x07C0
    add     ax, 288
    mov     ss, ax
    mov     sp, 4096
    mov     ax, 0x07C0
    mov     ds, ax

    call    kernel_load
    hlt

kernel_load:
    mov     si, k_load
    call    print

    mov     ax, 0x7C0
    mov     ds, ax
    mov     ah, 2
    mov     al, 1
    push    word 0x1000
    pop     es
    xor     bx, bx
    mov     cx, 2
    mov     dx, 0
    int     0x13

    jnc     .kjump
    mov     si, k_fail
    call    print
    ret

.kjump:
    mov     si, k_succ
    call    print

    ; this is where my confusion starts

    ; switch to protected mode???
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    ; jump to kernel? 
    jmp     0x1000:0

    hlt

data:
    k_load  db "Initializing Kernel...", 10, 0
    k_succ  db "Kernel loaded successfully!", 10, 0
    k_fail  db "Kernel failed to load!", 10, 0

print:
    mov     ah, 0x0E
.printchar:
    lodsb
    cmp     al, 0
    je      .done
    int     0x10
    jmp     .printchar
.done:
    ret

times 510-($-$$) db 0
dw 0xAA55

2 个答案:

答案 0 :(得分:7)

在尝试进入保护模式之前,您需要设置几项内容:

在内存中初始化GDT

您需要内存中的全局描述符表。它至少需要这些选择器的空间:

  • 您需要一个ring0 32位代码描述符
  • 您需要一个ring0 32位数据描述符
  • 您需要GDT细分
  • 您需要IDT细分
  • 您需要TSS细分
  • 你可能想要一个LDT段(每个进程都应该有一个LDT,它在每个进程中以相同的线性地址开始,然后一个LDT描述符可以处理每个进程,并且分页将处理切换)。

在保护模式下,选择器是GDT或LDT的索引。代码和数据描述符告诉CPU在选择器加载该索引时要使用的基址和内存长度。

LGDT指令设置LTDR

在内存中初始化TSS

TSS段告诉CPU您要在哪里存储TSS。最初内置于TSS中的一些功能非常有用,因为如果手动执行上下文切换会更快。但是,它有一点必不可少:它存储堆栈供内核在进程从ring3转换为ring0时使用。内核无法完全信任调用者 。它不能假设调用者没有发疯并破坏堆栈指针。当从ring3转换到ring0时,CPU从TSS加载堆栈指针,并在推送代码段和偏移返回地址之前将调用程序堆栈段和偏移量推送到内核堆栈。

LTR指令使用TSS段加载任务寄存器。

在内存中初始化IDT

IDT允许CPU查找发生各种事件时要执行的操作。基本目的是异常处理。 CPU将异常实现为中断。操作系统必须为所有异常设置处理程序。

LIDT指令加载IDTR

下面介绍了硬件中断。

如果在处理异常时发生异常,则会发生双重故障异常。如果在处理双重故障时发生异常,则CPU会将其转换为关闭主板的关闭消息。当发生这种情况时,典型的主板将重置CPU,BIOS将在其引导启动代码中看到重置是意外的,并且它将重新启动。

初始化中断控制器

硬件设备还提供硬件中断(与前面提到的软件中断相反)。当设备需要服务时,会发生硬件中断。

如果您打算支持旧机器,那么您需要使用代码来处理8259中断控制器。

您需要代码来处理中断,保存上下文,确认中断,并以某种方式调用驱动程序或将工作项排队到某处以便为硬件提供服务。

中断控制器设置为激活CPU处理中断,当硬件设备断言其中断控制线(在古代系统上),或当MSI中断数据包到达CPU时(在现代系统上能够并配置为使用MSI)。

如果您需要最大功能并需要支持多个处理器,那么您必须......

初始化APIC

APIC正如其名称所说:高级可编程中断控制器。

APIC允许对优先级,屏蔽和处理器间通信进行复杂的控制。在这里真正覆盖它太大而复杂。

初始化分页

分页被分解为两级查找。顶级称为页面目录。第二级称为页表。

每页包含1024个32位页面描述符。高20位是该页表条目的物理地址的高20位。较低的位包含多个允许的标志,并允许操作系统检测内存的使用情况,以便智能地交换/丢弃/保存。

每个页面目录条目描述该内存范围的一个4KB页表的基址。页面目录的每个条目都指向一个页面表,该表可以映射最多4MB的内存。

页表的每个页面描述符描述了4KB内存范围的权限,访问历史和基址。

因此,操作系统必须为页面目录分配至少一个4KB页面,并为每4MB内存提交至少一个4KB页面。请注意,您可能有稀疏映射,其中存在没有内存的大区域,如果您访问它,则会发生页面错误。

使用PGCR0启用分页功能。 PDBR MSR寄存器告诉CPU页面目录的物理地址。

顺序

初始化GDT,IDT,TSS(并在内存中分配内核堆栈内存,用户堆栈内存(如果需要)。

在GDT内存的索引1和2处搜索GDT代码和数据条目,并将它们设置为零基址,4GB限制,ring0。

将CR0位0,PE或保护使能位置1。

跳远

立即跳转到0x10:next-instruction,其中next-instruction可能在链接器中解析为下一行的标签。 (你可以将一个远指针推到堆栈上并通过它间接跳远)。您需要从基址减去(cs<<<<<<<<<<<<<<&#4),因为跳转目标是相对于您在某个任意基础上组装的段,在实模式cs中设置。

在进入保护模式后,必须加载所有段寄存器,因为CPU会执行一系列权限检查,并在CPU中设置多个内部内容,这些内容在保护模式下是不同的。

告诉汇编程序!

请注意,在该分支目标之后,您突然需要以不同方式开始汇编指令。在远程跳转之前,你处于实模式,但是一旦加载了cs,CPU中就会发生很多变化,实际上它改变了解码指令的方式。它假设32位寄存器和地址,地址大小前缀表示它是16位。

在实模式下,反过来说,地址大小或操作数大小前缀告诉它是32位。因此,您需要使用某种汇编程序指令来告诉汇编程序反转这些前缀的用法并更改各种事项以处理32位模式。

显然你需要设置堆栈。在设置LDT,IDT等的描述符地址时,您必须多次处理线性地址。

现在您可以设置页面目录和页面表,加载PBDR

切换页表时,每个页面目录条目都可以标记为不刷新。通常,内核模式对每个进程都有相同的映射。

通常,每个进程都有自己的页面目录,并共享内核表。其用户模式分配是针对用户内存范围的自己的私有页表进行的。

虽然不需要分页,但它可以提供许多非常酷的功能和保护。你可能想要它。

启用分页并加载PDBR后,按照每个定义,您完全处于保护模式,并且已经实现了一大块核心代码,以在x86架构上实现操作系统。

答案 1 :(得分:0)

遗憾的是,仅通过阅读英特尔/ AMD文档无法解决这个问题。看看他们的修订历史,人们不禁会认为这些年来他们的文档仍然存在错误。