实验环境
Vmware Station + Ubuntu 20.04
参考链接
Fan Xiao的博客
Tools used in 6.S081
Complie your tool chain
工具准备
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
gdb在ubuntu上安装失败,参考complie your tool chain自行编译,镜像站链接
# clone source code
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
# install dependent lib
sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
更新:可以用gdb-multiarch调试,不用编译
Lab 1: Xv6 and Unix utilities
编写软件与测试
在用户目录xv6-labs-2021/user/
编写用户程序,且在xv6-labs-2021/Makefile
中UPROGS
处追加自己编写的用户程序。
UPROGS=\
$U/_cat\
$U/_echo\
$U/_forktest\
$U/_grep\
$U/_init\
$U/_kill\
$U/_ln\
$U/_ls\
$U/_mkdir\
$U/_rm\
$U/_sh\
$U/_stressfs\
$U/_usertests\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_sleep\ # Add
测试时
./grade-lab-util prog_to_test
boot xv6
git clone git://g.csail.mit.edu/xv6-labs-2020
cd xv6-labs-2020
git checkout util
make qemu
出现编译错误
user/sh.c: In function ‘runcmd’:
user/sh.c:58:1: error: infinite recursion detected [-Werror=infinite-recursion]
58 | runcmd(struct cmd *cmd)
| ^~~~~~
user/sh.c:89:5: note: recursive call
89 | runcmd(rcmd->cmd);
| ^~~~~~~~~~~~~~~~~
user/sh.c:109:7: note: recursive call
109 | runcmd(pcmd->left);
| ^~~~~~~~~~~~~~~~~~
user/sh.c:116:7: note: recursive call
116 | runcmd(pcmd->right);
| ^~~~~~~~~~~~~~~~~~~
user/sh.c:95:7: note: recursive call
95 | runcmd(lcmd->left);
| ^~~~~~~~~~~~~~~~~~
user/sh.c:97:5: note: recursive call
97 | runcmd(lcmd->right);
| ^~~~~~~~~~~~~~~~~~~
user/sh.c:127:7: note: recursive call
127 | runcmd(bcmd->cmd);
| ^~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make: *** [<内置>:user/sh.o] 错误 1
参考MIT6.s081 编译QEMU中的错误 user/sh.c:58:1: error: infinite recursion detected [-Werror=infinite-recursi 递归],在runcmd
前添加编译属性永不返回。
__attribute__((noreturn))
void
runcmd(struct cmd *cmd)
qemu卡住,版本原因,自行编译qemu
wget https://download.qemu.org/qemu-5.1.0.tar.xz
tar xf qemu-5.1.0.tar.xz
cd qemu-5.1.0
sudo apt-get install libglib2.0-dev
sudo apt-get install libpixman-1-dev
./configure --disable-kvm --disable-werror --prefix=/usr/local --target-list="riscv64-softmmu"
make
sudo make install
sys call
sleep
/* xv6-labs-2021/user/sleep.c */
#include "kernel/types.h" // 定义基本类型
#include "kernel/stat.h" // 定义文件类型
#include "user/user.h" // 定义系统调用
int main(int argc, char* argv[]){
if(argc == 1){
fprintf(2, "Need a number\n"); // 向文件描述符2 输出,即向标准错误
}
else {
int time = atoi(argv[1]);
sleep(time);
return 0;
}
}
pingpong
#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char* argv[]){
int c2f[2];
int f2c[2];
pipe(c2f);
pipe(f2c);
char buf[2];
int pid = fork();
if(pid == 0){
// pipe的输入为长度为2的int数组p
// 其中p[0]对应输入文件描述符,p[1]对应输出文件描述符。
close(f2c[1]); // 关闭 f2c 输出
close(c2f[0]); // 关闭 c2f 输入
read(f2c[0], buf, 1);
printf("%d: received ping\n", getpid());
write(c2f[1], " ", 1);
close(f2c[0]);
close(c2f[1]);
exit(0);
}
else{
close(f2c[0]); // 关闭 f2c 输入
close(c2f[1]); // 关闭 c2f 输出
write(f2c[0], " ", 1);
read(c2f[1], buf, 1);
printf("%d: received pong\n", getpid());
close(f2c[1]);
close(c2f[0]);
exit(0);
}
}
primes
#include "kernel/types.h"
#include "user/user.h"
#define NUM 36
#define R 0
#define W 1
void child(int *pl);
int main()
{
int p[2];
pipe(p);
if (fork() == 0)
{
child(p);
}
else{
close(p[R]);
for(int i = 2; i < NUM;i++){
write(p[W], &i, sizeof(int));
}
close(p[W]);
wait(0); // 等待子进程,子进程等待自己的子进程
exit(0);
}
}
__attribute__ ((noreturn))
void child(int *pl){
int pr[2];
int n;
close(pl[W]);
if(read(pl[R], &n, sizeof(int))==0){
exit(0);
}
pipe(pr);
if(fork()==0){
child(pr);
}
else{
close(pr[R]);
printf("prime %d\n", n);
int prime = n;
while (read(pl[R], &n, sizeof(int)) != 0) {
if (n%prime != 0) {
write(pr[W], &n, sizeof(int));
}
}
close(pr[W]);
wait(0); // 等待子进程
exit(0);
}
}
find
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fs.h"
#include "user/user.h"
void find(char *path, char *target)
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if ((fd = open(path, 0)) < 0)
{
fprintf(2, "find: cannot open %s\n", path);
return;
}
if (fstat(fd, &st) < 0)
{
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}
while (read(fd, &de, sizeof(de)) == sizeof(de))
{
strcpy(buf, path);
p = buf + strlen(path);
*(p++) = '/';
if (de.inum == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if (stat(buf, &st) < 0)
{
fprintf(2, "find: cannot stat %s\n", buf);
continue;
}
switch (st.type)
{
case T_FILE:
if (strcmp(target, de.name)==0)
{
printf("%s\n", buf);
}
break;
case T_DIR:
if ((strcmp(de.name, ".") != 0) && (strcmp(de.name, "..") != 0))
{
find(buf, target);
break;
}
default:
break;
}
}
return;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("Need 2 arguments");
}
char *target_path = argv[1];
char *target_file = argv[2];
find(target_path, target_file);
exit(0);
}
xargs
#include "kernel/types.h"
#include "kernel/param.h"
#include "user/user.h"
#define MAX_LEN 100
// echo hello | xargs echo bye
int main(int argc, char *argv[])
{
// xargs执行时, argv[1] = "echo" 是要执行的命令
char *command = argv[1];
char bf;
char paramv[MAXARG][MAX_LEN]; // arguments, such as bye
char *m[MAXARG]; // 传入exec,内容上由paramv赋值
while (1)
{
int count = argc - 1;
memset(paramv, 0, MAXARG * MAX_LEN);
// 读取xargs的参数
for (int i = 0; i < argc; i++)
{
strcpy(paramv[i - 1], argv[i]);
}
int cursor = 0; // cursor pointing the char position in single_arg
int flag = 0; // flag indicating whether thers is argument preceding space
int read_result;
// 从标准输入逐个字符地读取参数
while ((read_result = read(0, &bf, 1)) > 0 && bf != '\n')
{
if (bf == ' ' && flag == 1) // 去除参数前导空格
{
count++;
cursor = 0;
flag = 0;
}
else if (bf != ' ')
{
paramv[count][cursor++] = bf;
// cursor++;
flag = 1;
}
}
// encounters EOF of input or \n
if (read_result <= 0)
{
break;
}
for (int i = 0; i < MAXARG - 1; i++)
{
m[i] = paramv[i];
}
m[MAXARG - 1] = 0;
if (fork() == 0)
{
exec(command, m);
exit(0);
}
else
{
wait(0);
}
}
exit(0);
}
Lab 2: Syscalls
trap
系统调用write函数
; usys.S
.global write
write:
li a7, SYS_write
ecall
ret
.global close
ecall
指令会
- 把模式从user mode切换到supervisor mode
- 保存
pc
寄存器的值(ecall
指令的地址)到sepc
寄存器 - 跳转到
stvec
寄存器指向的指令即trampoline.S
的开始uservec
函数
在trampoline.S
中交换寄存器a0
和 sscratch
的值,这样a0
寄存器中就存储了一个指向结构体trapframe
的指针,用此结构体保存用户程序的寄存器信息。
.globl uservec
uservec:
#
# trap.c sets stvec to point here, so
# traps from user space start here,
# in supervisor mode, but with a
# user page table.
#
# sscratch points to where the process's p->trapframe is
# mapped into user space, at TRAPFRAME.
# swap a0 and sscratch
# so that a0 is TRAPFRAME
csrrw a0, sscratch, a0
# save the user registers in TRAPFRAME
sd ra, 40(a0) ; save double word ra to *(ao+40)
sd sp, 48(a0)
sd gp, 56(a0)
sd tp, 64(a0)
# ...
# save the user a0 in p->trapframe->a0
csrr t0, sscratch
sd t0, 112(a0)
# restore kernel stack pointer from p->trapframe->kernel_sp
ld sp, 8(a0)
# make tp hold the current hartid, from p->trapframe->kernel_hartid
ld tp, 32(a0)
# load the address of usertrap(), p->trapframe->kernel_trap
ld t0, 16(a0)
# restore kernel page table from p->trapframe->kernel_satp
ld t1, 0(a0)
csrw satp, t1
sfence.vma zero, zero
# a0 is no longer valid, since the kernel page
# table does not specially map p->tf.
# jump to usertrap(), which does not return
jr t0
trampoline.S
在内核和用户空间中的虚拟地址一致,所以切换用户页表到内核页表时,没有影响
# restore kernel page table from p->trapframe->kernel_satp
ld t1, 0(a0)
csrw satp, t1
sfence.vma zero, zero
从uservec
到usertrap
,再到usertrapret
,最后通过trampoline.S
中的userret
从supervisor mode切换回user mode。
// trap.c
// usertrap 的作用是确定trap的原因,处理它并返回
void
usertrap(void)
{
int which_dev = 0;
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// 首先更改stvec寄存器的值,内核的trap应该由kernelvec指向的函数处理
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// save user program counter.
p->trapframe->epc = r_sepc();
if(r_scause() == 8){
// system call
if(p->killed)
exit(-1);
// 指向ecall的吓一跳指令
p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers,
// so don't enable until done with those registers.
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
if(p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
usertrapret();
}
// 该函数设置stvec、uservec等寄存器,为用户空间未来的trap作为准
void
usertrapret(void)
{
struct proc *p = myproc();
// we're about to switch the destination of traps from
// kerneltrap() to usertrap(), so turn off interrupts until
// we're back in user space, where usertrap() is correct.
intr_off();
// send syscalls, interrupts, and exceptions to trampoline.S
w_stvec(TRAMPOLINE + (uservec - trampoline));
// set up trapframe values that uservec will need when
// the process next re-enters the kernel.
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
// set up the registers that trampoline.S's sret will use
// to get to user space.
// set S Previous Privilege mode to User.
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
// set S Exception Program Counter to the saved user pc.
w_sepc(p->trapframe->epc);
// tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
// jump to trampoline.S at the top of memory, which
// switches to the user page table, restores user registers,
// and switches to user mode with sret.
uint64 fn = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64,uint64))fn)(TRAPFRAME, satp); // 调用时将指针传递给a0
}
最后在userret
中使用sret
指令
- 切换回user mode
- 从
sepc
寄存器中恢复程序计数器到pc
寄存器,继续执行用户程序指令
trace
Some hints:
- Add $U/_trace to UPROGS in Makefile
在 Makefile 中将 $U/_trace 添加到 UPROGS 中。- Run and you will see that the compiler cannot compile user/trace.c, because the user-space stubs for the system call don’t exist yet: add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h. The Makefile invokes the perl script user/usys.pl, which produces user/usys.S, the actual system call stubs, which use the RISC-V ecall instruction to transition to the kernel. Once you fix the compilation issues, run ; it will fail because you haven’t implemented the system call in the kernel yet. make qemutrace 32 grep hello README
运行时会发现编译器无法编译 user/trace.c,因为系统调用的用户空间存根尚不存在:需要在 user/user.h 中添加系统调用的原型,在 user/usys.pl 中添加存根,并在 kernel/syscall.h 中添加系统调用号。Makefile 会调用 Perl 脚本 user/usys.pl,该脚本生成 user/usys.S,即实际的系统调用存根,这些存根使用 RISC-V 的 ecall 指令切换到内核。解决编译问题后,运行以下命令;由于尚未在内核中实现系统调用,它会失败。
make qemutrace 32 grep hello README- Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.
在 kernel/sysproc.c 中添加一个 sys_trace() 函数,通过在 proc 结构中的新变量中记住其参数来实现新的系统调用(参见 kernel/proc.h)。从用户空间检索系统调用参数的函数在 kernel/syscall.c 中,你可以在 kernel/sysproc.c 中看到它们使用的示例。- Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.
修改 fork()(参见 kernel/proc.c)以将跟踪掩码从父进程复制到子进程。- Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.
修改 kernel/syscall.c 中的 syscall() 函数以打印跟踪输出。你需要添加一个系统调用名称数组以便于索引。
user/user.h
添加系统调用原型,该文件包含了一些系统调用函数如fork
的声明,
int trace(int);
在user/usys.pl
中添加
entry("trace");
Makefile 会调用 Perl 脚本 user/usys.pl,该脚本生成 user/usys.S,即实际的系统调用存根,这些存根使用 RISC-V 的 ecall 指令切换到内核。
在kernel/sysycall.h
定义trace的系统调用编号,为下一个系统调用编号。
#define SYS_trace 22
在kernel/syscall.c
中有函数指针数组syscalls
,在该数组添加sys_trace
static uint64 (*syscalls[])(void) = {
// ...
[SYS_trace] sys_trace,
// 在数组syscalls下标为SYS_trace处的函数指针指向sys_trace
}
uint64 (*syscalls[])(void)
:定义了一个函数指针数组,数组的每个元素是一个指向函数的指针。这些函数的返回值类型是uint64
(64位无符号整数),并且这些函数不接受任何参数(void
)。
在kernel/sysproc.c
中添加一个sys_trace()
函数,通过在proc
结构中的新变量中记住其参数来实现新的系统调用(参见kernel/proc.h
)。从用户空间检索系统调用参数的函数是argint(int n, int *ip)
,可以在kernel/sysproc.c
中看到示例。
// kernel/sysproc.c
uint64
sys_trace(void){
int n; // n is tracing mask
if(argint(0,&n)<0){ //argint(int n, int *ip)函数的作用是从用户空间获取第n个32位整数类型的系统调用参数,并将其存储到指定的指针所指向的内存位置。
return -1;
}
myproc()->mask= n;
return 0;
}
// kernel/proc.h
struct proc{
// ...
int mask;
}
修改void syscall((void)
函数
// kernel/syscall.c
void
syscall(void)
{
int num;
struct proc *p = myproc();
char *syscall_name[22] = {"fork", "exit", "wait", "pipe", "read", "kill", "exec", "fstat", "chdir", "dup", "getpid", "sbrk", "sleep", "uptime", "open", "write", "mknod", "unlink", "link", "mkdir", "close", "trace"}; // add
num = p->trapframe->a7;
if (num > 0 && num < NELEM(syscalls) && syscalls[num])
{
p->trapframe->a0 = syscalls[num]();
// add
if ((1 << num) & (p->mask))
{
printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num - 1], p->trapframe->a0);
}
}
else
{
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
sysinfo
In this assignment you will add a system call, sysinfo, that collects information about the running system. The system call takes one argument: a pointer to a struct sysinfo (see kernel/sysinfo.h). The kernel should fill out the fields of this struct: the freemem field should be set to the number of bytes of free memory, and the nproc field should be set to the number of processes whose state is not UNUSED. We provide a test program sysinfotest; you pass this assignment if it prints “sysinfotest: OK”.
添加一个系统调用 sysinfo,接受一个参数,该参数是指向结构体sysinfo的指针,内核填充这个结构体的字段:freemem字段指示空闲内存的字节数, nproc字段指示状态为UNUSED的进程数目,测试的用户程序是sysinfotest
添加系统调用的过程同上,在user/user.h
中添加系统调用函数声明
struct sysinfo; // 前向声明
int sysinfo(struct sysinfo*);
在user/usys.pl
中增加入口
entry("sysinfo");
在kernel/syscall.h
中定义系统调用编号
#define SYS_sysinfo 23
在kernel/syscall.c
的syscalls数组添加对应的函数指针
static uint64 (*syscalls[])(void) = {
// ...
[SYS_sysinfo] sys_sysinfo
在kernel/sysproc.c
中实现函数
extern uint64 freememsize(void);
extern int nproc_active(void);
uint64 sys_sysinfo(void){
uint64 p;
if(argaddr(0,&p)<0){
return -1;
}
struct sysinfo si;
si.freemem = freememsize(); // 需要在kalloc.c中实现
si.nproc = nproc_active(); // 需要在proc.c中实现
if(copyout(myproc()->pagetable, p, (char *)&si, sizeof(si)) < 0) // copy out the sysinfo struct si from kernel space to user space
return -1;
return 0;
}