prepare for next
This commit is contained in:
@ -208,8 +208,8 @@ def assert_lines_match(text, *regexps, **kw):
|
||||
msg.append("...")
|
||||
if bad:
|
||||
msg.append("unexpected lines in output")
|
||||
for r in regexps:
|
||||
msg.append(color("red", "MISSING") + " '%s'" % r)
|
||||
# for r in regexps:
|
||||
# msg.append(color("red", "MISSING") + " '%s'" % r)
|
||||
raise AssertionError("\n".join(msg))
|
||||
|
||||
##################################################################
|
||||
|
||||
321
superpage_implementation_summary.md
Normal file
321
superpage_implementation_summary.md
Normal file
@ -0,0 +1,321 @@
|
||||
# xv6 超页(Superpage)实现复盘
|
||||
|
||||
## 项目概述
|
||||
|
||||
在 xv6 内核中实现 2MB 超页支持,当用户程序调用 `sbrk()` 时指定的大小为 2MB 或更大,并且新创建的地址范围包含一个或多个 2MB 对齐且至少为 2MB 大小的区域时,内核应使用单个超页(而不是数百个普通页)。
|
||||
|
||||
## 实现目标
|
||||
|
||||
- 支持 2MB 超页分配
|
||||
- 通过 `superpg_test` 测试用例
|
||||
- 保持与现有代码的兼容性
|
||||
- 正确处理超页的内存管理(分配、释放、复制)
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 超页基本参数
|
||||
- **普通页大小**: 4KB (PGSIZE)
|
||||
- **超页大小**: 2MB (SUPERPGSIZE = 2 * 1024 * 1024)
|
||||
- **超页包含**: 512个普通页 (2MB / 4KB = 512)
|
||||
- **页表级别**: 在 level-1 页表中设置 PTE_PS 位
|
||||
|
||||
### 关键常量定义
|
||||
```c
|
||||
#define SUPERPGSIZE (2 * (1 << 20)) // 2MB
|
||||
#define SUPERPGROUNDUP(sz) (((sz)+SUPERPGSIZE-1) & ~(SUPERPGSIZE-1))
|
||||
#define PTE_PS (1L << 7) // Page Size bit
|
||||
```
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 超页内存分配器(kernel/kalloc.c)
|
||||
|
||||
#### 新增数据结构
|
||||
```c
|
||||
struct super_run {
|
||||
struct super_run *next;
|
||||
};
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
struct super_run *freelist;
|
||||
} skmem;
|
||||
```
|
||||
|
||||
#### 核心函数实现
|
||||
|
||||
**超页分配函数**
|
||||
```c
|
||||
void* superalloc() {
|
||||
struct super_run *r;
|
||||
acquire(&skmem.lock);
|
||||
r = skmem.freelist;
|
||||
if(r) skmem.freelist = r->next;
|
||||
release(&skmem.lock);
|
||||
if(r) memset((void*)r, 0, SUPERPGSIZE);
|
||||
return (void*)r;
|
||||
}
|
||||
```
|
||||
|
||||
**超页释放函数**
|
||||
```c
|
||||
void superfree(void *pa) {
|
||||
struct super_run *r;
|
||||
|
||||
if(((uint64)pa % SUPERPGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
|
||||
panic("superfree");
|
||||
|
||||
memset(pa, 1, SUPERPGSIZE);
|
||||
|
||||
r = (struct super_run *)pa;
|
||||
acquire(&skmem.lock);
|
||||
r->next = skmem.freelist;
|
||||
skmem.freelist = r;
|
||||
release(&skmem.lock);
|
||||
}
|
||||
```
|
||||
|
||||
**内存范围初始化**
|
||||
```c
|
||||
void freerange(void *pa_start, void *pa_end) {
|
||||
char *p;
|
||||
// 分配普通页
|
||||
p = (char*)PGROUNDUP((uint64)pa_start);
|
||||
for(; p + PGSIZE <= (char*)pa_end - 12 * 1024 * 1024; p += PGSIZE)
|
||||
kfree(p);
|
||||
|
||||
// 分配超页
|
||||
p = (char*)SUPERPGROUNDUP((uint64)p);
|
||||
for (; p + SUPERPGSIZE <= (char *)pa_end; p += SUPERPGSIZE) {
|
||||
superfree(p);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 页表管理(kernel/vm.c)
|
||||
|
||||
#### 超页页表遍历
|
||||
```c
|
||||
pte_t *super_walk(pagetable_t pagetable, uint64 va, int alloc) {
|
||||
if (va > MAXVA)
|
||||
panic("walk");
|
||||
|
||||
pte_t *pte = &(pagetable[PX(2, va)]);
|
||||
if (*pte & PTE_V) {
|
||||
pagetable = (pagetable_t)PTE2PA(*pte);
|
||||
} else {
|
||||
if (!alloc || (pagetable = (pde_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pagetable, 0, PGSIZE);
|
||||
*pte = PA2PTE(pagetable) | PTE_V;
|
||||
}
|
||||
|
||||
return &pagetable[PX(1, va)]; // 返回 level-1 PTE
|
||||
}
|
||||
```
|
||||
|
||||
#### 修改 walk 函数支持超页检测
|
||||
```c
|
||||
pte_t *walk(pagetable_t pagetable, uint64 va, int alloc) {
|
||||
// ... 现有代码 ...
|
||||
for(int level = 2; level > 0; level--) {
|
||||
pte_t *pte = &pagetable[PX(level, va)];
|
||||
if(*pte & PTE_V) {
|
||||
pagetable = (pagetable_t)PTE2PA(*pte);
|
||||
#ifdef LAB_PGTBL
|
||||
if (*pte & PTE_PS) {
|
||||
return pte; // 遇到超页时返回该PTE
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// ... 其他代码 ...
|
||||
}
|
||||
return &pagetable[PX(0, va)];
|
||||
}
|
||||
```
|
||||
|
||||
#### 地址转换函数增强
|
||||
```c
|
||||
uint64 walkaddr(pagetable_t pagetable, uint64 va) {
|
||||
// ... 现有代码 ...
|
||||
pa = PTE2PA(*pte);
|
||||
if(*pte & PTE_PS) {
|
||||
// 超页:添加超页内偏移
|
||||
pa += va & (SUPERPGSIZE - 1);
|
||||
} else {
|
||||
// 普通页:添加页内偏移
|
||||
pa += va & (PGSIZE - 1);
|
||||
}
|
||||
return pa;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 内存分配策略(uvmalloc)
|
||||
|
||||
**关键实现逻辑**:
|
||||
```c
|
||||
uint64 uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm) {
|
||||
// 检查是否应该使用超页
|
||||
if (newsz - oldsz >= SUPERPGSIZE) {
|
||||
uint64 super_start = SUPERPGROUNDUP(oldsz);
|
||||
uint64 super_end = newsz & ~(SUPERPGSIZE - 1);
|
||||
|
||||
// 1. 分配超页边界前的普通页
|
||||
for(a = oldsz; a < super_start; a += PGSIZE) {
|
||||
// 分配普通页
|
||||
}
|
||||
|
||||
// 2. 分配对齐的超页区域
|
||||
for (a = super_start; a < super_end; a += SUPERPGSIZE) {
|
||||
mem = superalloc();
|
||||
mappages(pagetable, a, SUPERPGSIZE, (uint64)mem,
|
||||
PTE_R | PTE_U | PTE_PS | xperm);
|
||||
}
|
||||
|
||||
// 3. 分配超页边界后的普通页
|
||||
for(a = super_end; a < newsz; a += PGSIZE) {
|
||||
// 分配普通页
|
||||
}
|
||||
} else {
|
||||
// 小于2MB的分配使用普通页
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 内存操作函数适配
|
||||
|
||||
#### mappages 函数
|
||||
```c
|
||||
int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) {
|
||||
if ((perm & PTE_PS) == 0) {
|
||||
// 普通页映射逻辑
|
||||
// 使用 walk() 函数
|
||||
} else {
|
||||
// 超页映射逻辑
|
||||
// 使用 super_walk() 函数
|
||||
last = va + size - SUPERPGSIZE;
|
||||
for (;;) {
|
||||
pte = super_walk(pagetable, a, 1);
|
||||
*pte = PA2PTE(pa) | perm | PTE_V;
|
||||
a += SUPERPGSIZE;
|
||||
pa += SUPERPGSIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### uvmunmap 函数
|
||||
```c
|
||||
void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) {
|
||||
for(a = va; a < va + npages*PGSIZE; a += sz){
|
||||
sz = PGSIZE;
|
||||
pte = walk(pagetable, a, 0);
|
||||
|
||||
if ((*pte & PTE_PS)) {
|
||||
// 超页释放
|
||||
if(do_free) superfree((void*)PTE2PA(*pte));
|
||||
*pte = 0;
|
||||
a += SUPERPGSIZE - sz;
|
||||
} else {
|
||||
// 普通页释放
|
||||
if(do_free) kfree((void*)PTE2PA(*pte));
|
||||
*pte = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### uvmcopy 函数(fork支持)
|
||||
```c
|
||||
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) {
|
||||
for(i = 0; i < sz; i += szinc){
|
||||
szinc = PGSIZE;
|
||||
pte = walk(old, i, 0);
|
||||
flags = PTE_FLAGS(*pte);
|
||||
|
||||
if ((flags & PTE_PS) == 0) {
|
||||
// 复制普通页
|
||||
mem = kalloc();
|
||||
memmove(mem, (char*)pa, PGSIZE);
|
||||
mappages(new, i, PGSIZE, (uint64)mem, flags);
|
||||
} else {
|
||||
// 复制超页
|
||||
mem = superalloc();
|
||||
memmove(mem, (char*)pa, SUPERPGSIZE);
|
||||
mappages(new, i, SUPERPGSIZE, (uint64)mem, flags);
|
||||
szinc = SUPERPGSIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### copy操作函数适配
|
||||
```c
|
||||
// copyout, copyin, copyinstr 都需要类似的修改
|
||||
uint64 pgsize = (*pte & PTE_PS) ? SUPERPGSIZE : PGSIZE;
|
||||
uint64 va_base = va0 & ~(pgsize - 1);
|
||||
n = pgsize - (srcva - va_base);
|
||||
```
|
||||
|
||||
## 关键测试理解
|
||||
|
||||
### superpg_test 测试流程
|
||||
1. **分配8MB内存**: `sbrk(N)` 其中 N = 8MB
|
||||
2. **计算超页起始地址**: `SUPERPGROUNDUP(end)`
|
||||
3. **验证512个连续页面**: `supercheck(s)`
|
||||
- 检查512个4KB页面有相同的PTE(证明是超页)
|
||||
- 验证PTE权限(PTE_V | PTE_R | PTE_W)
|
||||
- 测试内存读写功能
|
||||
4. **Fork测试**: 验证超页在进程复制时正确工作
|
||||
|
||||
### 测试期望
|
||||
- 512个连续的4KB页面应该映射到同一个超页
|
||||
- 每个页面通过 `pgpte()` 返回相同的PTE值
|
||||
- PTE必须设置正确的权限位
|
||||
- 内存读写必须正常工作
|
||||
- Fork后子进程中超页仍然正常
|
||||
|
||||
## 遇到的问题和解决方案
|
||||
|
||||
### 1. fork失败问题
|
||||
**问题**: 测试中fork调用失败
|
||||
**原因**: uvmcopy函数中超页处理逻辑错误
|
||||
**解决**: 修正超页复制时的步长计算和错误处理
|
||||
|
||||
### 2. 内存分配策略问题
|
||||
**问题**: 没有正确识别何时使用超页
|
||||
**原因**: 原始逻辑基于总分配大小,未考虑对齐
|
||||
**解决**: 重写分配策略,考虑超页对齐边界
|
||||
|
||||
### 3. 地址计算错误
|
||||
**问题**: walkaddr等函数对超页地址计算错误
|
||||
**原因**: 未考虑超页的偏移计算差异
|
||||
**解决**: 根据PTE_PS位选择不同的偏移计算方法
|
||||
|
||||
### 4. 内存释放错误
|
||||
**问题**: 释放超页时调用kfree而非superfree
|
||||
**原因**: uvmunmap函数未区分超页和普通页
|
||||
**解决**: 根据PTE_PS位选择正确的释放函数
|
||||
|
||||
## 实现验证
|
||||
|
||||
### 测试结果
|
||||
- ✅ superpg_test: OK - 超页分配和使用正常
|
||||
- ✅ pgaccess_test: OK - 页面访问跟踪正常
|
||||
- ✅ ugetpid_test: OK - 基本系统调用正常
|
||||
- ✅ usertests: 通过 - 系统整体稳定性良好
|
||||
|
||||
### 性能优势
|
||||
- **内存效率**: 2MB超页减少页表条目数量
|
||||
- **TLB效率**: 单个TLB条目覆盖2MB而非4KB
|
||||
- **管理效率**: 减少页表遍历深度
|
||||
|
||||
## 总结
|
||||
|
||||
超页实现的核心挑战在于:
|
||||
1. **双重内存管理**: 同时支持4KB普通页和2MB超页
|
||||
2. **智能分配策略**: 自动识别何时使用超页
|
||||
3. **页表处理**: 正确处理不同级别的页表项
|
||||
4. **兼容性**: 保持与现有代码的完全兼容
|
||||
|
||||
通过仔细的设计和实现,成功在xv6中添加了超页支持,提高了大内存分配的效率,同时保持了系统的稳定性和兼容性。
|
||||
Reference in New Issue
Block a user