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

本文介绍了从零开始实现操作系统虚拟内存管理的过程,包括内核映射和页表的创建。详细讨论了如何将内核映射到虚拟地址空间的3G-4G,并创建临时页表以解决启动阶段的问题。内容涵盖了链接器脚本的修改、内核栈的调整以及分页机制的开启。在调试过程中,解决了因分页开启前调用某些函数导致的错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我的博客:startcraft

虚拟内存管理startcraft

虚拟内存就是对每一个进程而言,对它来说它认为它独占所有4G内存,进程内的地址就是以这4G的虚拟内存来表示的,当要执行时,cpu通过分段机制和分页机制将虚拟地址转换成物理内存地址进行访问。同时一个进程也不是所有的页都在内存中,只有部分在内存中,当需要的页不在内存时产生一个缺页中断,然后进行调度,将需要的页调入内存

仿照linux的设计,对于一个进程的4G虚拟空间3G-4G的空间给系统内核,0-3G给用户程序,现在要将内核映射到虚拟地址空间的3G-4G,但是映射完加载内核就需要页表来指示正式的物理内存地址,但是内核不加载就没有页表,所有需要一个临时的页表

内核的映射

修改链接器的脚本script/kernel.ld

/*
 * * kernel.ld -- 针对 kernel 格式所写的链接脚本
 * */

ENTRY(start)
SECTIONS
{
    PROVIDE( kern_start = 0xC0100000);
    /* 段起始位置 */
    . = 0x100000;
    .init.text :
    {
        *(.init.text)
        . = ALIGN(4096);
    }
    .init.data :
    {
        *(.init.data)
        . = ALIGN(4096);
    }
    . += 0xC0000000;
    .text : AT(ADDR(.text) - 0xC0000000)
    {
        *(.text)
        . = ALIGN(4096);
    }

    .data : AT(ADDR(.data) - 0xC0000000)
    {
        *(.data)
        *(.rodata)
        . = ALIGN(4096);
    }

    .bss : AT(ADDR(.bss) - 0xC0000000)
    {
        *(.bss)
        . = ALIGN(4096);
    }

    .stab : AT(ADDR(.stab) - 0xC0000000)
    {
        *(.stab)
        . = ALIGN(4096);
    }

    .stabstr : AT(ADDR(.stabstr) - 0xC0000000)
    {
        *(.stabstr)
        . = ALIGN(4096);
    }
    PROVIDE( kern_end = . );
    /DISCARD/ : { *(.comment) *(.eh_frame) }
}

第8行修改了内核的加载地址为3G,然后新增的两个.init段放临时页表和函数,这两个段放在0x100000处给grub加载,然后将当前地址加上0xC0000000的偏移量
后面的部分和原来的区别就是加了AT(ADDR(.xxxx) - 0xC0000000)这些,这些是指明区段所载入内存的实际地址,所以将当前偏移量减去0xC0000000就是实际加载地址
链接器修改了,相应的其他代码也要修改
boot/boot.s

......

[BITS 32] ; 所有代码以 32-bit 的方式编译
section .init.text  ; 临时代码段从这里开始

; 在代码段的起始位置设置符合 Multiboot 规范的标记

dd MBOOT_HEADER_MAGIC ; GRUB 会通过这个魔数判断该映像是否支持
dd MBOOT_HEADER_FLAGS ; GRUB 的一些加载时选项,其详细注释在定义处
dd MBOOT_CHECKSUM ; 检测数值,其含义在定义处

[GLOBAL start] ; 向外部声明内核代码入口,此处提供该声明给链接器
[GLOBAL mboot_ptr_tmp] ; 向外部声明 struct multiboot * 变量
[EXTERN kern_entry] ; 声明内核 C 代码的入口函数

start:
cli ; 此时还没有设置好保护模式的中断处理,要关闭中断
; 所以必须关闭中断
mov [mboot_ptr_tmp], ebx ; 将 ebx 中存储的指针存入全局变量
mov esp, STACK_TOP ; 设置内核栈地址
and esp, 0FFFFFFF0H ; 栈地址按照字节对齐16
mov ebp, 0 ; 帧指针修改为 0
call kern_entry ; 调用内核入口函数
stop:
hlt ; 停机指令,可以降低 CPU 功耗
jmp stop ; 到这里结束,关机什么的后面再说

;-----------------------------------------------------------------------------
section .init.data  ; 开启分页前临时数据段
stack:  times 1024 db 0 ; 临时内核栈
STACK_TOP equ $-stack-1 ; 内核栈顶,$ 符指代是当前地址
mboot_ptr_tmp: dd 0 ;临时的全局multiboot结构体指针

第五行修改代码段从.init.text开始,同时指定kern_entry()函数在代码段.init.text处,并且在该函数中定义临时页表,切换到高虚拟地址的kern_init()执行,并且切换内核栈和multiboot结构体指针
修改include/pmm.h

#ifndef INCLUDE_PMM_H
#define INCLUDE_PMM_H

#include "multiboot.h" 

// 内核文件在内存中的起始和结束位置
// 在链接器脚本中要求链接器定义
extern uint8_t kern_start[];
extern uint8_t kern_end[];
extern uint32_t phy_mem_count;//动态分配的物理内存总数

#define PMM_MAX_SIZE 0x20000000//规定最大的物理内存为512MB
#define PMM_PAGE_SIZE 0x1000 //一页的大小为4KB
#define PAGE_MAX_SIZE (PMM_MAX_SIZE/PMM_PAGE_SIZE)//最多的物理页面的数量
#define STACK_SIZE 8192//线程栈的大小
#define PHY_PAGE_MASK 0xFFFFF000//页掩码按照 4096 对齐地址

//打印物理内存布局
void show_memory_map();

void init_pmm();//初始化内存布局
uint32_t pmm_alloc_page();//申请一页物理页,返回该页的地址
void pmm_free_page(uint32_t p);//释放申请的内存
#endif// INCLUDE_PMM_H

修改init/entry.c

#include "console.h" 
#include "timer.h"
#include "debug.h"
#include "gdt.h" 
#include "idt.h"
#include "pmm.h" 
#include "vmm.h" 

//内核初始化函数
void kern_init();
// 开启分页机制之后的 Multiboot 数据指针
multiboot_t *glb_mboot_ptr;
// 开启分页机制之后的内核栈
char kern_stack[STACK_SIZE];

// 内核使用的临时页表和页目录
// 该地址必须是页对齐的地址,内存 0-640KB 肯定是空闲的
__attribute__((section(".init.data"))) pgd_t *pgd_tmp = (pgd_t *)0x1000;
__attribute__((section(".init.data"))) pgd_t *pte_low = (pgd_t *)0x2000;
__attribute__((section(".init.data"))) pgd_t *pte_hign = (pgd_t *)0x3000;
// 内核入口函数
__attribute__((section(".init.text"))) void kern_entry()
{
   
    pgd_tmp[0] = (uint32_t)pte_low | PAGE_PRESENT | PAGE_WRITE;
    pgd_tmp[PGD_INDEX(PAGE_OFFSET)] = (uint32_t)pte_hign | PAGE_PRESENT |PAGE_WRITE;
    // 映射内核虚拟地址 4MB 到物理地址的前 4MB
    int i;
    for (i = 0; i < 1024; i++) {
   
       pte_low[i] = (i << 12) | PAGE_PRESENT | PAGE_WRITE;
    } 
    // 映射 0x00000000-0x00400000 的物理地址到虚拟地址 0xC0000000-0xC0400000
    for (i = 0; i < 1024; i++
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值