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