在 64 位 UEFI 操作系统中实现用户模式和内核模式切换

问题描述

我正在编写 64 位 UEFI 操作系统(gnu-efi - 引导加载程序)。我想知道操作系统中的用户模式和内核模式,我必须在我的操作系统中实现用户模式和内核模式,我在互联网上找到了一些但它对我不起作用(我认为这是因为 64 位),那么我该怎么做呢?

我用过这个:

OSDEV - Ring3

但是当我在我的 gdt 中实现它时,我的内核挂了!

我的内核代码

评论不是我的普通代码,这些代码是从 OSDEV 复制的 - 这些挂出内核,所以我评论了那些。

gdt.h

// gdt.h

#pragma once
#include <stdint.h>

struct GDTDescriptor {
    uint16_t Size;
    uint64_t Offset;
} __attribute__((packed));

struct GDTEntry {
    uint16_t Limit0;
    uint16_t Base0;
    uint8_t Base1;
    uint8_t AccessByte;
    uint8_t Limit1_Flags;
    uint8_t Base2;
    
    // These are from OSDEV,if I enable these,Kernel Hangs Out

    // unsigned int Read_Write;
    // unsigned int Confirming_Expand_Down;
    // unsigned int Code;
    // unsigned int Code_data_segment;
    // unsigned int dpl;
    // unsigned int present;
    // unsigned int available;
    // unsigned int long_mode;
    // unsigned int big;
    // unsigned int gran;

}__attribute__((packed));

struct GDT {
    GDTEntry Null; //0x00
    GDTEntry KernelCode; //0x08
    GDTEntry KernelData; //0x10
    GDTEntry UserNull;
    GDTEntry UserCode;
    GDTEntry UserData;
} __attribute__((packed)) 
__attribute((aligned(0x1000)));

extern GDT DefaultGDT;

extern "C" void LoadGDT(GDTDescriptor* gdtDescriptor);

gdt.cpp

// gdt.cpp
#include "gdt.h"


__attribute__((aligned(0x1000)))
GDT DefaultGDT = {
    {0,0x00,0},// null
    {0,0x9a,0xa0,// kernel code segment
    {0,0x92,// kernel data segment
    {0,// user null
    {0,// user code segment    
    {0,// user data segment
};


// This is for commented vars of GDTEntry Struct

// __attribute__((aligned(0x1000)))
// GDT DefaultGDT = {
//     {0,// null
//     {0,// kernel code segment
//     {0,// kernel data segment

//     {0,// user null
    
//     {0xFFFF,0xF,1,3,1},// user code segment

//     {0xFFFF,// user code segment
// };

gdt.asm

[bits 64]
LoadGDT:   
    lgdt [rdi]
    mov ax,0x10 
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    pop rdi
    mov rax,0x08
    push rax
    push rdi
    retfq
GLOBAL LoadGDT

64位UEFI OS如何进入Ring3,然后如何将用户模式切换到内核模式?

解决方法

您引用的 GDT 入口结构 from OSDev 如下所示:

struct gdt_entry_bits {
    unsigned int limit_low              : 16;
    unsigned int base_low               : 24;
    unsigned int accessed               :  1;
    unsigned int read_write             :  1; // readable for code,writable for data
    unsigned int conforming_expand_down :  1; // conforming for code,expand down for data
    unsigned int code                   :  1; // 1 for code,0 for data
    unsigned int code_data_segment      :  1; // should be 1 for everything but TSS and LDT
    unsigned int DPL                    :  2; // privilege level
    unsigned int present                :  1;
    unsigned int limit_high             :  4;
    unsigned int available              :  1; // only used in software; has no effect on hardware
    unsigned int long_mode              :  1;
    unsigned int big                    :  1; // 32-bit opcodes for code,uint32_t stack for data
    unsigned int gran                   :  1; // 1 to use 4k page addressing,0 for byte addressing
    unsigned int base_high              :  8;
} __packed; // or `__attribute__((packed))` depending on compiler

这个结构体定义使用了bit fields;请注意每个字段声明的结尾,其中有一个冒号,后跟该字段占用的位数。尽管每个字段都有自己的类型声明,但字段将根据其位宽打包在一起; OSDev 示例的这些位宽之和为 64。

您所写的定义(如果包含注释字段)为 48 字节宽。 GDT 条目应该是 8 字节(64 位)宽:参见 Intel Software Developer's Manual 的第 3.4.5 节。我强烈建议将英特尔手册放在手边; OSDev wiki 上的大部分信息对于 x86_64 来说都是过时的,而 Intel(和 AMD)手册则不断更新。

关于内核模式和用户模式之间的切换,x86_64 在 SYSCALLSYSRET 中有专门的系统调用指令。我建议使用这些指令在内核和用户模式之间切换,例如Linux 内核通常会这样做(尽管在某些情况下它也使用 IRET)。即使进程没有通过正确设置环境生成 SYSRET,您也可以使用 SYSCALL 跳转到用户代码(请参阅链接的指令参考)。