从零实现一个操作系统-day10

本文详细介绍了从80386时代的保护模式,包括其4GB寻址空间、分段策略、段描述符结构及如何从实模式切换到保护模式。重点讲述了全局段描述符表(GDT)的工作原理,以及如何初始化GDT和设置GDTR寄存器。通过具体的代码实现,展示了如何构建和加载GDT。
摘要由CSDN通过智能技术生成

我的博客:startcraft

保护模式

保护模式是从80386时代引入的,之前的8086只有实模式,实模式有1MB的寻址空间(2 ^ 20),而保护模式有4GB的寻址空间,且支持分页等操作
保护模式对内存段的访问做了限制,对于内存段的性质和允许的操作给出了定义,所以每一个段有一个段描述符,占8个字节,段描述符有很多,只能放在内存里,其中最重要的全局段描述符表(GDT),GDT的相关信息存在cpu的寄存器GDTR中
GDTR有48位,其中0 - 15位表示GDT的边界,也就是GDT段的大小,16 - 47位这32位存的是GDT的基地址,所以GDT段的大小最多为65536字节,也就是可以存8192个段描述符
也就是我们通过GDTR寄存器获取GDT的位置和大小,然后GDT中存储了所有段描述符


cup加电后工作在实模式,而进入保护模式后就需要GDT和GDTR,所以我们需要在进入保护模式之前就设置好GDT和GDTR,但是实模式只有1MB的寻址空间,所以一开始的GDT只能放在这里,进入保护模式后还可以更改
从实模式进入保护模式只需要修改CR0寄存器的第0位,为0表示实模式,为1表示保护模式

具体的分段策略

分段是cpu为了兼容以前的程序所保留的,所以我们一般绕过分段直接用分页技术,而绕过分段的方法就是把4GB的空间分成一段,整个虚拟地址的其实位置是0,长度为4G,这样偏移地址和段机制处理后的地址是同一个数
当然并不是给所有的段相同的描述符,数据段和代码段是不同的描述符

段描述符的结构

参考: https://blog.csdn.net/longintchar/article/details/50489889
看这个,就不搬运了

代码实现

include/gdt.h
#ifndef INCLUDE_GDT_H_
#define INCLUDE_GDT_H_

#include "types.h"

// 全局描述符类型
typedef
struct gdt_entry_t {
    uint16_t limit_low; // 段界限 15 ~ 0
    uint16_t base_low; // 段基地址 15 ~ 0
    uint8_t base_middle; // 段基地址 23 ~ 16
    uint8_t access; // 段存在位、描述符特权级、描述符类型、描述符子类别
    uint8_t granularity; // 其他标志、段界限 19 ~ 16
    uint8_t base_high; // 段基地址 31 ~ 24
} __attribute__((packed)) gdt_entry_t;

// GDTR
typedef
struct gdt_ptr_t {
    uint16_t limit; // 全局描述符表限长
    uint32_t base; // 全局描述符表 32 位基地址
} __attribute__((packed)) gdt_ptr_t;

// 初始化全局描述符表
void init_gdt();

// GDT 加载到 GDTR 的函数汇编实现[]
extern void gdt_flush(uint32_t);

#endif // INCLUDE_GDT_H_
gdt/gdt.c
#include "gdt.h"
#include "string.h"

// 全局描述符表长度
#define GDT_LENGTH 5

// 全局描述符表定义
gdt_entry_t gdt_entries[GDT_LENGTH];

// GDTR
gdt_ptr_t gdt_ptr;

static void gdt_set_gate(int32_t num, uint32_t base,
                         uint32_t limit, uint8_t access, uint8_t gran);

// 声明内核栈地址
extern uint32_t stack;

// 初始化全局描述符表
void init_gdt()
{
    // 全局描述符表界限 e.g. 从 0 开始,所以总长要 - 1
    gdt_ptr.limit = sizeof(gdt_entry_t) * GDT_LENGTH - 1;
    gdt_ptr.base = (uint32_t)&gdt_entries;

    // 采用 Intel 平坦模型
    gdt_set_gate(0, 0, 0, 0, 0); // 按照 Intel 文档要求,第一个描述符必须全 0
    gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // 指令段
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // 数据段
    gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // 用户模式代码段
    gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // 用户模式数据段

    // 加载全局描述符表地址到 GPTR 寄存器
    gdt_flush((uint32_t)&gdt_ptr);
}

// 全局描述符表构造函数,根据下标构造
// 参数分别是数组下标、基地址、限长、访问标志,其它访问标志
static void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran)
{
    gdt_entries[num].base_low = (base & 0xFFFF);
    gdt_entries[num].base_middle = (base >> 16) & 0xFF;
    gdt_entries[num].base_high = (base >> 24) & 0xFF;

    gdt_entries[num].limit_low = (limit & 0xFFFF);
    gdt_entries[num].granularity = (limit >> 16) & 0x0F;

    gdt_entries[num].granularity |= gran & 0xF0;
    gdt_entries[num].access = access;
}

一点一点来看
首先定义了GDT的长度,这个不是GDT的大小,而是GDT数组的长度,因为GDT就可以看作是一个数组,其中存放的是一个个8字节的段描述符
然后定义了GDT数组gdt_entries和GDTR gdt_ptr
然后是初始化GDT的函数,首先设定该gdt的大小也就是数组长度*元素大小 -1,然后gdt的基地址,也就是数组的起始地址
后面是构造全局段描述符表,首先规定第一个段是全0,段的基地址拆成低16位,中8位和高8位,然后段限长是20位的,其中低16位存在limit_low中,还有4位存在granularity的低4位中,剩下4位是访问标志
设置的描述符,除了后面的标志不同前面的都是基地址为0,限长为4GB


然后是把GDT的描述信息写入GDTR

gdt/gdt_s.s
[GLOBAL gdt_flush]

gdt_flush:
mov eax, [esp + 4] ; 参数存入 eax 寄存器
lgdt [eax] ; 加载到 GDTR 修改原先[设置GRUB]

mov ax, 0x10 ; 加载我们的数据段描述符
mov ds, ax ; 更新所有可以更新的段寄存器
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08: .flush ; 远跳转, 0x08 是我们的代码段描述符
; 远跳目的是清空流水线并串行化处理器
.flush:
ret

最后更改init / entry.c测试一下

#include "gdt.h"
#include "console.h"
#include "debug.h"

int kern_entry()
{
    init_debug();
    init_gdt();

    console_clear();
    printk_color(rc_black, rc_green, "Hello, OS kernel!\n");

    return 0;
}

如果正常输出就ok了

现在的目录结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值