我的博客: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++