從零實現一個操作系統-day13

我的博客startcraft

物理內存管理

由於之前採用了平坦模式,也就是整個內存一段,所以這裏就不用管段的處理了。

分頁

參考:https://www.cnblogs.com/peterYong/p/6556619.html#_label8
分頁就是將主存,程序執行的線性地址空間,還有外存都在邏輯上劃分位固定大小的塊,這樣的好處是降低內存碎片的影響,每一頁的大小不能太大,也不能太小,x86採用的是一頁4KB,4GB的主存就可以分爲2^20個頁
對存儲空間進行邏輯劃分之後,尋址時就要由相應的映射,對於每一個進程來說都有這麼一個映射來將自己的邏輯地址映射到物理的地址上,這個映射的結構就是頁表,頁表也存放在內存中,對於一個進程來說,如果要映射全部4GB的空間需要32/8*2^20=4MB的頁表,如果進程多了這會消耗大量內存,顯然不合適


x86架構的cpu採用的分頁機制是兩級頁表的方法,即有一個頂級頁表,它指向的是一個頁表,然後頁表指向物理頁,頂級頁表也就是頁表的頁表,我們有220頁,即需要220個頁表項,一個頁表項是4B,一頁的大小爲4KB,所以需要210個頁來存放這些頁表項,我們的頂級頁表映射的就是這210個頁,所以頂級頁表需要的空間也剛好是一頁,這樣對於一個進程,我們只需要將頂級頁表加載入內存就可以了,用到的頁表和物理頁可以在進程執行的過程中加入,節省了很多空間

這樣一個32位的邏輯地址,它的高10位代表的是頂級頁表中的偏移量可以映射到一個二級頁表,中10位代表的是二級頁表中的偏移量映射到一個物理頁,最後12位代表的是物理頁中的偏移量,這樣就找到了物理地址
上面都是理論,現在看實際的代碼實現

獲取可用的物理內存的大小和地址

GRUB的Mutilboot協議已經幫我們做完了這件事,在之前定義的結構體中就有這些信息
include/mutilboot.h

    ......
    /**
    * 以下兩項指出保存由 BIOS 提供的內存分佈的緩衝區的地址和長度
    * mmap_addr 是緩衝區的地址, mmap_length 是緩衝區的總大小
    * 緩衝區由一個或者多個下面的 mmap_entry_t 組成
    */
    uint32_t mmap_length;
    uint32_t mmap_addr;
    ......
   /**
    * size 是相關結構的大小,單位是字節,它可能大於最小值 20
    * base_addr_low 是啓動地址的低32位,base_addr_high 是高 32 位,啓動地址總共有 64 位
    * length_low 是內存區域大小的低32位,length_high 是內存區域大小的高 32 位,總共是 64 位
    * type 是相應地址區間的類型,1 代表可用,所有其它的值代表保留區域 RAM
    */
typedef
    struct mmap_entry_t {
        uint32_t size; // size 是不含 size 自身變量的大小
        uint32_t base_addr_low;
        uint32_t base_addr_high;
        uint32_t length_low;
        uint32_t length_high;
        uint32_t type;
    } __attribute__((packed)) mmap_entry_t; 

GRUB將內存探測結果按分段存儲爲一個mmap_entry_t數組,數組的首地址是mmap_addr,長度爲mmap_length。
我們還要知道內核加載的地址,這一段地址我們不能分配出去
可以根據鏈接器腳本里的值來確定
scripts/kernel.ld

......
. = 0x100000;
PROVIDE( kern_start = . );
.text :
{
*(.text)
. = ALIGN(4096);
}

.......

.stabstr :
{
*(.stabstr)
. = ALIGN(4096);
}
PROVIDE( kern_end = . );
... ...

在腳本的開始位置和結束位置定義兩個變量kern_startkern_end在c代碼中聲明就可以使用了,分別代表內核的起始地址和結束地址
(.代表的是腳本中的當前地址)
打印出來看看
include/pmm.h

#ifndef INCLUDE_PMM_H
#define INCLUDE_PMM_H

#include "multiboot.h" 

// 內核文件在內存中的起始和結束位置
// 在鏈接器腳本中要求鏈接器定義
extern uint8_t kern_start[];
extern uint8_t kern_end[];
//打印物理內存佈局
void show_memory_map();
                                                                                                                                                                                                               
#endif// INCLUDE_PMM_H

pmm/pmm.c

#include "multiboot.h"
#include "common.h" 
#include "pmm.h" 
#include "debug.h" 
void show_memory_map()
{
    uint32_t mmap_addr=glb_mboot_ptr->mmap_addr;
    uint32_t mmap_length=glb_mboot_ptr->mmap_length;
    mmap_entry_t * mmap= (mmap_entry_t*)mmap_addr;
    for (uint32_t i=0;i<mmap_length;++i)
    {
        printk("base_addr=0x%X%08X,length=0x%X%08X,type=0x%X\n",(uint32_t)mmap[i].base_addr_high,(uint32_t)mmap[i].base_addr_low,(uint32_t)mmap[i].length_high,(uint32_t)
                mmap[i].length_low,(uint32_t)mmap[i].type);
    }
}

修改init/entry.c測試一下

#include "console.h"                                                                                                                                                                                           
#include "timer.h"
#include "debug.h"
#include "gdt.h" 
#include "idt.h"
#include "pmm.h" 
int kern_entry()
{
    init_debug();
    init_gdt();
    init_idt();
    console_clear();

    printk_color(rc_black, rc_green, "Hello, OS kernel!\n");
    init_timer(100);
    //開啓中斷
    //asm volatile("sti");
    printk("kernel in memory start: 0x%08X\n", kern_start);
    printk("kernel in memory end: 0x%08X\n", kern_end);
    printk("kernel in memory used: %d KB\n\n", (kern_end - kern_start +1023) / 1024);
    show_memory_map();
    return 0;
}

申請和釋放內存的實現

內核佔了76kb,可用內存段有兩段爲0x00000000-0x0009FC00和0x00100000-0x07EE0000
第一段是1MB一下的內存段存在很多指向外部設備的地址,我們不去用它,我們用1MB以上的部分,當然要在操作系統加載的結束地址之後,讓上限就取到512MB,將這部分的地址按照頁的大小放入一個棧中,申請物理地址就從棧中彈出來給申請者,釋放就壓入棧中
修改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)//最多的物理頁面的數量
//打印物理內存佈局
void show_memory_map();

void init_pmm();//初始化內存佈局
uint32_t pmm_alloc_page();//申請一頁物理頁,返回該頁的地址
void pmm_free_page(uint32_t p);//釋放申請的內存
#endif// INCLUDE_PMM_H

修改pmm/pmm.c

#include "multiboot.h" 
#include "common.h" 
#include "pmm.h" 
#include "debug.h" 
static uint32_t pmm_stack[PAGE_MAX_SIZE+1];//存放物理頁框的棧
static uint32_t pmm_stack_top;//物理頁框的棧指針
uint32_t phy_mem_count;//動態分配的頁面數量
void show_memory_map()
{
    uint32_t mmap_addr=glb_mboot_ptr->mmap_addr;
    uint32_t mmap_length=glb_mboot_ptr->mmap_length;
    mmap_entry_t * mmap= (mmap_entry_t*)mmap_addr;
for (mmap = (mmap_entry_t *)mmap_addr; (uint32_t)mmap < mmap_addr +
        mmap_length; mmap++)
    {
        printk("base_addr=0x%X%08X,length=0x%X%08X,type=0x%X\n",(uint32_t)mmap->base_addr_high,(uint32_t)mmap->base_addr_low,(uint32_t)mmap->length_high,(uint32_t)
                mmap->length_low,(uint32_t)mmap->type);
    }
}
void init_pmm()
{
    //獲取GRUB提供的內存描述結構
    mmap_entry_t * mmap_start_addr=(mmap_entry_t*)glb_mboot_ptr->mmap_addr;
    mmap_entry_t * mmap_end_addr=(mmap_entry_t*)glb_mboot_ptr->mmap_addr+glb_mboot_ptr->mmap_length;
    mmap_entry_t *mmap_entry;
    //遍歷該結構尋找可用的內存段
    for(mmap_entry=mmap_start_addr;mmap_entry<mmap_end_addr;mmap_entry++)
    {
        //type爲1表示可用,然後低於1MB的內存我們不用
        if(mmap_entry->type==1&&mmap_entry->base_addr_low==0x00100000)
        {
            uint32_t page_addr=mmap_entry->base_addr_low+(kern_end-kern_start);//獲得內核加載結束的地址
            uint32_t length=mmap_entry->base_addr_low+mmap_entry->length_low; //獲取該內存段的結束地址
            //分配的內存要小於最大支持的內存,也不能超過該內存段的長度
            while(page_addr<=PMM_MAX_SIZE&&page_addr<length)
            {
                pmm_free_page(page_addr);
                page_addr+=PMM_PAGE_SIZE;//向後移動一頁的大小
                phy_mem_count++;
            }
        }
    }
}
//釋放物理內存空間 
void pmm_free_page(uint32_t p)
{   
    assert(pmm_stack_top != PAGE_MAX_SIZE , "out of pmm_stack stack");
    pmm_stack[++pmm_stack_top]=p;
}   
//申請物理內存空間
uint32_t pmm_alloc_page ()
{       
    assert(pmm_stack_top!=0,"out of memory");
    uint32_t page=pmm_stack[pmm_stack_top--];
    return page;
}

修改entry.c測試一下

#include "console.h" 
#include "timer.h"
#include "debug.h"
#include "gdt.h" 
#include "idt.h"
#include "pmm.h" 
int kern_entry()
{
    init_debug();
    init_gdt();
    init_idt();
    console_clear();

    printk_color(rc_black, rc_green, "Hello, OS kernel!\n");
    init_timer(100);
    //
    //asm volatile("sti");
    printk("kernel in memory start: 0x%08X\n", kern_start);
    printk("kernel in memory end: 0x%08X\n", kern_end);
    printk("kernel in memory used: %d KB\n\n", (kern_end - kern_start +1023) / 1024);
    show_memory_map();
    init_pmm();

    printk_color(rc_black, rc_red, "\nThe Count of Physical Memory Page is: %u\n\n", phy_mem_count);

    uint32_t allc_addr = NULL;
    printk_color(rc_black, rc_light_brown , "Test Physical Memory Alloc :\n");
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章