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

物理内存管理

由于之前采用了平坦模式,也就是整个内存一段,所以这里就不用管段的处理了。

分页

参考:https://www.cnblogs.com/peterYong/p/6556619.html#_label8
分页就是将主存,程序执行的线性地址空间,还有外存都在逻辑上划分位固定大小的块,这样的好处是降低内存碎片的影响,每一页的大小不能太大,也不能太小,x86采用的是一页4KB,4GB的主存就可以分为2^20个页
对存储空间进行逻辑划分之后,寻址时就要由相应的映射,对于每一个进程来说都有这么一个映射来将自己的逻辑地址映射到物理的地址上,这个映射的结构就是页表,页表也存放在内存中,对于一个进程来说,如果要映射全部4GB的空间需要32/8*2^20=4MB的页表,如果进程多了这会消耗大量内存,显然不合适

x86架构的cpu采用的分页机制是两级页表的方法,即有一个顶级页表,它指向的是一个页表,然后页表指向物理页,顶级页表也就是页表的页表,我们有2^20页,即需要2^20个页表项,一个页表项是4B,一页的大小为4KB,所以需要2^10个页来存放这些页表项,我们的顶级页表映射的就是这2^10个页,所以顶级页表需要的空间也刚好是一页,这样对于一个进程,我们只需要将顶级页表加载入内存就可以了,用到的页表和物理页可以在进程执行的过程中加入,节省了很多空间

这样一个32位的逻辑地址,它的高10位代表的是顶级页表中的偏移量可以映射到一个二级页表,中10位代表的是二级页表中的偏移量映射到一个物理页,最后12位代表的是物理页中的偏移量,这样就找到了物理地址
上面都是理论,现在看实际的代码实现

获取可用的物理内存的大小和地址

GRUB的Mutilboot协议已经帮我们做完了这件事,在之前定义的结构体中就有这些信息
include/mutilboot.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    ......
/**
* 以下两项指出保存由 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......
. = 0x100000;
PROVIDE( kern_start = . );
.text :
{
*(.text)
. = ALIGN(4096);
}

.......

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

在脚本的开始位置和结束位置定义两个变量kern_startkern_end在c代码中声明就可以使用了,分别代表内核的起始地址和结束地址
(.代表的是脚本中的当前地址)
打印出来看看
include/pmm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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;
}

本文标题:从零实现一个操作系统-day13

文章作者:

发布时间:2020年05月14日 - 22:05

最后更新:2020年05月16日 - 01:05

原始链接:http://startcraft.cn/post/4611aa0f.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------