mineucore-物理内存管理学习笔记

0x00 bootloader探测真实物理内存大小(bootasm.S)

bootloader通过INT 15h中断,中断号为e820h,获取空闲物理内存信息,将数据按struct e820map的格式保存在虚拟空间的0xc0000000+0x8000处

0x01 使能段页式内存管理机制的设计方案(entry.S)

想要最终实现的段页式的映射关系是:

Virtual Address = Linear Address = Pythal Address + 0xC0000000

实模式情况下,是没有页机制的,ld在链接阶段生成了ucore的虚拟地址,是0xc0100000,但是ucore需要运行在低物理内存空间,所以,从计算机加电到启动段页式管理,映射关系发生了多次的变化,我们通过以下三个阶段去实现这个映射关系:

第一阶段(开启保护模式,创建启动段表)

该阶段是bootloader阶段,此时cpu是实模式,所以地址映射关系如下:
Virtual Address = Linear Address = Pythal Address
虽然ld链接文件说明了ucore内核的装载空间是0xC0100000,但是bootloader会将ucore装载到0x100000中

1
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);

第二阶段(创建初始页目录表,开启分页模式)

在entry.S的kern_entry函数中,初始化了一个临时页目录表和页表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kern_entry:
# load pa of boot pgdir
movl $REALLOC(__boot_pgdir), %eax
movl %eax, %cr3
......
__boot_pgdir:
.globl __boot_pgdir
# map va 0 ~ 4M to pa 0 ~ 4M (temporary)
.long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
.space (KERNBASE >> PGSHIFT >> 10 << 2) - (. - __boot_pgdir) # pad to PDE of KERNBASE
# map va KERNBASE + (0 ~ 4M) to pa 0 ~ 4M
.long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
.space PGSIZE - (. - __boot_pgdir) # pad to PGSIZE

.set i, 0
__boot_pt1:
.rept 1024
.long i * PGSIZE + (PTE_P | PTE_W)
.set i, i + 1
.endr

解释下临时映射的建立:第9到第13行
第一句,将页表的物理地址填入页目录表的第一项,实现map va 0 ~ 4M to pa 0 ~ 4M
.space 空字节数,后面跟数量 .space 4
第二句,填充页目录表,填到0xc0000000那项,KERNBASE = 0xc0000000,PGSHIFT = 12,向右移12位,接着向右移10位,相当于被4K*1k(4M)除,因为页目录表项一项映射的地址空间是4M,然后左移两位,相当于乘4,因为每一项占4字节,然后减去(当前地址减页目录表的地址),相当于减去页目录表第一项的地址
第三句,完成map va KERNBASE + (0 ~ 4M) to pa 0 ~ 4M
第四句,将页目录4K对齐

映射关系为:
Virtual Address = Linear Address = Pythal Address(0~4M的线性地址空间)
Virtual Address = Linear Address = Pythal Address + 0xC0000000(0xC0000000~0xC0000000+4M的线性地址空间)

为什么要这样映射呢?
ucore此时在0~4M的低虚拟地址区域运行,为了保证使能段页机制后ucore能够正常运行,所以添加了Virtual Address = Linear Address = Pythal Address(0~4M的线性地址空间)的映射关系

接下来调整内核的EIP到高虚拟地址

1
2
3
4
5
6
    # update eip
# now, eip = 0x1.....
leal next, %eax
# set eip = KERNBASE + 0x1.....
jmp *%eax
next:

然后清除(0~4M的映射关系)

1
2
3
# unmap va 0 ~ 4M, it's temporary mapping
xorl %eax, %eax
movl %eax, __boot_pgdir

此时的映射关系就只有:
Virtual Address = Linear Address = Pythal Address + 0xC0000000(0xC0000000~0xC0000000+4M的线性地址空间)

第三阶段(完善段表和页表)

将页目录表从(0~4M扩充到0~KMEMSIZE)
最后的映射关系为:

Virtual Address = Linear Address = Pythal Address + 0xC0000000

0x02初始化物理内存管理(pmm.c:page_init)

从物理内存获取空闲内存布局struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); 通过物理布局获得最大的物理地址空间,但是最大不大于0x38000000,从而计算得到需要多少物理页去管理物理内存

1
2
3
4
5
6
7
8
        if (maxpa < end && begin < KMEMSIZE) {
maxpa = end;
}
}
}
if (maxpa > KMEMSIZE) {//#define KMEMSIZE 0x38000000
maxpa = KMEMSIZE;
}

因为加载ucore的结束地址(用end全局指针变量记录)以上的高地址空间没有被使用,所以设为管理物理内存的结构体Page的内存空间起始地址为 pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);

为了简化起见,已使用物理空间设为0~pages,空闲物理空间设为pages~0x38000000。

结尾调用init_memmap函数初始化空闲物理地址空间。每一页物理帧都对应一个Page结构体

1
2
3
4
5
6
struct Page {
int ref; // page frame's reference counter to page table
uint32_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
};

接着对所有物理页进行占用标记

1
2
3
for (i = 0; i < npage; i ++) {
SetPageReserved(pages + i);
}

0x03 初始化空闲物理内存(default_pmm.c:default_init_memmap)

清除标志空闲物理内存的结构体Page的标志位,表示pages~KMEMSIZE为空闲内存,

1
2
3
4
5
for (; p != base + n; p ++) {
assert(PageReserved(p));
p->flags = p->property = 0;
set_page_ref(p, 0);
}

设置管理连续内存空闲块的结构体

1
2
3
4
typedef struct {
list_entry_t free_list; // the list header
unsigned int nr_free; // # of free pages in this free list
} free_area_t;

TIPS:pages~0x38000000的每一个物理页都对应一个Page结构体,ucore通过Page结构体去管理物理内存,free_area_t结构体记录空闲的连续物理内存块。

0x04 first-fit连续物理内存分配算法(实验)

思路

first-fit算法的思路就是按序遍历,当大小满足即分配,具体来说:当申请内存时first-fit的思路是从空闲链表的表头开始寻找地址最小的物理页,通过nr_free->next或者通过定义好的函数list_next去寻找,找到空闲物理页后,需要判断大小是不是满足,这主要通过Page结构体的property成员判断,所以需要通过le2page宏,通过链表元素指针nr_free计算出相应的Page指针p,当property>=n时,大小满足,即分配出去。

实现

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
return NULL;
}
struct Page *page = NULL;
list_entry_t *le = &free_list;
// TODO: optimize (next-fit)
while ((le = list_next(le)) != &free_list) {
struct Page *p = le2page(le, page_link);
if (p->property >= n) {
page = p;
break;
}
}
if (page != NULL) {
if (page->property > n) {
struct Page *p = page + n;
p->property = page->property - n;
SetPageProperty(p);
list_add_after(&(page->page_link), &(p->page_link));
}
list_del(&(page->page_link));
nr_free -= n;
ClearPageProperty(page);
}
return page;
}

static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(!PageReserved(p) && !PageProperty(p));
p->flags = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
list_entry_t *le = list_next(&free_list);
while (le != &free_list) {
p = le2page(le, page_link);
le = list_next(le);
// TODO: optimize
if (base + base->property == p) {
base->property += p->property;
ClearPageProperty(p);
list_del(&(p->page_link));
}
else if (p + p->property == base) {
p->property += base->property;
ClearPageProperty(base);
base = p;
list_del(&(p->page_link));
}
}
nr_free += n;
le = list_next(&free_list);
while (le != &free_list) {
p = le2page(le, page_link);
if (base + base->property <= p) {
assert(base + base->property != p);
break;
}
le = list_next(le);
}
list_add_before(le, &(base->page_link));
}