cow lab initialized

This commit is contained in:
2025-06-28 09:57:36 +08:00
parent 7c42fbed17
commit c6fa25504f
25 changed files with 351 additions and 617 deletions

View File

@ -213,8 +213,7 @@ endif
ifeq ($(LAB),traps)
UPROGS += \
$U/_call\
$U/_bttest\
$U/_alarmtest
$U/_bttest
endif
ifeq ($(LAB),lazy)

46
README
View File

@ -1,46 +0,0 @@
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".

View File

@ -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".

View File

@ -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()` 返回。
---

View File

@ -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 会从栈上读取一个任意的、未初始化的内存位置的值,这个值是不可预测的(垃圾值)。

View File

@ -1 +1 @@
LAB=traps
LAB=cow

59
grade-lab-cow Normal file
View 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()

View File

@ -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()

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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()

View File

@ -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

View File

@ -20,5 +20,3 @@
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_sigalarm 22
#define SYS_sigreturn 23

View File

@ -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;
}

View File

@ -76,21 +76,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();
}

View File

@ -1 +0,0 @@
8

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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
View 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);
}

View File

@ -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*);

View File

@ -244,7 +244,7 @@ copyinstr3(char *s)
// See if the kernel refuses to read/write user memory that the
// application doesn't have anymore, because it returned it.
void
rwsbrk(char* argv)
rwsbrk()
{
int fd, n;

View File

@ -36,5 +36,3 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("sigalarm");
entry("sigreturn");