Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8bae855c5 | |||
| 66657d1ca0 | |||
| bc8921a77f | |||
| c6fa25504f |
3
Makefile
3
Makefile
@ -213,8 +213,7 @@ endif
|
||||
ifeq ($(LAB),traps)
|
||||
UPROGS += \
|
||||
$U/_call\
|
||||
$U/_bttest\
|
||||
$U/_alarmtest
|
||||
$U/_bttest
|
||||
endif
|
||||
|
||||
ifeq ($(LAB),lazy)
|
||||
|
||||
46
README.md
46
README.md
@ -0,0 +1,46 @@
|
||||
xv6 is a re-implementation of Dennis Ritchie's and Ken Thompson's Unix
|
||||
Version 6 (v6). xv6 loosely follows the structure and style of v6,
|
||||
but is implemented for a modern RISC-V multiprocessor using ANSI C.
|
||||
|
||||
ACKNOWLEDGMENTS
|
||||
|
||||
xv6 is inspired by John Lions's Commentary on UNIX 6th Edition (Peer
|
||||
to Peer Communications; ISBN: 1-57398-013-7; 1st edition (June 14,
|
||||
2000)). See also https://pdos.csail.mit.edu/6.1810/, which provides
|
||||
pointers to on-line resources for v6.
|
||||
|
||||
The following people have made contributions: Russ Cox (context switching,
|
||||
locking), Cliff Frey (MP), Xiao Yu (MP), Nickolai Zeldovich, and Austin
|
||||
Clements.
|
||||
|
||||
We are also grateful for the bug reports and patches contributed by
|
||||
Takahiro Aoyagi, Marcelo Arroyo, Silas Boyd-Wickizer, Anton Burtsev,
|
||||
carlclone, Ian Chen, Dan Cross, Cody Cutler, Mike CAT, Tej Chajed,
|
||||
Asami Doi,Wenyang Duan, eyalz800, Nelson Elhage, Saar Ettinger, Alice
|
||||
Ferrazzi, Nathaniel Filardo, flespark, Peter Froehlich, Yakir Goaron,
|
||||
Shivam Handa, Matt Harvey, Bryan Henry, jaichenhengjie, Jim Huang,
|
||||
Matúš Jókay, John Jolly, Alexander Kapshuk, Anders Kaseorg, kehao95,
|
||||
Wolfgang Keller, Jungwoo Kim, Jonathan Kimmitt, Eddie Kohler, Vadim
|
||||
Kolontsov, Austin Liew, l0stman, Pavan Maddamsetti, Imbar Marinescu,
|
||||
Yandong Mao, Matan Shabtay, Hitoshi Mitake, Carmi Merimovich, Mark
|
||||
Morrissey, mtasm, Joel Nider, Hayato Ohhashi, OptimisticSide,
|
||||
phosphagos, Harry Porter, Greg Price, RayAndrew, Jude Rich, segfault,
|
||||
Ayan Shafqat, Eldar Sehayek, Yongming Shen, Fumiya Shigemitsu, snoire,
|
||||
Taojie, Cam Tenny, tyfkda, Warren Toomey, Stephen Tu, Alissa Tung,
|
||||
Rafael Ubal, Amane Uehara, Pablo Ventura, Xi Wang, WaheedHafez,
|
||||
Keiichi Watanabe, Lucas Wolf, Nicolas Wolovick, wxdao, Grant Wu, x653,
|
||||
Jindong Zhang, Icenowy Zheng, ZhUyU1997, and Zou Chang Wei.
|
||||
|
||||
ERROR REPORTS
|
||||
|
||||
Please send errors and suggestions to Frans Kaashoek and Robert Morris
|
||||
(kaashoek,rtm@mit.edu). The main purpose of xv6 is as a teaching
|
||||
operating system for MIT's 6.1810, so we are more interested in
|
||||
simplifications and clarifications than new features.
|
||||
|
||||
BUILDING AND RUNNING XV6
|
||||
|
||||
You will need a RISC-V "newlib" tool chain from
|
||||
https://github.com/riscv/riscv-gnu-toolchain, and qemu compiled for
|
||||
riscv64-softmmu. Once they are installed, and in your shell
|
||||
search path, you can run "make qemu".
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
# alarmtest.c 测试
|
||||
|
||||
## test0: 单次调用测试
|
||||
|
||||
**函数**: `test0()`
|
||||
|
||||
**目的**: 测试内核是否至少调用了一次 alarm 处理程序。
|
||||
|
||||
**描述**:
|
||||
|
||||
- 设置 alarm 每 2 个时钟周期触发一次。
|
||||
|
||||
- 在一个长循环中检查 `count` 是否被处理程序修改。
|
||||
|
||||
- 如果 `count > 0`,测试通过;否则失败。
|
||||
|
||||
---
|
||||
|
||||
## test1: 多次调用与寄存器恢复测试
|
||||
|
||||
**函数**: `test1()`
|
||||
|
||||
**目的**: 测试内核是否多次调用 alarm 处理程序,并确保处理程序返回后程序继续从中断点执行,且寄存器值未被破坏。
|
||||
|
||||
**描述**:
|
||||
|
||||
- 设置 alarm 每 2 个时钟周期触发一次。
|
||||
|
||||
- 在循环中调用 `foo()` 函数,同时检查 `count` 是否达到 10。
|
||||
|
||||
- 验证 `foo()` 的调用次数是否与循环计数一致。
|
||||
|
||||
- 如果 `count < 10` 或寄存器值不一致,测试失败;否则通过。
|
||||
|
||||
---
|
||||
|
||||
## test2: 防止重入调用
|
||||
|
||||
**函数**: `test2()`
|
||||
|
||||
**目的**: 测试内核是否防止 alarm 处理程序的重入调用。
|
||||
|
||||
**描述**:
|
||||
|
||||
- 设置 alarm 每 2 个时钟周期触发一次,处理程序为 `slow_handler()`。
|
||||
|
||||
- 在子进程中运行一个长循环,检查 `count` 是否被修改。
|
||||
|
||||
- 如果 `count > 1`,测试失败;否则通过。
|
||||
|
||||
---
|
||||
|
||||
## test3: 寄存器 a0 的完整性测试
|
||||
|
||||
**函数**: `test3()`
|
||||
|
||||
**目的**: 测试从 `sys_sigreturn()` 返回时,寄存器 `a0` 的值是否被修改。
|
||||
|
||||
**描述**:
|
||||
|
||||
- 设置 alarm 每 1 个时钟周期触发一次,处理程序为 `dummy_handler()`。
|
||||
|
||||
- 在触发 alarm 后检查寄存器 `a0` 的值是否保持为初始值 `0xac`。
|
||||
|
||||
- 如果 `a0` 被修改,测试失败;否则通过。
|
||||
|
||||
---
|
||||
|
||||
## 处理程序说明
|
||||
|
||||
### `periodic()`
|
||||
|
||||
- 增加 `count` 值并打印 "alarm!"。
|
||||
|
||||
- 调用 `sigreturn()` 返回。
|
||||
|
||||
### `slow_handler()`
|
||||
|
||||
- 增加 `count` 值并打印 "alarm!"。
|
||||
|
||||
- 如果 `count > 1`,测试失败并退出。
|
||||
|
||||
- 运行一个长循环以模拟慢速处理程序。
|
||||
|
||||
- 调用 `sigreturn()` 返回。
|
||||
|
||||
### `dummy_handler()`
|
||||
|
||||
- 立即卸载自身并调用 `sigreturn()` 返回。
|
||||
|
||||
---
|
||||
@ -1,67 +0,0 @@
|
||||
问题 1: 哪些寄存器包含函数的参数?例如,在 main 对 printf 的调用中,哪个寄存器包含 13?
|
||||
|
||||
在 RISC-V 调用约定中,函数参数通常通过寄存器 a0 到 a7 传递。
|
||||
|
||||
对于函数 g(int x) 和 f(int x) ,它们的参数 x 都通过寄存器 a0 传递。
|
||||
在 main 对 printf 的调用中, printf("y=%d\n", 13) 语句中,数字 13 将包含在寄存器 a1 中。 printf 的第一个参数(格式字符串的地址)将包含在寄存器 a0 中。
|
||||
|
||||
问题 2: 在 main 的汇编代码中,对函数 f 的调用在哪里?对 g 的调用在哪里?(提示:编译器可以内联函数。)
|
||||
|
||||
由于编译器优化, f 和 g 函数在 main 函数中被内联了。这意味着它们的实际代码直接插入到 main 函数的调用点,而不是通过 jal 或 jalr 指令进行函数调用。
|
||||
|
||||
我们可以通过观察 main 函数的汇编代码来验证这一点。在 main 函数中,你会看到对 f 和 g 的操作直接在 main 的代码流中执行,而没有显式的 jal 或 jalr 到 <f> 或 <g> 的地址。
|
||||
|
||||
问题 3: 函数 printf 位于哪个地址?
|
||||
|
||||
printf 函数不在 call.asm 文件提供的汇编代码中。 call.asm 似乎只包含用户定义的函数和一些与内存分配相关的代码。 printf 是一个标准库函数,它的地址会在程序链接时确定,并且通常在运行时动态加载。因此,我们无法从 call.asm 的反汇编代码中直接找到 printf 的地址。
|
||||
|
||||
问题 4: 在 main 中 jalr 到 printf 之后的寄存器 ra 中有什么值?
|
||||
|
||||
在 main 函数中 jalr 到 printf 之后,寄存器 ra (返回地址寄存器) 将包含紧跟在 jalr 指令之后的指令的地址。这个地址是 printf 函数执行完毕后应该返回到的 main 函数中的位置。
|
||||
|
||||
问题 5: 运行以下代码。无符号 int i = 0x00646c72; printf(“H%x Wo%s”, 57616, (字符 ) &i); 输出是什么?
|
||||
|
||||
i = 0x00646c72
|
||||
printf("H%x Wo%s", 57616, (char ) &i)
|
||||
|
||||
输出: H0e110 Wo rld
|
||||
|
||||
解释:
|
||||
|
||||
"H" 和 "Wo" 是直接打印的字符串。
|
||||
%x 对应 57616 。 57616 的十六进制是 0xE110 。
|
||||
%s 对应 (char ) &i 。由于 RISC-V 是 little-endian,内存中 i 的字节顺序是 72 6C 64 00 。
|
||||
0x72 是字符 'r'
|
||||
0x6C 是字符 'l'
|
||||
0x64 是字符 'd'
|
||||
0x00 是字符串终止符
|
||||
因此, %s 会打印 "rld"。
|
||||
|
||||
问题 6: 如果 RISC-V 是 big-endian,您会将 i 设置为什么 i 才能产生相同的输出?是否需要将 57616 更改为其他值?
|
||||
|
||||
如果 RISC-V 是 big-endian:
|
||||
为了使 %s 打印 "rld",我们需要将 i 设置为 0x726c6400 。在 big-endian 系统中,高位字节存储在低内存地址,所以 0x72 会在最低地址,然后是 0x6c , 0x64 ,最后是 0x00 。
|
||||
|
||||
是否需要将 57616 更改为其他值?
|
||||
不需要。 57616 是一个整数值,无论系统是 little-endian 还是 big-endian,它的数值表示是不变的。 %x 格式说明符只是将其打印为十六进制。字节序只影响多字节数据类型(如 int )在内存中的存储方式,而不影响其数值。
|
||||
|
||||
问题 7: 在下面的代码中, 'y=' 后面会打印什么?(注意:答案不是具体值。为什么会这样?
|
||||
|
||||
c
|
||||
printf("x=%d y=%d", 3);
|
||||
|
||||
|
||||
输出: y= 后面会打印一个不确定的值(垃圾值)。
|
||||
|
||||
为什么会这样:
|
||||
|
||||
printf 是一个变参函数,它根据格式字符串中的格式说明符来确定需要从栈中取出多少个参数。
|
||||
|
||||
格式字符串是 "x=%d y=%d" 。
|
||||
这意味着 printf 期望有两个 %d 对应的整数参数。
|
||||
但是,在实际调用中,只提供了一个参数 3 。
|
||||
|
||||
因此:
|
||||
|
||||
1. printf 会从栈中读取第一个整数参数来填充 x=%d ,这个值就是 3 。
|
||||
2. 然后, printf 会尝试读取第二个整数参数来填充 y=%d 。由于调用时只提供了 3 这一个参数, printf 会从栈上读取一个任意的、未初始化的内存位置的值,这个值是不可预测的(垃圾值)。
|
||||
@ -1 +1 @@
|
||||
LAB=traps
|
||||
LAB=cow
|
||||
|
||||
159
cowlab.md
Normal file
159
cowlab.md
Normal file
@ -0,0 +1,159 @@
|
||||
# xv6-labs: Copy-On-Write (COW) Fork 实验详细总结
|
||||
|
||||
## 实验目标
|
||||
|
||||
实现 xv6 操作系统的 COW(写时复制)fork:
|
||||
- fork 时父子进程共享物理页,只有在写入时才真正分配和复制物理页。
|
||||
- 提高 fork+exec 的效率,减少不必要的内存拷贝。
|
||||
|
||||
## 主要实现步骤与详细代码
|
||||
|
||||
### 1. 物理页引用计数
|
||||
**文件:kernel/kalloc.c**
|
||||
- 在文件顶部添加:
|
||||
```c
|
||||
#define NPAGE ((PHYSTOP - KERNBASE) / PGSIZE)
|
||||
static int refcnt[NPAGE];
|
||||
static inline int pa2idx(void *pa) { return ((uint64)pa - KERNBASE) / PGSIZE; }
|
||||
```
|
||||
- kalloc 分配新页时:
|
||||
```c
|
||||
if(r) {
|
||||
memset((char*)r, 5, PGSIZE);
|
||||
int idx = pa2idx((void*)r);
|
||||
refcnt[idx] = 1;
|
||||
}
|
||||
```
|
||||
- kfree 释放页时:
|
||||
```c
|
||||
int idx = pa2idx(pa);
|
||||
acquire(&kmem.lock);
|
||||
if(refcnt[idx] > 1) {
|
||||
refcnt[idx]--;
|
||||
release(&kmem.lock);
|
||||
return;
|
||||
}
|
||||
refcnt[idx] = 0;
|
||||
release(&kmem.lock);
|
||||
memset(pa, 1, PGSIZE);
|
||||
// ...加入空闲链表...
|
||||
```
|
||||
- 增加辅助函数:
|
||||
```c
|
||||
void incref(void *pa) { refcnt[pa2idx(pa)]++; }
|
||||
void decref(void *pa) { refcnt[pa2idx(pa)]--; }
|
||||
int getref(void *pa) { return refcnt[pa2idx(pa)]; }
|
||||
```
|
||||
|
||||
### 2. PTE 标记 COW
|
||||
**文件:kernel/riscv.h**
|
||||
```c
|
||||
#define PTE_COW (1L << 8) // 使用RSW位标记COW
|
||||
```
|
||||
|
||||
### 3. 修改 uvmcopy 实现 COW fork
|
||||
**文件:kernel/vm.c**
|
||||
- 在文件头部声明:
|
||||
```c
|
||||
void incref(void *pa);
|
||||
```
|
||||
- 修改 uvmcopy:
|
||||
```c
|
||||
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) {
|
||||
...
|
||||
for(i = 0; i < sz; i += PGSIZE){
|
||||
...
|
||||
pa = PTE2PA(*pte);
|
||||
flags = PTE_FLAGS(*pte);
|
||||
if(flags & PTE_W) {
|
||||
flags = (flags & ~PTE_W) | PTE_COW;
|
||||
*pte = PA2PTE(pa) | flags;
|
||||
}
|
||||
if(mappages(new, i, PGSIZE, pa, flags) != 0){
|
||||
uvmunmap(new, 0, i / PGSIZE, 0);
|
||||
return -1;
|
||||
}
|
||||
incref((void*)pa);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. usertrap 处理写页错误
|
||||
**文件:kernel/trap.c**
|
||||
- 在文件头部声明:
|
||||
```c
|
||||
void kfree(void *pa);
|
||||
void *kalloc(void);
|
||||
```
|
||||
- 在 usertrap() 添加COW处理:
|
||||
```c
|
||||
else if((scause == 15 || scause == 13)) { // store/load page fault
|
||||
pte_t *pte = walk(p->pagetable, stval, 0);
|
||||
if(pte && (*pte & PTE_V) && (*pte & PTE_U) && (*pte & PTE_COW)) {
|
||||
uint64 pa = PTE2PA(*pte);
|
||||
char *mem = kalloc();
|
||||
if(mem == 0) {
|
||||
setkilled(p);
|
||||
} else {
|
||||
memmove(mem, (void*)pa, PGSIZE);
|
||||
*pte = PA2PTE(mem) | (PTE_FLAGS(*pte) & ~PTE_COW) | PTE_W;
|
||||
kfree((void*)pa);
|
||||
}
|
||||
} else {
|
||||
setkilled(p);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. copyout 处理 COW
|
||||
**文件:kernel/vm.c**
|
||||
- 修改 copyout:
|
||||
```c
|
||||
if((*pte & PTE_W) == 0) {
|
||||
if((*pte & PTE_COW) != 0) {
|
||||
pa0 = PTE2PA(*pte);
|
||||
char *mem = kalloc();
|
||||
if(mem == 0)
|
||||
return -1;
|
||||
memmove(mem, (void*)pa0, PGSIZE);
|
||||
*pte = PA2PTE(mem) | PTE_FLAGS(*pte) | PTE_W;
|
||||
*pte &= ~PTE_COW;
|
||||
kfree((void*)pa0);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 释放页时引用计数
|
||||
- uvmunmap 释放页时直接调用 kfree,kfree 内部已处理引用计数。
|
||||
|
||||
### 7. walk() 边界健壮性
|
||||
**文件:kernel/vm.c**
|
||||
- 修改 walk():
|
||||
```c
|
||||
if(va >= MAXVA)
|
||||
return 0;
|
||||
```
|
||||
|
||||
## 调试与测试
|
||||
|
||||
- 通过 `make qemu`,运行 `usertests` 和 `cowtest`,确保所有测试通过。
|
||||
- 重点关注 `MAXVAplus`、`forktest`、`sbrkfail` 等边界和异常测试。
|
||||
- 遇到 panic("walk") 问题,修正 walk() 返回 0。
|
||||
- 通过多次 fork/exec、写入、回收等场景,验证内存共享和释放的正确性。
|
||||
|
||||
## 常见问题与修复
|
||||
|
||||
- **panic("walk")**:walk() 对非法地址应返回0而不是panic。
|
||||
- **refcnt数组不能用end做基址**:应以KERNBASE为基址,保证数组大小编译期可知。
|
||||
- **重复定义PTE宏**:只保留riscv.h中的定义。
|
||||
|
||||
## 总结
|
||||
|
||||
- COW fork 显著提升了 fork+exec 的效率。
|
||||
- 通过引用计数和 COW 标志,保证了内存的正确共享与释放。
|
||||
- 通过边界检查和优雅失败,保证了系统健壮性。
|
||||
|
||||
本实验加深了对操作系统内存管理、页表机制和异常处理的理解。
|
||||
59
grade-lab-cow
Normal file
59
grade-lab-cow
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
from gradelib import *
|
||||
|
||||
r = Runner(save("xv6.out"))
|
||||
|
||||
@test(0, "running cowtest")
|
||||
def test_cowtest():
|
||||
r.run_qemu(shell_script([
|
||||
'cowtest'
|
||||
]), timeout=300)
|
||||
|
||||
@test(30, "simple", parent=test_cowtest)
|
||||
def test_simple():
|
||||
matches = re.findall("^simple: ok$", r.qemu.output, re.M)
|
||||
assert_equal(len(matches), 2, "Number of appearances of 'simple: ok'")
|
||||
|
||||
@test(30, "three", parent=test_cowtest)
|
||||
def test_three():
|
||||
matches = re.findall("^three: ok$", r.qemu.output, re.M)
|
||||
assert_equal(len(matches), 3, "Number of appearances of 'three: ok'")
|
||||
|
||||
@test(20, "file", parent=test_cowtest)
|
||||
def test_file():
|
||||
r.match('^file: ok$')
|
||||
|
||||
@test(20, "forkfork", parent=test_cowtest)
|
||||
def test_forkfork():
|
||||
r.match('^forkfork: ok$')
|
||||
|
||||
@test(0, "usertests")
|
||||
def test_usertests():
|
||||
r.run_qemu(shell_script([
|
||||
'usertests -q'
|
||||
]), timeout=1000)
|
||||
r.match('^ALL TESTS PASSED$')
|
||||
|
||||
def usertest_check(testcase, nextcase, output):
|
||||
if not re.search(r'\ntest {}: [\s\S]*OK\ntest {}'.format(testcase, nextcase), output):
|
||||
raise AssertionError('Failed ' + testcase)
|
||||
|
||||
@test(5, "usertests: copyin", parent=test_usertests)
|
||||
def test_sbrkbugs():
|
||||
usertest_check("copyin", "copyout", r.qemu.output)
|
||||
|
||||
@test(5, "usertests: copyout", parent=test_usertests)
|
||||
def test_sbrkbugs():
|
||||
usertest_check("copyout", "copyinstr1", r.qemu.output)
|
||||
|
||||
@test(19, "usertests: all tests", parent=test_usertests)
|
||||
def test_usertests_all():
|
||||
r.match('^ALL TESTS PASSED$')
|
||||
|
||||
@test(1, "time")
|
||||
def test_time():
|
||||
check_time()
|
||||
|
||||
run_tests()
|
||||
@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from gradelib import *
|
||||
|
||||
r = Runner(save("xv6.out"))
|
||||
|
||||
@test(5, "answers-traps.txt")
|
||||
def test_answers():
|
||||
# just a simple sanity check, will be graded manually
|
||||
check_answers("answers-traps.txt")
|
||||
|
||||
BACKTRACE_RE = r"^(0x000000008[0-9a-f]+)"
|
||||
|
||||
def addr2line():
|
||||
for f in ['riscv64-unknown-elf-addr2line', 'riscv64-linux-gnu-addr2line', 'addr2line', ]:
|
||||
try:
|
||||
devnull = open(os.devnull)
|
||||
subprocess.Popen([f], stdout=devnull, stderr=devnull).communicate()
|
||||
return f
|
||||
except OSError:
|
||||
continue
|
||||
raise AssertionError('Cannot find the addr2line program')
|
||||
|
||||
@test(10, "backtrace test")
|
||||
def test_backtracetest():
|
||||
r.run_qemu(shell_script([
|
||||
'bttest'
|
||||
]))
|
||||
a2l = addr2line()
|
||||
matches = re.findall(BACKTRACE_RE, r.qemu.output, re.MULTILINE)
|
||||
assert_equal(len(matches), 3)
|
||||
files = ['sysproc.c', 'syscall.c', 'trap.c']
|
||||
for f, m in zip(files, matches):
|
||||
result = subprocess.run([a2l, '-e', 'kernel/kernel', m], stdout=subprocess.PIPE)
|
||||
if not f in result.stdout.decode("utf-8"):
|
||||
raise AssertionError('Trace is incorrect; no %s' % f)
|
||||
|
||||
@test(0, "running alarmtest")
|
||||
def test_alarmtest():
|
||||
r.run_qemu(shell_script([
|
||||
'alarmtest'
|
||||
]))
|
||||
|
||||
@test(20, "alarmtest: test0", parent=test_alarmtest)
|
||||
def test_alarmtest_test0():
|
||||
r.match('^test0 passed$')
|
||||
|
||||
@test(20, "alarmtest: test1", parent=test_alarmtest)
|
||||
def test_alarmtest_test1():
|
||||
r.match('^\\.?test1 passed$')
|
||||
|
||||
@test(10, "alarmtest: test2", parent=test_alarmtest)
|
||||
def test_alarmtest_test2():
|
||||
r.match('^\\.?test2 passed$')
|
||||
|
||||
@test(10, "alarmtest: test3", parent=test_alarmtest)
|
||||
def test_alarmtest_test3():
|
||||
r.match('^test3 passed$')
|
||||
|
||||
@test(19, "usertests")
|
||||
def test_usertests():
|
||||
r.run_qemu(shell_script([
|
||||
'usertests -q'
|
||||
]), timeout=300)
|
||||
r.match('^ALL TESTS PASSED$')
|
||||
|
||||
@test(1, "time")
|
||||
def test_time():
|
||||
check_time()
|
||||
|
||||
run_tests()
|
||||
@ -80,7 +80,6 @@ int pipewrite(struct pipe*, uint64, int);
|
||||
int printf(char*, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
void panic(char*) __attribute__((noreturn));
|
||||
void printfinit(void);
|
||||
void backtrace(void);
|
||||
|
||||
// proc.c
|
||||
int cpuid(void);
|
||||
|
||||
@ -9,6 +9,12 @@
|
||||
#include "riscv.h"
|
||||
#include "defs.h"
|
||||
|
||||
#define NPAGE ((PHYSTOP - KERNBASE) / PGSIZE)
|
||||
static int refcnt[NPAGE];
|
||||
static inline int pa2idx(void *pa) {
|
||||
return ((uint64)pa - KERNBASE) / PGSIZE;
|
||||
}
|
||||
|
||||
void freerange(void *pa_start, void *pa_end);
|
||||
|
||||
extern char end[]; // first address after kernel.
|
||||
@ -23,10 +29,25 @@ struct {
|
||||
struct run *freelist;
|
||||
} kmem;
|
||||
|
||||
void incref(void *pa) {
|
||||
int idx = pa2idx(pa);
|
||||
refcnt[idx]++;
|
||||
}
|
||||
void decref(void *pa) {
|
||||
int idx = pa2idx(pa);
|
||||
refcnt[idx]--;
|
||||
}
|
||||
int getref(void *pa) {
|
||||
int idx = pa2idx(pa);
|
||||
return refcnt[idx];
|
||||
}
|
||||
|
||||
void
|
||||
kinit()
|
||||
{
|
||||
initlock(&kmem.lock, "kmem");
|
||||
for(int i = 0; i < NPAGE; i++)
|
||||
refcnt[i] = 0;
|
||||
freerange(end, (void*)PHYSTOP);
|
||||
}
|
||||
|
||||
@ -51,6 +72,16 @@ kfree(void *pa)
|
||||
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
|
||||
panic("kfree");
|
||||
|
||||
int idx = pa2idx(pa);
|
||||
acquire(&kmem.lock);
|
||||
if(refcnt[idx] > 1) {
|
||||
refcnt[idx]--;
|
||||
release(&kmem.lock);
|
||||
return;
|
||||
}
|
||||
refcnt[idx] = 0;
|
||||
release(&kmem.lock);
|
||||
|
||||
// Fill with junk to catch dangling refs.
|
||||
memset(pa, 1, PGSIZE);
|
||||
|
||||
@ -76,7 +107,10 @@ kalloc(void)
|
||||
kmem.freelist = r->next;
|
||||
release(&kmem.lock);
|
||||
|
||||
if(r)
|
||||
if(r) {
|
||||
memset((char*)r, 5, PGSIZE); // fill with junk
|
||||
int idx = pa2idx((void*)r);
|
||||
refcnt[idx] = 1;
|
||||
}
|
||||
return (void*)r;
|
||||
}
|
||||
|
||||
@ -176,24 +176,3 @@ printfinit(void)
|
||||
initlock(&pr.lock, "pr");
|
||||
pr.locking = 1;
|
||||
}
|
||||
|
||||
// 打印当前调用栈的返回地址,实现简单的函数回溯,用于调试。
|
||||
void
|
||||
backtrace(void)
|
||||
{
|
||||
printf("barcktrace:\n");
|
||||
|
||||
uint64 ra,fp = r_fp();//frame pointer -> address
|
||||
uint64 pre_fp = *((uint64*)(fp - 16));
|
||||
|
||||
// 只要当前fp和上一个fp在同一页内,就继续回溯
|
||||
while(PGROUNDDOWN(fp)==PGROUNDDOWN(pre_fp)){
|
||||
ra = *(uint64 *)(fp - 8); // 取出返回地址
|
||||
printf("%p\n", (void*)ra); // 打印返回地址
|
||||
fp = pre_fp; // 更新fp为上一个fp
|
||||
pre_fp = *((uint64*)(fp - 16)); // 获取新的上一个fp
|
||||
}
|
||||
|
||||
ra = *(uint64 *)(fp - 8);
|
||||
printf("%p\n",(void*)ra);
|
||||
}
|
||||
@ -132,13 +132,6 @@ found:
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 为进程分配 pre_trapframe 空间,分配失败则释放进程资源并返回 0
|
||||
if((p->pre_trapframe = (struct trapframe *)kalloc()) == 0){
|
||||
freeproc(p);
|
||||
release(&p->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// An empty user page table.
|
||||
p->pagetable = proc_pagetable(p);
|
||||
if(p->pagetable == 0){
|
||||
@ -167,10 +160,6 @@ freeproc(struct proc *p)
|
||||
p->trapframe = 0;
|
||||
if(p->pagetable)
|
||||
proc_freepagetable(p->pagetable, p->sz);
|
||||
// 如果进程的 pre_trapframe 存在,则释放其占用的内存,并将指针置为 0,防止悬空指针。
|
||||
if(p->pre_trapframe)
|
||||
kfree((void*)p->pre_trapframe);
|
||||
p->pre_trapframe = 0;
|
||||
p->pagetable = 0;
|
||||
p->sz = 0;
|
||||
p->pid = 0;
|
||||
|
||||
@ -104,8 +104,4 @@ struct proc {
|
||||
struct file *ofile[NOFILE]; // Open files
|
||||
struct inode *cwd; // Current directory
|
||||
char name[16]; // Process name (debugging)
|
||||
int alarm_cnt; // Alarm count 触发alarm的时钟中断数量
|
||||
int inter_cnt; // interrupts count 需要统计的时钟中断的数量
|
||||
uint64 handler; // alarm func address 记录处理alarm的函数的地址
|
||||
struct trapframe *pre_trapframe;
|
||||
};
|
||||
|
||||
@ -338,15 +338,6 @@ r_ra()
|
||||
return x;
|
||||
}
|
||||
|
||||
// 读取当前帧指针(s0)寄存器的值
|
||||
static inline uint64
|
||||
r_fp()
|
||||
{
|
||||
uint64 x;
|
||||
asm volatile("mv %0, s0" : "=r" (x) );
|
||||
return x;
|
||||
}
|
||||
|
||||
// flush the TLB.
|
||||
static inline void
|
||||
sfence_vma()
|
||||
@ -371,6 +362,7 @@ typedef uint64 *pagetable_t; // 512 PTEs
|
||||
#define PTE_W (1L << 2)
|
||||
#define PTE_X (1L << 3)
|
||||
#define PTE_U (1L << 4) // user can access
|
||||
#define PTE_COW (1L << 8) // 使用RSW位标记COW
|
||||
|
||||
// shift a physical address to the right place for a PTE.
|
||||
#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
|
||||
|
||||
@ -101,8 +101,6 @@ extern uint64 sys_unlink(void);
|
||||
extern uint64 sys_link(void);
|
||||
extern uint64 sys_mkdir(void);
|
||||
extern uint64 sys_close(void);
|
||||
extern uint64 sys_sigalarm(void);
|
||||
extern uint64 sys_sigreturn(void);
|
||||
|
||||
// An array mapping syscall numbers from syscall.h
|
||||
// to the function that handles the system call.
|
||||
@ -128,8 +126,6 @@ static uint64 (*syscalls[])(void) = {
|
||||
[SYS_link] sys_link,
|
||||
[SYS_mkdir] sys_mkdir,
|
||||
[SYS_close] sys_close,
|
||||
[SYS_sigalarm] sys_sigalarm,
|
||||
[SYS_sigreturn] sys_sigreturn,
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@ -20,5 +20,3 @@
|
||||
#define SYS_link 19
|
||||
#define SYS_mkdir 20
|
||||
#define SYS_close 21
|
||||
#define SYS_sigalarm 22
|
||||
#define SYS_sigreturn 23
|
||||
|
||||
@ -54,8 +54,6 @@ sys_sleep(void)
|
||||
int n;
|
||||
uint ticks0;
|
||||
|
||||
backtrace();
|
||||
|
||||
argint(0, &n);
|
||||
if(n < 0)
|
||||
n = 0;
|
||||
@ -93,44 +91,3 @@ sys_uptime(void)
|
||||
release(&tickslock);
|
||||
return xticks;
|
||||
}
|
||||
|
||||
// sys_sigalarm函数用于设置进程的定时信号处理机制。
|
||||
// 参数:
|
||||
// alarm_cnt:定时器计数值,表示每经过alarm_cnt个时钟中断后触发一次信号处理。
|
||||
// addr:信号处理函数的用户空间地址。
|
||||
// 实现:
|
||||
// 1. 通过argint和argaddr获取用户传入的参数。
|
||||
// 2. 将进程的inter_cnt(中断计数器)清零。
|
||||
// 3. 保存信号处理函数地址和定时器计数值到进程结构体。
|
||||
// 4. 返回0,表示设置成功。
|
||||
|
||||
// sys_sigreturn函数用于在信号处理函数执行完毕后恢复进程的上下文。
|
||||
// 实现:
|
||||
// 1. 获取当前进程指针。
|
||||
// 2. 将trapframe恢复为信号处理前保存的pre_trapframe,恢复进程上下文。
|
||||
// 3. 将inter_cnt(中断计数器)清零,重新计数。
|
||||
// 4. 返回信号处理前a0寄存器的值,作为系统调用的返回值。
|
||||
uint64
|
||||
sys_sigalarm(void)
|
||||
{
|
||||
int alarm_cnt;
|
||||
uint64 addr;
|
||||
struct proc *p = myproc();
|
||||
argint(0, &alarm_cnt);
|
||||
argaddr(1, &addr);
|
||||
|
||||
p->inter_cnt = 0;
|
||||
p->handler = addr;
|
||||
p->alarm_cnt = alarm_cnt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64
|
||||
sys_sigreturn(void)
|
||||
{
|
||||
struct proc* p = myproc();
|
||||
*p->trapframe = *p->pre_trapframe;
|
||||
p->inter_cnt = 0;
|
||||
return p->pre_trapframe->a0;
|
||||
}
|
||||
|
||||
@ -16,6 +16,9 @@ void kernelvec();
|
||||
|
||||
extern int devintr();
|
||||
|
||||
typedef uint64 pte_t;
|
||||
pte_t *walk(pagetable_t pagetable, uint64 va, int alloc);
|
||||
|
||||
void
|
||||
trapinit(void)
|
||||
{
|
||||
@ -50,7 +53,10 @@ usertrap(void)
|
||||
// save user program counter.
|
||||
p->trapframe->epc = r_sepc();
|
||||
|
||||
if(r_scause() == 8){
|
||||
uint64 scause = r_scause();
|
||||
uint64 stval = r_stval();
|
||||
|
||||
if(scause == 8){
|
||||
// system call
|
||||
|
||||
if(killed(p))
|
||||
@ -67,6 +73,23 @@ usertrap(void)
|
||||
syscall();
|
||||
} else if((which_dev = devintr()) != 0){
|
||||
// ok
|
||||
} else if((scause == 15 || scause == 13)) { // store/load page fault
|
||||
pte_t *pte = walk(p->pagetable, stval, 0);
|
||||
if(pte && (*pte & PTE_V) && (*pte & PTE_U) && (*pte & PTE_COW)) {
|
||||
uint64 pa = PTE2PA(*pte);
|
||||
char *mem = kalloc();
|
||||
if(mem == 0) {
|
||||
setkilled(p);
|
||||
} else {
|
||||
memmove(mem, (void*)pa, PGSIZE);
|
||||
*pte = PA2PTE(mem) | (PTE_FLAGS(*pte) & ~PTE_COW) | PTE_W;
|
||||
kfree((void*)pa);
|
||||
}
|
||||
} else {
|
||||
printf("usertrap(): unexpected scause 0x%lx pid=%d\n", scause, p->pid);
|
||||
printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval());
|
||||
setkilled(p);
|
||||
}
|
||||
} else {
|
||||
printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
|
||||
printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval());
|
||||
@ -76,21 +99,9 @@ usertrap(void)
|
||||
if(killed(p))
|
||||
exit(-1);
|
||||
|
||||
// 如果这是一个定时器中断,则让出CPU。
|
||||
if(which_dev == 2){
|
||||
// 当前进程的中断计数器加一。
|
||||
p->inter_cnt++;
|
||||
// 如果中断计数器达到设定的报警计数,并且报警计数大于0。
|
||||
if (p->inter_cnt == p->alarm_cnt && 0 < p->alarm_cnt) {
|
||||
// 备份当前trapframe到pre_trapframe,用于后续恢复。
|
||||
*p->pre_trapframe = *p->trapframe;
|
||||
// 将epc设置为用户定义的handler函数地址,返回用户态时会跳转到handler执行。
|
||||
p->trapframe->epc = p->handler;
|
||||
} else {
|
||||
// 否则让出CPU,进行进程调度。
|
||||
yield();
|
||||
}
|
||||
}
|
||||
// give up the CPU if this is a timer interrupt.
|
||||
if(which_dev == 2)
|
||||
yield();
|
||||
|
||||
usertrapret();
|
||||
}
|
||||
@ -228,3 +239,6 @@ devintr()
|
||||
}
|
||||
}
|
||||
|
||||
void kfree(void *pa);
|
||||
void *kalloc(void);
|
||||
|
||||
|
||||
51
kernel/vm.c
51
kernel/vm.c
@ -6,6 +6,10 @@
|
||||
#include "defs.h"
|
||||
#include "fs.h"
|
||||
|
||||
void incref(void *pa);
|
||||
void kfree(void *pa);
|
||||
void *kalloc(void);
|
||||
|
||||
/*
|
||||
* the kernel's page table.
|
||||
*/
|
||||
@ -86,7 +90,8 @@ pte_t *
|
||||
walk(pagetable_t pagetable, uint64 va, int alloc)
|
||||
{
|
||||
if(va >= MAXVA)
|
||||
panic("walk");
|
||||
return 0;
|
||||
// panic("walk");
|
||||
|
||||
for(int level = 2; level > 0; level--) {
|
||||
pte_t *pte = &pagetable[PX(level, va)];
|
||||
@ -315,7 +320,7 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
|
||||
pte_t *pte;
|
||||
uint64 pa, i;
|
||||
uint flags;
|
||||
char *mem;
|
||||
// char *mem;
|
||||
|
||||
for(i = 0; i < sz; i += PGSIZE){
|
||||
if((pte = walk(old, i, 0)) == 0)
|
||||
@ -324,19 +329,21 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
|
||||
panic("uvmcopy: page not present");
|
||||
pa = PTE2PA(*pte);
|
||||
flags = PTE_FLAGS(*pte);
|
||||
if((mem = kalloc()) == 0)
|
||||
goto err;
|
||||
memmove(mem, (char*)pa, PGSIZE);
|
||||
if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
|
||||
kfree(mem);
|
||||
goto err;
|
||||
// 如果是可写页,去掉PTE_W,设置PTE_COW
|
||||
if(flags & PTE_W) {
|
||||
flags = (flags & ~PTE_W) | PTE_COW;
|
||||
*pte = PA2PTE(pa) | flags;
|
||||
}
|
||||
// 子页表同样映射,权限同上
|
||||
if(mappages(new, i, PGSIZE, pa, flags) != 0){
|
||||
// kfree(mem); // 不再分配新页
|
||||
uvmunmap(new, 0, i / PGSIZE, 0);
|
||||
return -1;
|
||||
}
|
||||
// 增加物理页引用计数
|
||||
incref((void*)pa);
|
||||
}
|
||||
return 0;
|
||||
|
||||
err:
|
||||
uvmunmap(new, 0, i / PGSIZE, 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// mark a PTE invalid for user access.
|
||||
@ -366,9 +373,25 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
|
||||
if(va0 >= MAXVA)
|
||||
return -1;
|
||||
pte = walk(pagetable, va0, 0);
|
||||
if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 ||
|
||||
(*pte & PTE_W) == 0)
|
||||
if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
|
||||
return -1;
|
||||
if((*pte & PTE_W) == 0) {
|
||||
// COW处理
|
||||
if((*pte & PTE_COW) != 0) {
|
||||
pa0 = PTE2PA(*pte);
|
||||
char *mem = kalloc();
|
||||
if(mem == 0)
|
||||
return -1;
|
||||
memmove(mem, (void*)pa0, PGSIZE);
|
||||
// 更新PTE为新物理页,可写,去掉COW
|
||||
*pte = PA2PTE(mem) | PTE_FLAGS(*pte) | PTE_W;
|
||||
*pte &= ~PTE_COW;
|
||||
// 原物理页引用计数减一
|
||||
kfree((void*)pa0);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
pa0 = PTE2PA(*pte);
|
||||
n = PGSIZE - (dstva - va0);
|
||||
if(n > len)
|
||||
|
||||
193
user/alarmtest.c
193
user/alarmtest.c
@ -1,193 +0,0 @@
|
||||
//
|
||||
// test program for the alarm lab.
|
||||
// you can modify this file for testing,
|
||||
// but please make sure your kernel
|
||||
// modifications pass the original
|
||||
// versions of these tests.
|
||||
//
|
||||
|
||||
#include "kernel/param.h"
|
||||
#include "kernel/types.h"
|
||||
#include "kernel/stat.h"
|
||||
#include "kernel/riscv.h"
|
||||
#include "user/user.h"
|
||||
|
||||
void test0();
|
||||
void test1();
|
||||
void test2();
|
||||
void test3();
|
||||
void periodic();
|
||||
void slow_handler();
|
||||
void dummy_handler();
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
test0();
|
||||
test1();
|
||||
test2();
|
||||
test3();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
volatile static int count;
|
||||
|
||||
void
|
||||
periodic()
|
||||
{
|
||||
count = count + 1;
|
||||
printf("alarm!\n");
|
||||
sigreturn();
|
||||
}
|
||||
|
||||
// tests whether the kernel calls
|
||||
// the alarm handler even a single time.
|
||||
void
|
||||
test0()
|
||||
{
|
||||
int i;
|
||||
printf("test0 start\n");
|
||||
count = 0;
|
||||
sigalarm(2, periodic);
|
||||
for(i = 0; i < 1000*500000; i++){
|
||||
if((i % 1000000) == 0)
|
||||
write(2, ".", 1);
|
||||
if(count > 0)
|
||||
break;
|
||||
}
|
||||
sigalarm(0, 0);
|
||||
if(count > 0){
|
||||
printf("test0 passed\n");
|
||||
} else {
|
||||
printf("\ntest0 failed: the kernel never called the alarm handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void __attribute__ ((noinline)) foo(int i, int *j) {
|
||||
if((i % 2500000) == 0) {
|
||||
write(2, ".", 1);
|
||||
}
|
||||
*j += 1;
|
||||
}
|
||||
|
||||
//
|
||||
// tests that the kernel calls the handler multiple times.
|
||||
//
|
||||
// tests that, when the handler returns, it returns to
|
||||
// the point in the program where the timer interrupt
|
||||
// occurred, with all registers holding the same values they
|
||||
// held when the interrupt occurred.
|
||||
//
|
||||
void
|
||||
test1()
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
|
||||
printf("test1 start\n");
|
||||
count = 0;
|
||||
j = 0;
|
||||
sigalarm(2, periodic);
|
||||
for(i = 0; i < 500000000; i++){
|
||||
if(count >= 10)
|
||||
break;
|
||||
foo(i, &j);
|
||||
}
|
||||
if(count < 10){
|
||||
printf("\ntest1 failed: too few calls to the handler\n");
|
||||
} else if(i != j){
|
||||
// the loop should have called foo() i times, and foo() should
|
||||
// have incremented j once per call, so j should equal i.
|
||||
// once possible source of errors is that the handler may
|
||||
// return somewhere other than where the timer interrupt
|
||||
// occurred; another is that that registers may not be
|
||||
// restored correctly, causing i or j or the address ofj
|
||||
// to get an incorrect value.
|
||||
printf("\ntest1 failed: foo() executed fewer times than it was called\n");
|
||||
} else {
|
||||
printf("test1 passed\n");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// tests that kernel does not allow reentrant alarm calls.
|
||||
void
|
||||
test2()
|
||||
{
|
||||
int i;
|
||||
int pid;
|
||||
int status;
|
||||
|
||||
printf("test2 start\n");
|
||||
if ((pid = fork()) < 0) {
|
||||
printf("test2: fork failed\n");
|
||||
}
|
||||
if (pid == 0) {
|
||||
count = 0;
|
||||
sigalarm(2, slow_handler);
|
||||
for(i = 0; i < 1000*500000; i++){
|
||||
if((i % 1000000) == 0)
|
||||
write(2, ".", 1);
|
||||
if(count > 0)
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
printf("\ntest2 failed: alarm not called\n");
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
wait(&status);
|
||||
if (status == 0) {
|
||||
printf("test2 passed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
slow_handler()
|
||||
{
|
||||
count++;
|
||||
printf("alarm!\n");
|
||||
if (count > 1) {
|
||||
printf("test2 failed: alarm handler called more than once\n");
|
||||
exit(1);
|
||||
}
|
||||
for (int i = 0; i < 1000*500000; i++) {
|
||||
asm volatile("nop"); // avoid compiler optimizing away loop
|
||||
}
|
||||
sigalarm(0, 0);
|
||||
sigreturn();
|
||||
}
|
||||
|
||||
//
|
||||
// dummy alarm handler; after running immediately uninstall
|
||||
// itself and finish signal handling
|
||||
void
|
||||
dummy_handler()
|
||||
{
|
||||
sigalarm(0, 0);
|
||||
sigreturn();
|
||||
}
|
||||
|
||||
//
|
||||
// tests that the return from sys_sigreturn() does not
|
||||
// modify the a0 register
|
||||
void
|
||||
test3()
|
||||
{
|
||||
uint64 a0;
|
||||
|
||||
sigalarm(1, dummy_handler);
|
||||
printf("test3 start\n");
|
||||
|
||||
asm volatile("lui a5, 0");
|
||||
asm volatile("addi a0, a5, 0xac" : : : "a0");
|
||||
for(int i = 0; i < 500000000; i++)
|
||||
;
|
||||
asm volatile("mv %0, a0" : "=r" (a0) );
|
||||
|
||||
if(a0 != 0xac)
|
||||
printf("test3 failed: register a0 changed\n");
|
||||
else
|
||||
printf("test3 passed\n");
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
#include "kernel/types.h"
|
||||
#include "kernel/stat.h"
|
||||
#include "user/user.h"
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
sleep(1);
|
||||
exit(0);
|
||||
}
|
||||
17
user/call.c
17
user/call.c
@ -1,17 +0,0 @@
|
||||
#include "kernel/param.h"
|
||||
#include "kernel/types.h"
|
||||
#include "kernel/stat.h"
|
||||
#include "user/user.h"
|
||||
|
||||
int g(int x) {
|
||||
return x+3;
|
||||
}
|
||||
|
||||
int f(int x) {
|
||||
return g(x);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
printf("%d %d\n", f(8)+1, 13);
|
||||
exit(0);
|
||||
}
|
||||
240
user/cowtest.c
Normal file
240
user/cowtest.c
Normal file
@ -0,0 +1,240 @@
|
||||
//
|
||||
// tests for copy-on-write fork() assignment.
|
||||
//
|
||||
|
||||
#include "kernel/types.h"
|
||||
#include "kernel/memlayout.h"
|
||||
#include "user/user.h"
|
||||
|
||||
// allocate more than half of physical memory,
|
||||
// then fork. this will fail in the default
|
||||
// kernel, which does not support copy-on-write.
|
||||
void
|
||||
simpletest()
|
||||
{
|
||||
uint64 phys_size = PHYSTOP - KERNBASE;
|
||||
int sz = (phys_size / 3) * 2;
|
||||
|
||||
printf("simple: ");
|
||||
|
||||
char *p = sbrk(sz);
|
||||
if(p == (char*)0xffffffffffffffffL){
|
||||
printf("sbrk(%d) failed\n", sz);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
for(char *q = p; q < p + sz; q += 4096){
|
||||
*(int*)q = getpid();
|
||||
}
|
||||
|
||||
int pid = fork();
|
||||
if(pid < 0){
|
||||
printf("fork() failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if(pid == 0)
|
||||
exit(0);
|
||||
|
||||
wait(0);
|
||||
|
||||
if(sbrk(-sz) == (char*)0xffffffffffffffffL){
|
||||
printf("sbrk(-%d) failed\n", sz);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf("ok\n");
|
||||
}
|
||||
|
||||
// three processes all write COW memory.
|
||||
// this causes more than half of physical memory
|
||||
// to be allocated, so it also checks whether
|
||||
// copied pages are freed.
|
||||
void
|
||||
threetest()
|
||||
{
|
||||
uint64 phys_size = PHYSTOP - KERNBASE;
|
||||
int sz = phys_size / 4;
|
||||
int pid1, pid2;
|
||||
|
||||
printf("three: ");
|
||||
|
||||
char *p = sbrk(sz);
|
||||
if(p == (char*)0xffffffffffffffffL){
|
||||
printf("sbrk(%d) failed\n", sz);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
pid1 = fork();
|
||||
if(pid1 < 0){
|
||||
printf("fork failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
if(pid1 == 0){
|
||||
pid2 = fork();
|
||||
if(pid2 < 0){
|
||||
printf("fork failed");
|
||||
exit(-1);
|
||||
}
|
||||
if(pid2 == 0){
|
||||
for(char *q = p; q < p + (sz/5)*4; q += 4096){
|
||||
*(int*)q = getpid();
|
||||
}
|
||||
for(char *q = p; q < p + (sz/5)*4; q += 4096){
|
||||
if(*(int*)q != getpid()){
|
||||
printf("wrong content\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
exit(-1);
|
||||
}
|
||||
for(char *q = p; q < p + (sz/2); q += 4096){
|
||||
*(int*)q = 9999;
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
for(char *q = p; q < p + sz; q += 4096){
|
||||
*(int*)q = getpid();
|
||||
}
|
||||
|
||||
wait(0);
|
||||
|
||||
sleep(1);
|
||||
|
||||
for(char *q = p; q < p + sz; q += 4096){
|
||||
if(*(int*)q != getpid()){
|
||||
printf("wrong content\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if(sbrk(-sz) == (char*)0xffffffffffffffffL){
|
||||
printf("sbrk(-%d) failed\n", sz);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf("ok\n");
|
||||
}
|
||||
|
||||
char junk1[4096];
|
||||
int fds[2];
|
||||
char junk2[4096];
|
||||
char buf[4096];
|
||||
char junk3[4096];
|
||||
|
||||
// test whether copyout() simulates COW faults.
|
||||
void
|
||||
filetest()
|
||||
{
|
||||
printf("file: ");
|
||||
|
||||
buf[0] = 99;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
if(pipe(fds) != 0){
|
||||
printf("pipe() failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
int pid = fork();
|
||||
if(pid < 0){
|
||||
printf("fork failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
if(pid == 0){
|
||||
sleep(1);
|
||||
if(read(fds[0], buf, sizeof(i)) != sizeof(i)){
|
||||
printf("error: read failed\n");
|
||||
exit(1);
|
||||
}
|
||||
sleep(1);
|
||||
int j = *(int*)buf;
|
||||
if(j != i){
|
||||
printf("error: read the wrong value\n");
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
if(write(fds[1], &i, sizeof(i)) != sizeof(i)){
|
||||
printf("error: write failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
int xstatus = 0;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
wait(&xstatus);
|
||||
if(xstatus != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(buf[0] != 99){
|
||||
printf("error: child overwrote parent\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("ok\n");
|
||||
}
|
||||
|
||||
//
|
||||
// try to expose races in page reference counting.
|
||||
//
|
||||
void
|
||||
forkforktest()
|
||||
{
|
||||
printf("forkfork: ");
|
||||
|
||||
int sz = 256 * 4096;
|
||||
char *p = sbrk(sz);
|
||||
memset(p, 27, sz);
|
||||
|
||||
int children = 3;
|
||||
|
||||
for(int iter = 0; iter < 100; iter++){
|
||||
for(int nc = 0; nc < children; nc++){
|
||||
if(fork() == 0){
|
||||
sleep(2);
|
||||
fork();
|
||||
fork();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
for(int nc = 0; nc < children; nc++){
|
||||
int st;
|
||||
wait(&st);
|
||||
}
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
for(int i = 0; i < sz; i += 4096){
|
||||
if(p[i] != 27){
|
||||
printf("error: parent's memory was modified!\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
printf("ok\n");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
simpletest();
|
||||
|
||||
// check that the first simpletest() freed the physical memory.
|
||||
simpletest();
|
||||
|
||||
threetest();
|
||||
threetest();
|
||||
threetest();
|
||||
|
||||
filetest();
|
||||
|
||||
forkforktest();
|
||||
|
||||
printf("ALL COW TESTS PASSED\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
@ -22,8 +22,6 @@ int getpid(void);
|
||||
char* sbrk(int);
|
||||
int sleep(int);
|
||||
int uptime(void);
|
||||
int sigalarm(int, void(*)());
|
||||
int sigreturn(void);
|
||||
|
||||
// ulib.c
|
||||
int stat(const char*, struct stat*);
|
||||
|
||||
@ -36,5 +36,3 @@ entry("getpid");
|
||||
entry("sbrk");
|
||||
entry("sleep");
|
||||
entry("uptime");
|
||||
entry("sigalarm");
|
||||
entry("sigreturn");
|
||||
|
||||
Reference in New Issue
Block a user