MIT6.S081 2021

  • 环境配置
  • Xv6 and Unix utilities
    • vscode格式化头文件排序问题
    • 以地址空间的视角看待变量
    • 其他
    • 代码参考
  • system calls
    • trace
    • Sysinfo
  • page tables
    • Speed up system calls
    • Print a page table
    • Detecting which pages have been accessed
  • traps
    • RISC-V assembly
    • Backtrace
    • Alarm
  • Copy-on-Write
  • Multithreading
  • networking
  • locks
  • file system
  • mmap

6.S081 lab地址

推荐博客:二十八画生征友:一起来通关6.S081/6.828吧~

piazza课程主页:https://piazza.com/mooc_self-learning_university/fall2020/6s081
access code: 6s081

环境配置

虚拟机基本配置
下载Ubuntu镜像华为镜像站, 在software&update中修改软件源(我选的是阿里源)。
更新软件:apt-get update & apt-get upgrade
创建root用户 : sudo passwd root
安装open-vm-tools-desktop 支持宿主机 虚拟机之间复制粘贴
设置ssh远程登录及公钥登录登录
安装ssh相关服务

sudo apt-get install openssh-client openssh-server

修改ssh配置文件/etc/ssh/sshd_config

Port 22
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication yes

将公钥添加到authorized_keys中

gedit pub # 复制粘贴宿主机公钥
mkdir ~/.ssh # 其中~为你想用公钥登录的用户目录,我直接在root用户下创建
cat pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
sudo service sshd restart

vscode远程登录
安装Remote-ssh插件-》点击左下角图标-》Connect to Host-》Configure SSH Hosts
新建一段为

Host 6.081HostName 192.168.252.135 Port 22User root

虚拟机IP地址,使用ifconfig查看
而后直接Connect to Host登录即可

安装实验工具

sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu

后面发现在云服务器上安装一下qemu就好了,没必要用VMware。。
vscode crtl + p快捷键被占用,修改crtl + p快捷方式
退出qemu两种方法:

  1. 在另一个终端中输入 killall qemu-system-arm(网上的方法,我试着不行,使用ps -A | grep qemu找到进程id,而后kill -9 pid杀死进程)
  2. 在 qemu 中 输入ctrl+a 抬起后,再输入’x’。

Xv6 and Unix utilities

vscode格式化头文件排序问题

由于设置了保存时自动格式化,保存时头文件会自动排序

#include "kernel/types.h"
#include "kernel/stat.h"
// 变成了
#include "kernel/types.h"
#include "kernel/stat.h"
// 使得stat.h中uint未声明
struct stat {int dev;     // File system's disk deviceuint ino;    // Inode numbershort type;  // Type of fileshort nlink; // Number of links to fileuint64 size; // Size of file in bytes
};

解决:将Clang_format_sort Include改成false

以地址空间的视角看待变量

int number
res = read(read_fd, &number, 4);
// 等价于
char number[4]
res = read(read_fd, number, 4);


把普通的变量当作数组,很巧妙的视角。

其他

fmtname函数
ls.c中fmtname函数在末尾填充空格,find.c使用这个函数时需换成在末尾加上’\0’

warning:suggest parentheses around assignment used as truth value

while(res = func()){}
// 改成
while((res = func())){}

加一个括号括起来就可以了

a label can only be part of a statement and a declaration is not a statement
在写代码的时候,变量的声明不应该出现在label之后。中间加上大括号即可

case T_FILE: int a;break;
// 改成
case T_FILE:{int a;break;
} 

xargs

char*
gets(char *buf, int max)
{int i, cc;char c;for(i=0; i+1 < max; ){cc = read(0, &c, 1);if(cc < 1)break;buf[i++] = c;if(c == '\n' || c == '\r')break;}buf[i] = '\0';return buf;
}

这个部分是耗时最久的,刚开始我没理解题目意思,一直打算按空格分割字符串,将其拆分成多行,使用strtok函数发现并没有提供,而动态数组也不知道咋申请,如果用定长的二维数组又觉得太奇怪,故一直卡在这了。后面发现原来将参数当成一行就行了。我又直接把系统gets函数拿来用,没有发现gets函数最后还是把’\n’放在字符串里面了,使得一直显示如下的错误。所以说与其抄系统的代码,不如自己写一个。

$ sh < xargstest.sh
$ $ $ $ $ $ ./a/b // 自己测试的输出grep: cannot open ./a/b ./c/b   // 自己测试的输出grep: cannot open ./c/b./b    // 自己测试的输出grep: cannot open ./b

我自己实现的gets函数如下,如果不设返回值,单以字符串第一位是否’\0’判断是否结束,则\n\n会被理解为结束,故设置返回值,当读到文件末尾时返回-1。
字符串长度为0且返回值为-1时表示参数读取完毕

int my_gets(char* buf, int max) {  //读取字符串,读到文件末尾时返回-1int i, cc;char c;int res = 0;for (i = 0; i + 1 < max;) {cc = read(0, &c, 1);if (cc < 1) {res = -1;break;}if (c == '\n') break;buf[i++] = c;}buf[i] = '\0';return res;
}

通过截图

代码参考

sleep

Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int main(int argc, char *argv[]) {if (argc != 2) {  // 参数格式不对fprintf(2, "Usage: sleep ticks\n");exit(1);}int ticks = atoi(argv[1]);  // 将字符串转换成整数int res = sleep(ticks);if (res == -1) {exit(-1);}exit(0);
}

pingpong

Write a program that uses UNIX system calls to ‘‘ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “: received ping”, where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “: received pong”, and exit. Your solution should be in the file user/pingpong.c.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int main(int argc, char *argv[]) {int p[2];char msg = 'a';char buf;pipe(p);if (fork() == 0) {read(p[0], &buf, 1);  // 读取字符int pid = getpid();fprintf(2, "%d: received ping\n", pid);write(p[1], &buf, 1);  // 向管道写入相同字符close(p[0]);           // 关闭读端close(p[1]);           // 关闭写端exit(0);} else {write(p[1], &msg, 1);  // 向管道写入字符read(p[0], &buf, 1);   // 读取字符int pid = getpid();fprintf(2, "%d: received pong\n", pid);close(p[0]);  // 关闭读端close(p[1]);  // 关闭写端}exit(0);
}

primes

Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"void func(int read_fd, int write_fd) {close(write_fd);int first_number;int other_number;// 读取第一个数字,作为素数int res = read(read_fd, &first_number, 4);if (res == 0) {  // 管道关闭,立即退出进程close(read_fd);exit(-1);} else {int p[2];pipe(p);if (fork() == 0) {  // 创建子进程func(p[0], p[1]);} else {close(p[0]);while (1) {// 读取其他数字,若不被第一个数字整除,则传给子进程res = read(read_fd, &other_number, 4);// 管道关闭,输出第一个数字后等待子进程退出,下列语句的顺序很重要if (res == 0) {fprintf(2, "prime %d\n", first_number);close(p[1]);wait((int *)0);exit(0);} else {if (other_number % first_number != 0) {write(p[1], &other_number, 4);}}}}}
}
int main(int argc, char *argv[]) {int p[2];pipe(p);if (fork() == 0) {func(p[0], p[1]);} else {close(p[0]);for (int i = 2; i <= 35; i++) {  // 将数字传给子进程write(p[1], &i, 4);}close(p[1]);wait((int *)0);}exit(0);
}

find

Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"char *fmtname(char *path) {static char buf[DIRSIZ + 1];char *p;// Find first character after last slash.for (p = path + strlen(path); p >= path && *p != '/'; p--);p++;uint name_len = strlen(p);// Return blank-padded name.if (name_len >= DIRSIZ) return p;memmove(buf, p, strlen(p));buf[name_len] = 0;  // 末尾加上'\0'return buf;
}void find(char *path, char *target_filename) {// printf("test1: %s %s\n", path, target_filename);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;}switch (st.type) {case T_FILE: {char *filename = fmtname(path);// printf("test2: %s\n", filename);if (strcmp(filename, target_filename) == 0) {printf("%s\n", path);}break;}case T_DIR:if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {printf("find: path too long\n");break;}strcpy(buf, path);p = buf + strlen(buf);*p++ = '/';while (read(fd, &de, sizeof(de)) == sizeof(de)) {if (de.inum == 0) continue;memmove(p, de.name, DIRSIZ);p[DIRSIZ] = 0;if (stat(buf, &st) < 0) {printf("find: cannot stat %s\n", buf);continue;}// 判断是否为.或..if (strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {find(buf, target_filename);}}break;}close(fd);
}
int main(int argc, char *argv[]) {if (argc < 3) {fprintf(2, "Usage: find path target_filename\n");exit(1);}find(argv[1], argv[2]);exit(0);
}

xargs

Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"#define MAX_EXTRA_ARG_LEN 100
int my_gets(char* buf, int max) {  //读取字符串,读到文件末尾时返回-1int i, cc;char c;int res = 0;for (i = 0; i + 1 < max;) {cc = read(0, &c, 1);if (cc < 1) {res = -1;break;}if (c == '\n') break;buf[i++] = c;}buf[i] = '\0';return res;
}
int main(int argc, char* argv[]) {char extra_args[MAX_EXTRA_ARG_LEN];while (1) {int res = my_gets(extra_args, MAX_EXTRA_ARG_LEN);// 双重判断,避免\n\n的情况if (extra_args[0] == '\0' && res == -1) {break;}if (fork() == 0) {// 此时argv[0]为xargs,需将参数整体往前移动一位for (int i = 0; i < argc - 1; i++) {argv[i] = argv[i + 1];}argv[argc - 1] = extra_args;  // 设置额外参数exec(argv[0], argv);exit(0);} else {wait((int*)0);}}exit(0);
}

system calls

trace

In this assignment you will add a system call tracing feature that may help you when debugging later labs. You’ll create a new trace system call that will control tracing. It should take one argument, an integer “mask”, whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from kernel/syscall.h. You have to modify the xv6 kernel to print out a line when each system call is about to return, if the system call’s number is set in the mask. The line should contain the process id, the name of the system call and the return value; you don’t need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.

trace调用过程

trace(atoi(argv[1]))->.global trace->syscall->sys_trace->syscall

首先在用户空间调用trace函数

#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
main(int argc, char *argv[])
{int i;char *nargv[MAXARG];if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){fprintf(2, "Usage: %s mask command\n", argv[0]);exit(1);}if (trace(atoi(argv[1])) < 0) {fprintf(2, "%s: trace failed\n", argv[0]);exit(1);}for(i = 2; i < argc && i < MAXARG; i++){nargv[i-2] = argv[i];}exec(nargv[0], nargv);exit(0);
}
在user/user.h文件中加入函数声明
int trace(int);
此时可以通过编译过程,只需链接时找到trace函数定义即可

trace函数定义如下:

在kernel/syscall.h中定义调用号
#define SYS_trace 22usys.pl插入函数入口点
entry("trace");usys.S生成代码片段
.global trace
trace:li a7, SYS_trace # 将系统调用号存入a7中ecall # 陷入内核,跳转到syscall函数ret

内核函数调用流程

extern uint64 sys_trace(void);   // sys_trace函数在别处定义
static uint64 (*syscalls[])(void) = {[SYS_fork] sys_fork,   [SYS_exit] sys_exit,     [SYS_wait] sys_wait,[SYS_pipe] sys_pipe,   [SYS_read] sys_read,     [SYS_kill] sys_kill,[SYS_exec] sys_exec,   [SYS_fstat] sys_fstat,   [SYS_chdir] sys_chdir,[SYS_dup] sys_dup,     [SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk,[SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, [SYS_open] sys_open,[SYS_write] sys_write, [SYS_mknod] sys_mknod,   [SYS_unlink] sys_unlink,[SYS_link] sys_link,   [SYS_mkdir] sys_mkdir,   [SYS_close] sys_close,[SYS_trace] sys_trace,
};  //  增加一条[SYS_trace] sys_trace
static char *sys_call_names[] = {[SYS_fork] "fork",   [SYS_exit] "exit",     [SYS_wait] "wait",[SYS_pipe] "pipe",   [SYS_read] "read",     [SYS_kill] "kill",[SYS_exec] "exec",   [SYS_fstat] "fstat",   [SYS_chdir] "chdir",[SYS_dup] "dup",     [SYS_getpid] "getpid", [SYS_sbrk] "sbrk",[SYS_sleep] "sleep", [SYS_uptime] "uptime", [SYS_open] "open",[SYS_write] "write", [SYS_mknod] "mknod",   [SYS_unlink] "unlink",[SYS_link] "link",   [SYS_mkdir] "mkdir",   [SYS_close] "close",[SYS_trace] "trace",
};  // 系统调用对应的名字void syscall(void) {int num;struct proc *p = myproc();num = p->trapframe->a7;if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {p->trapframe->a0 = syscalls[num]();   // 执行sys_trace()int test_bit = p->trace_mask & (1 << num);if (test_bit) {printf("%d: syscall %s -> %d\n", p->pid, sys_call_names[num],p->trapframe->a0);}} else {printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);p->trapframe->a0 = -1;}
}

sys_trace执行流程如下

static uint64 argraw(int n) {    // 将系统调用参数存于各个寄存器struct proc *p = myproc();switch (n) {case 0:return p->trapframe->a0;case 1:return p->trapframe->a1;case 2:return p->trapframe->a2;case 3:return p->trapframe->a3;case 4:return p->trapframe->a4;case 5:return p->trapframe->a5;}panic("argraw");return -1;
}int argint(int n, int *ip) {*ip = argraw(n);return 0;
}uint64 sys_trace(void) {int mask;if (argint(0, &mask) < 0) return -1;   // 获取参数myproc()->trace_mask = mask; // 设置trace_maskreturn 0;
}

其中trace_mask为proc结构体成员,可以在allocproc函数中将trace_mask初始化为0(不一定需要)

struct proc {struct spinlock lock;// p->lock must be held when using these:enum procstate state;  // Process statevoid *chan;            // If non-zero, sleeping on chanint killed;            // If non-zero, have been killedint xstate;            // Exit status to be returned to parent's waitint pid;               // Process ID// wait_lock must be held when using this:struct proc *parent;  // Parent process// these are private to the process, so p->lock need not be held.uint64 kstack;                // Virtual address of kernel stackuint64 sz;                    // Size of process memory (bytes)pagetable_t pagetable;        // User page tablestruct trapframe *trapframe;  // data page for trampoline.Sstruct context context;       // swtch() here to run processstruct file *ofile[NOFILE];   // Open filesstruct inode *cwd;            // Current directorychar name[16];                // Process name (debugging)int trace_mask;               // add trace mask
};static struct proc *allocproc(void) {struct proc *p;for (p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if (p->state == UNUSED) {goto found;} else {release(&p->lock);}}return 0;found:p->pid = allocpid();p->state = USED;p->trace_mask = 0;   // init trace_mask// Allocate a trapframe page.if ((p->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) {freeproc(p);release(&p->lock);return 0;}// Set up new context to start executing at forkret,// which returns to user space.memset(&p->context, 0, sizeof(p->context));p->context.ra = (uint64)forkret;p->context.sp = p->kstack + PGSIZE;return p;
}

当进程创建子进程时,复制trace_mask

// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int fork(void) {int i, pid;struct proc *np;struct proc *p = myproc();// Allocate process.if ((np = allocproc()) == 0) {return -1;}// Copy user memory from parent to child.if (uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) {freeproc(np);release(&np->lock);return -1;}np->sz = p->sz;//  copy the trace mask from the parent to the child process.np->trace_mask = p->trace_mask;// copy saved user registers.*(np->trapframe) = *(p->trapframe);// Cause fork to return 0 in the child.np->trapframe->a0 = 0;// increment reference counts on open file descriptors.for (i = 0; i < NOFILE; i++)if (p->ofile[i]) np->ofile[i] = filedup(p->ofile[i]);np->cwd = idup(p->cwd);safestrcpy(np->name, p->name, sizeof(p->name));pid = np->pid;release(&np->lock);acquire(&wait_lock);np->parent = p;release(&wait_lock);acquire(&np->lock);np->state = RUNNABLE;release(&np->lock);return pid;
}

接着看syscall函数

void syscall(void) {int num;struct proc *p = myproc();num = p->trapframe->a7;if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {p->trapframe->a0 = syscalls[num]();        // a0存储返回值int test_bit = p->trace_mask & (1 << num);  // 取出系统调用号相应位if (test_bit) {     // 如果不为0则输出系统调用相关信息printf("%d: syscall %s -> %d\n", p->pid, sys_call_names[num],p->trapframe->a0);}} else {printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);p->trapframe->a0 = -1;}
}

在测试trace时,总是出现超时的情况,显示MISSING ALL TESTS PASSED
修改gradelib.py中的timeout,将30改成100。测试通过!

   def run_qemu(self, *monitors, **kw):"""Run a QEMU-based test.  monitors should functions that willbe called with this Runner instance once QEMU and GDB arestarted.  Typically, they should register callbacks that throwTerminateTest when stop events occur.  The target_baseargument gives the make target to run.  The make_args argumentshould be a list of additional arguments to pass to make.  Thetimeout argument bounds how long to run before returning."""def run_qemu_kw(target_base="qemu", make_args=[], timeout=100):return target_base, make_args, timeouttarget_base, make_args, timeout = run_qemu_kw(**kw)

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
void sinfo(struct sysinfo *info) {if (sysinfo(info) < 0) {printf("FAIL: sysinfo failed");exit(1);}
}user/user.h头文件中加入sysinfo声明
struct sysinfo;
int sysinfo(struct sysinfo*);

编译过程完成,链接时寻找sysinfo实际定义

kernel/syscall.h头文件插入系统调用号
#define SYS_sysinfo 23usys.pl插入函数入口点
entry("sysinfo");生成汇编代码:
.global sysinfo
sysinfo:li a7, SYS_sysinfoecallret

内核函数调用过程

// syscall.c
extern uint64 sys_sysinfo(void);    // 实际的系统调用函数,在sysproc.c中定义static uint64 (*syscalls[])(void) = {[SYS_fork] sys_fork,   [SYS_exit] sys_exit,     [SYS_wait] sys_wait,[SYS_pipe] sys_pipe,   [SYS_read] sys_read,     [SYS_kill] sys_kill,[SYS_exec] sys_exec,   [SYS_fstat] sys_fstat,   [SYS_chdir] sys_chdir,[SYS_dup] sys_dup,     [SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk,[SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, [SYS_open] sys_open,[SYS_write] sys_write, [SYS_mknod] sys_mknod,   [SYS_unlink] sys_unlink,[SYS_link] sys_link,   [SYS_mkdir] sys_mkdir,   [SYS_close] sys_close,[SYS_trace] sys_trace, [SYS_sysinfo] sys_sysinfo,
};static char *sys_call_names[] = {[SYS_fork] "fork",   [SYS_exit] "exit",     [SYS_wait] "wait",[SYS_pipe] "pipe",   [SYS_read] "read",     [SYS_kill] "kill",[SYS_exec] "exec",   [SYS_fstat] "fstat",   [SYS_chdir] "chdir",[SYS_dup] "dup",     [SYS_getpid] "getpid", [SYS_sbrk] "sbrk",[SYS_sleep] "sleep", [SYS_uptime] "uptime", [SYS_open] "open",[SYS_write] "write", [SYS_mknod] "mknod",   [SYS_unlink] "unlink",[SYS_link] "link",   [SYS_mkdir] "mkdir",   [SYS_close] "close",[SYS_trace] "trace", [SYS_sysinfo] "sysinfo",
};

sys_sysinfo函数

// sysproc.c
#include "sysinfo.h" // 导入struct sysinfo结构体定义uint64 sys_sysinfo(void) {uint64 up_sysinfo;  // user pointer to struct sysinfo// 与trace调用中的argint类似,指针变量同样存储一个整数,只不过这个整数为另一个变量的地址if (argaddr(0, &up_sysinfo) < 0) return -1;   struct sysinfo info;info.freemem = free_memory_amount();       // 计算空余内存info.nproc = live_process_number();       // 计算使用中的进程数struct proc *p = myproc();if (copyout(p->pagetable, up_sysinfo, (char *)&info, sizeof(info)) < 0)return -1;return 0;
}

两个统计函数的定义如下,研究同文件中其他函数可得

uint64 free_memory_amount(){struct run *r;uint64 cnt = 0;acquire(&kmem.lock);r = kmem.freelist;while (r){cnt++;r = r->next;}release(&kmem.lock);return cnt * PGSIZE;
}uint64 live_process_number() {uint64 cnt = 0;struct proc *p;for (p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if (p->state != UNUSED) {cnt++;}release(&p->lock);}return cnt;
}
// 为了使用这两个函数,将其函数声明放在defs.h中
// kalloc.c
void* kalloc(void);
void kfree(void*);
void kinit(void);
uint64 free_memory_amount();// proc.c
int cpuid(void);
void exit(int);
int fork(void);
int growproc(int);
void proc_mapstacks(pagetable_t);
pagetable_t proc_pagetable(struct proc*);
void proc_freepagetable(pagetable_t, uint64);
int kill(int);
struct cpu* mycpu(void);
struct cpu* getmycpu(void);
struct proc* myproc();
void procinit(void);
void scheduler(void) __attribute__((noreturn));
void sched(void);
void sleep(void*, struct spinlock*);
void userinit(void);
int wait(uint64);
void wakeup(void*);
void yield(void);
int either_copyout(int user_dst, uint64 dst, void* src, uint64 len);
int either_copyin(void* dst, int user_src, uint64 src, uint64 len);
void procdump(void);
uint64 live_process_number();

简单看一下copyout函数

#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1))// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{uint64 n, va0, pa0;while(len > 0){va0 = PGROUNDDOWN(dstva);            // 获得逻辑页号pa0 = walkaddr(pagetable, va0);       // 逻辑页号转换成物理页号if(pa0 == 0)return -1;n = PGSIZE - (dstva - va0);if(n > len)n = len;// pa0 物理页号 + dstva - va0 页内偏移地址  = 物理地址 memmove((void *)(pa0 + (dstva - va0)), src, n);    len -= n;src += n;dstva = va0 + PGSIZE;}return 0;
}

测试通过截图

page tables

在做实验之前推荐阅读《深入理解linux内核》第一章,隔了很长时间重新写这个实验,之前看的xv6 book和代码全忘光了,但也懒得再看一遍了。
阅读xv6 book,最主要就是搞懂书中几张图的含义

第一张图可以看出虚拟地址为64位,物理地址为56位,页大小为4kb

第二张图可以看出使用了3级页表,虚拟地址中使用39位用于寻址,其中27位用于页的寻址,12用作页偏移量。每一页表占用空间为一页(4KB),总共有512个表项,每一个表项占用8B(64位),其中10字节保留,44字节指向下一物理页,该物理页有可能是页表,有可能是存储真实数据的页,12位存储标志位。U代表用户态是否可以访问,X表示是否能以解释为指令执行,V表示是否有效,即是否发生缺页。

第三张图为内核地址空间与物理内存的映射关系,可以对照这张图阅读memlayout.h vm.c的代码

第四张图为用户地址空间分布,书中的其他内容我全忘了,毕竟是英文书籍。。。
大致阅读kern/vm.c代码,对walk mappages进行简要分析(页表的建立与查询)
要了解函数功能最快的方式就是看其最简单的分支

// Return the address of the PTE in page table pagetable
// that corresponds to virtual address va.  If alloc!=0,
// create any required page-table pages.
//
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
//   39..63 -- must be zero.
//   30..38 -- 9 bits of level-2 index.
//   21..29 -- 9 bits of level-1 index.
//   12..20 -- 9 bits of level-0 index.
//    0..11 -- 12 bits of byte offset within the page.
pte_t *walk(pagetable_t pagetable, uint64 va, int alloc) {if (va >= MAXVA) panic("walk");for (int level = 2; level > 0; level--) {pte_t *pte = &pagetable[PX(level, 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(0, va)];
}
简化版即为
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); // 将表项转换成物理页地址} }return &pagetable[PX(0, va)];       // 返回0级页表的表项指针
}// extract the three 9-bit page table indices from a virtual address.
#define PXMASK          0x1FF // 9 bits         // 页表索引掩码
#define PXSHIFT(level)  (PGSHIFT+(9*(level)))  // 页表索引的位置
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)  // 取出相应页表的索引并将其移动到低位// shift a physical address to the right place for a PTE.
#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
#define PTE2PA(pte) (((pte) >> 10) << 12)   // 首先去除10位标志位,而后右移12位(页起始地址偏移量为0)// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned. Returns 0 on success, -1 if walk() couldn't
// allocate a needed page-table page.
int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa,int perm) {uint64 a, last;pte_t *pte;if (size == 0) panic("mappages: size");a = PGROUNDDOWN(va);last = PGROUNDDOWN(va + size - 1);for (;;) {if ((pte = walk(pagetable, a, 1)) == 0) return -1;if (*pte & PTE_V) panic("mappages: remap");*pte = PA2PTE(pa) | perm | PTE_V;if (a == last) break;a += PGSIZE;pa += PGSIZE;}return 0;
}
简化后即为
int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa,int perm) {uint64 a, last;pte_t *pte;a = PGROUNDDOWN(va);    // 起始页last = PGROUNDDOWN(va + size - 1); // 结束页for (;;) { // 映射是以页的粒度pte = walk(pagetable, a, 1);       // 得到虚拟地址的页表表项指针*pte = PA2PTE(pa) | perm | PTE_V;  // 将该表项设为pa物理地址,建立映射关系,并设置标志位if (a == last) break;a += PGSIZE;pa += PGSIZE;}return 0;
}
#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) // 将低12位置为0,即对页大小取整

正式进行实验,Speed up system calls 和 Print a page table是很久之前写的,不太记得了,如有遗漏的地方,望请告知!

Speed up system calls

When each process is created, map one read-only page at USYSCALL (a VA defined in memlayout.h). At the start of this page, store a struct usyscall (also defined in memlayout.h), and initialize it to store the PID of the current process. For this lab, ugetpid() has been provided on the userspace side and will automatically use the USYSCALL mapping. You will receive full credit for this part of the lab if the ugetpid test case passes when running pgtbltest.

int
ugetpid(void)
{struct usyscall *u = (struct usyscall *)USYSCALL;return u->pid;
}
#define USYSCALL (TRAPFRAME - PGSIZE)// usyscall的实现对标trapframe,创建,注销的方法都与trapframe相似
struct proc {struct trapframe *trapframe;  // data page for trampoline.Sstruct usyscall* usyscall;  // 增加usyscall成员
};
// 比较对称的四个函数allocproc proc_pagetable freeproc proc_freepagetable
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc *allocproc(void) {struct proc *p;for (p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if (p->state == UNUSED) {goto found;} else {release(&p->lock);}}return 0;found:p->pid = allocpid();p->state = USED;// Allocate a trapframe page.if ((p->trapframe = (struct trapframe *)kalloc()) == 0) {freeproc(p);release(&p->lock);return 0;}// Allocate a usyscall page. if ((p->usyscall = (struct usyscall *)kalloc()) == 0) {freeproc(p);release(&p->lock);return 0;}// An empty user page table.p->pagetable = proc_pagetable(p);if (p->pagetable == 0) {freeproc(p);release(&p->lock);return 0;}// Set up new context to start executing at forkret,// which returns to user space.memset(&p->context, 0, sizeof(p->context));p->context.ra = (uint64)forkret;p->context.sp = p->kstack + PGSIZE;// init usyscall pagep->usyscall->pid = p->pid;return p;
}// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t proc_pagetable(struct proc *p) {pagetable_t pagetable;// An empty page table.pagetable = uvmcreate();if (pagetable == 0) return 0;// map the trampoline code (for system call return)// at the highest user virtual address.// only the supervisor uses it, on the way// to/from user space, so not PTE_U.if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline,PTE_R | PTE_X) < 0) {uvmfree(pagetable, 0);return 0;}// map the trapframe just below TRAMPOLINE, for trampoline.S.if (mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe),PTE_R | PTE_W) < 0) {uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmfree(pagetable, 0);return 0;}// map one read-only page at USYSCALLif (mappages(pagetable, USYSCALL, PGSIZE, (uint64)(p->usyscall),PTE_R | PTE_U) < 0) {  // PTE_R | PTE_U 用户可以访问,可以读uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 0); // 取消之前的映射,这次映射未成功,不用取消uvmfree(pagetable, 0);return 0;}return pagetable;
}// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void freeproc(struct proc *p) {if (p->trapframe) kfree((void *)p->trapframe);p->trapframe = 0;kfree((void *)p->usyscall);p->usyscall = 0;if (p->pagetable) proc_freepagetable(p->pagetable, p->sz);p->pagetable = 0;p->sz = 0;p->pid = 0;p->parent = 0;p->name[0] = 0;p->chan = 0;p->killed = 0;p->xstate = 0;p->state = UNUSED;
}// Free a process's page table, and free the
// physical memory it refers to.
void proc_freepagetable(pagetable_t pagetable, uint64 sz) {uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 0);uvmunmap(pagetable, USYSCALL, 1, 0);uvmfree(pagetable, sz);
}

Print a page table

Define a function called vmprint(). It should take a pagetable_t argument, and print that pagetable in the format described below. Insert if(p->pid==1) vmprint(p->pagetable) in exec.c just before the return argc, to print the first process’s page table. You receive full credit for this part of the lab if you pass the pte printout test of make grade.

// defs.h
void vmprint(pagetable_t pagetable);
// vm.c
void vmprint_impl(pagetable_t pagetable, int level)
{// there are 2^9 = 512 PTEs in a page table.for (int i = 0; i < 512; i++){pte_t pte = pagetable[i];pagetable_t next_pagetable = (pagetable_t)PTE2PA(pte);if (pte & PTE_V) // 该页有效{for (int j = 2; j >= level; j--){printf(" ..");}printf("%d: pte %p pa %p\n", i, pte, next_pagetable); // %p pointer 以十六进制整数方式输出指针的值if ((pte & (PTE_R | PTE_W | PTE_X)) == 0) // 表示该表项指示的页存储着页表{vmprint_impl(next_pagetable, level - 1);}}}
}
void vmprint(pagetable_t pagetable)
{printf("page table %p\n", pagetable);vmprint_impl(pagetable, 2);
}
//exec()
// print pid==1 info
if (p->pid == 1) vmprint(p->pagetable);
return argc;  // this ends up in a0, the first argument to main(argc, argv)

其中pte & (PTE_R | PTE_W | PTE_X)) == 0让我挺疑惑的,为什么读写执行标志位全为0下一项就是页表呢。

在lab2: 物理内存和页表中我找到了上表。

重新看walk函数代码

pte_t *walk(pagetable_t pagetable, uint64 va, int alloc)
{if (va >= MAXVA)panic("walk");for (int level = 2; level > 0; level--){pte_t *pte = &pagetable[PX(level, 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(0, va)];
}*pte = PA2PTE(pagetable) | PTE_V;
// 表示该表项只有PTE_V置位,这表示指向页表的表项的R W X标志位确实全为0

Detecting which pages have been accessed

Your job is to implement pgaccess(), a system call that reports which pages have been accessed. The system call takes three arguments. First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit). You will receive full credit for this part of the lab if the pgaccess test case passes when running pgtbltest.

pgaccess系统调用的大部分代码已经写好了,只需要补全sys_pgaccess函数就可以。
系统调用路径为

// pgtbltest.c
if (pgaccess(buf, 32, &abits) < 0) // 调用pgaccess函数// user.h
int pgaccess(void *base, int len, void *mask); // 函数声明// syscall.h
#define SYS_pgaccess  30 // 定义调用号// usys.S
pgaccess:   // 函数入口li a7, SYS_pgaccessecallret// syscall.c
// 定义pgaccess内核函数原型
#ifdef LAB_PGTBL
extern uint64 sys_pgaccess(void);
#endifstatic uint64 (*syscalls[])(void) = {#ifdef LAB_PGTBL[SYS_pgaccess] sys_pgaccess,
#endif
};
void
syscall(void)
{int num;struct proc *p = myproc();num = p->trapframe->a7;if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {p->trapframe->a0 = syscalls[num]();} else {printf("%d %s: unknown sys call %d\n",p->pid, p->name, num);p->trapframe->a0 = -1;}
}// sysproc.c 函数实现
#ifdef LAB_PGTBL
int sys_pgaccess(void){return 0;
}
#endif

sys_pgaccess函数比较简单,分为获取参数,查询PTE_A标志位并写入临时缓冲区,写入用户缓冲区三个部分。
可以参照walkaddr函数使用walk函数,获取调用参数和写入用户地址的方法与上一个实验类似。

查看手册可知PTE_A标志位在第六位

参考代码如下

// riscv.h
#define PTE_A (1L << 6)
// sysproc.c
#ifdef LAB_PGTBL
#define MAX_PGACCESS_PAGE_NUMBER 64     // 最大查询页数
int sys_pgaccess(void)
{// lab pgtbl: your code here.uint64 base;int len;uint64 mask;// 获取参数if (argaddr(0, &base) < 0)return -1;if (argint(1, &len) < 0)return -1;if (argaddr(2, &mask) < 0)return -1;len = len > MAX_PGACCESS_PAGE_NUMBER ? MAX_PGACCESS_PAGE_NUMBER : len;uint64 tmp_buffer = 0; // 临时缓冲区pagetable_t pagetable = myproc()->pagetable;pte_t *pte;for (int i = 0; i < len; i++){pte = walk(pagetable, base + PGSIZE * i, 0);// 如果页表项存在且PTE_A标志位置位if (pte != 0 && (*pte & PTE_A) != 0)  {tmp_buffer |= (1 << i);*pte &= ~PTE_A;   // 清空标志位}}uint64 mask_size = (len - 1) / 8 + 1;  // 写入用户空间的字节数,向上取整if (copyout(pagetable, mask, (char *)&tmp_buffer, mask_size))return -1;return 0;
}
#endif
// defs.h
pte_t *walk(pagetable_t pagetable, uint64 va, int alloc);

编译时报错

kernel/sysproc.c:95:15: error: implicit declaration of function ‘walk’ [-Werror=implicit-function-declaration]95 |         pte = walk(pagetable, base + PGSIZE * i, 0);|               ^~~~
kernel/sysproc.c:95:13: error: assignment to ‘pte_t *’ {aka ‘long unsigned int *’} from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion]95 |         pte = walk(pagetable, base + PGSIZE * i, 0);

排查后发现没有walk函数声明,在defs.h添加walk函数声明
usertests测试超时

Timeout! (300.3s)
== Test   usertests: all tests == usertests: all tests: FAIL ...test bigfile: OKtest dirfile: OKtest iref: OKtest forktest: OKtest bigdir: qemu-system-riscv64: terminating on signal 15 from pid 3652612 (make)MISSING '^ALL TESTS PASSED$'

修改超时时间为1000

@test(0, "usertests")
def test_usertests():r.run_qemu(shell_script(['usertests']), timeout=1000)

测试通过截图

traps

  • 在xv6书中,异常控制流的原因分为系统调用,设备中断,异常(故障),并将其统称为陷阱。
  • xv6在内核中处理所有陷阱,不会移交给用户空间代码
  • 对于陷阱处理,分为三种情况,分别是用户空间陷阱,内核空间陷阱,定时器中断
  • 用户空间陷阱处理流程大致为uservec->usertrap->usertrapret->userret,简要看一下这四个函数,大部分代码是关于陷入前保存现场和内核处理后还原现场,有比较详尽的注释。
  • 内核空间陷阱处理流程大致为kernelvec->kerneltrap->kernelvec
  • trampoline page放置代码,trapframe page放置数据,这两页同时存在内核页表和进程页表中
  • 系统调用:ecall指令陷入内核,执行uservec->usertrap->syscall,补全了之前分析的系统调用路径。
  • 内核将物理地址直接映射成虚拟地址,故可以直接使用物理地址
  • Xv6对异常的响应相当简单:如果在用户空间中发生异常,内核直接杀死该进程。如果在内核发生了异常,则中止内核。
  • 如果将内核空间映射到进程页表,陷入内核空间就不需要切换页表,也可以直接使用用户空间指针,这也是现行操作系统采取的方式,但xv6为简化问题,使用单独的内核页表映射内核空间。参见内核页表学习记录

用户参数与寄存器对应代码

static uint64
argraw(int n)
{struct proc *p = myproc();switch (n) {case 0:return p->trapframe->a0;case 1:return p->trapframe->a1;case 2:return p->trapframe->a2;case 3:return p->trapframe->a3;case 4:return p->trapframe->a4;case 5:return p->trapframe->a5;}panic("argraw");return -1;
}

RISC-V assembly

相关文章与书籍
聊聊 ARM 与 RISC-V
RISC-V手册(中科院翻译版)
只需要简单阅读即可,主要是看手册附录部分的指令列表

在附录A RISC-V 指令列表查询指令详细信息
一些用到的指令如下

int g(int x) {0: 1141                    addi    sp,sp,-162: e422                    sd  s0,8(sp)4:  0800                    addi    s0,sp,16    // 保存寄存器的值return x+3;
}6: 250d                    addiw   a0,a0,3     // a0寄存器既存参数,又存返回值8: 6422                    ld  s0,8(sp)a:  0141                    addi    sp,sp,16    // 还原寄存器的值c:    8082                    ret000000000000000e <f>:int f(int x) {e:  1141                    addi    sp,sp,-1610:    e422                    sd  s0,8(sp)12: 0800                    addi    s0,sp,16return g(x);
}14:    250d                    addiw   a0,a0,3     // 编译器优化,进行了函数展开,即为x+316:    6422                    ld  s0,8(sp)18: 0141                    addi    sp,sp,161a: 8082                    ret000000000000001c <main>:void main(void) {1c:   1141                    addi    sp,sp,-161e:    e406                    sd  ra,8(sp)20: e022                    sd  s0,0(sp)22: 0800                    addi    s0,sp,16printf("%d %d\n", f(8)+1, 13);24:    4635                    li  a2,1326:    45b1                    li  a1,12       // 编译期间提前进行计算,8+3+128: 00000517            auipc   a0,0x0   2c:    7c050513            addi    a0,a0,1984 # 7e8 <malloc+0xea>   // 应该存储的是字符串"%d %d\n"的起始地址30: 00000097            auipc   ra,0x034:   610080e7            jalr    1552(ra) # 640 <printf>exit(0);38:    4501                    li  a0,03a: 00000097            auipc   ra,0x03e:   27e080e7            jalr    638(ra) # 2b8 <exit>

Q1:
可以看出a0存放第一个参数,a1存放第二个参数,a2存放第三个参数,与argraw函数对应关系类似

Q2:
都进行了函数展开,在addiw a0,a0,3和li a1,12语句位置调用函数

Q3:
printf函数位于0x640的位置

Q4:
ra寄存器存放返回地址,即为0x38

后面的问题懒得看

Backtrace

Implement a backtrace() function in kernel/printf.c. Insert a call to this function in sys_sleep, and then run bttest, which calls sys_sleep. Your output should be as follows:
backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898
After bttest exit qemu. In your terminal: the addresses may be slightly different but if you run addr2line -e kernel/kernel (or riscv64-unknown-elf-addr2line -e kernel/kernel) and cut-and-paste the above addresses as follows:
$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D
You should see something like this:
kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85

这部分实验主要理清整数,地址,指针等类似概念,最好画一张示意图,便于理解。
参考代码如下

// defs.h
void backtrace(void);
// riscv.h
static inline uint64
r_fp()
{uint64 x;asm volatile("mv %0, s0" : "=r" (x) );return x;
}// printf.c
void backtrace(void)
{printf("backtrace:\n");uint64 *current_frame_pointer = (uint64 *)r_fp();                         // 当前栈帧指针uint64 *top_pointer = (uint64 *)PGROUNDUP((uint64)current_frame_pointer); // 页尾部uint64 return_address;for (uint64 *fp = current_frame_pointer; fp < top_pointer; fp = (uint64 *)(*(fp - 2))){return_address = *(fp - 1); // 计算函数返回地址printf("%p\n", return_address);}
}在sys_sleep函数插入backtrace
在panic函数插入backtrace(可选)

-1 -2是因为类型为uint64指针,一次加减8,相对于整数运算的-8 -16。
可以直接使用整数变量而不用指针变量,只在取值时强制转换为指针,但我觉得那样不好看。

Alarm

In this exercise you’ll add a feature to xv6 that periodically alerts a process as it uses CPU time. This might be useful for compute-bound processes that want to limit how much CPU time they chew up, or for processes that want to compute but also want to take some periodic action. More generally, you’ll be implementing a primitive form of user-level interrupt/fault handlers; you could use something similar to handle page faults in the application, for example. Your solution is correct if it passes alarmtest and usertests.

在这个部分我最主要错误的点是想在内核中执行用户空间的函数,而没有注意到epc(之前没认真看xv6 book),但让我奇怪的一点是handler作为一个函数指针竟然有时候是0(periodic()函数),奇怪,这个函数正好放在在0的位置吗?

没注意原来实验特地说明了这一点。。

Note that the address of the user’s alarm function might be 0 (e.g., in user/alarmtest.asm, periodic is at address 0).

// alarmtest.asm
0000000000000000 <periodic>:
}volatile static int count;void periodic()

依据系统调用路径编写sigalarm sigreturn系统调用
只展示相关代码

// 用户空间调用
void periodic()
{count = count + 1;printf("alarm!\n");sigreturn();      // 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);  // sigalarm系统调用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");}
}// user.h 系统调用函数声明
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);// usys.pl 函数入口点
entry("sigalarm");
entry("sigreturn");// syscall.h 系统调用号
#define SYS_sigalarm  22
#define SYS_sigreturn  23// usys.S 最终生成的汇编代码
.global sigalarm
sigalarm:li a7, SYS_sigalarmecallret
.global sigreturn
sigreturn:li a7, SYS_sigreturnecallret// syscall.c 内核的系统调用处理函数
extern uint64 sys_sigalarm(void);
extern uint64 sys_return(void);
static uint64 (*syscalls[])(void) = {[SYS_sigalarm]   sys_sigalarm,
[SYS_sigreturn ]   sys_return,
};void
syscall(void)
{int num;struct proc *p = myproc();num = p->trapframe->a7;if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {p->trapframe->a0 = syscalls[num]();} else {printf("%d %s: unknown sys call %d\n",p->pid, p->name, num);p->trapframe->a0 = -1;}
}// sysproc.c 系统调用对应的处理函数
uint64 sys_sigalarm(void)
{return 0;
}
uint64 sys_return(void)
{return 0;
}// Makefile 加入alarmtest对象
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/_alarmtest\

首先获取ticks和handler参数是必要的,test0只需要将在特定时机将handler赋值给epc即可,test1需要保存执行handler前的进程现场,handler结束后还原现场,test2需保证在执行handler时不会再次触发alarm。
其大致实现如下

// proc.h
struct proc
{int alarm_call_exist;                   // 是否存在alarm调用int allow_trigger_alarm;                // 是否允许触发alarm调用,如果现在正在处理handler函数,不能再次触发int time_interval;                      // alarm间隔uint64 handler_fn;                      // alarm调用函数int elapsed_time;                       // 自上次调用起经历时间struct trapframe alarm_saved_trapframe; // alarm调用前的trapframe
};// proc.c
static struct proc *allocproc(void)
{found:p->alarm_call_exist = 0; // 设置成不需要触发alarmp->allow_trigger_alarm = 0;return p;
}// sysproc.c
uint64 sys_sigalarm(void)
{struct proc *p = myproc();int ticks;uint64 handler;if (argint(0, &ticks) < 0)  // 获取参数return -1;if (argaddr(1, &handler) < 0)return -1;if (ticks == 0)    // 取消alarm调用{p->alarm_call_exist = 0;return 0;}printf("tick %d handler %p\n", ticks, handler);p->alarm_call_exist = 1; // 将两个标志变量均设置为有效p->allow_trigger_alarm = 1;p->time_interval = ticks;p->handler_fn = handler;p->elapsed_time = 0; // 开启计时器return 0;
}
uint64 sys_return(void)
{struct proc *p = myproc();if (p->alarm_call_exist == 1) // 如果存在alarm调用{*p->trapframe = p->alarm_saved_trapframe; // 还原现场p->allow_trigger_alarm = 1;               // 允许触发alarm调用}return 0;
}// trap.c
void usertrap(void)
{// give up the CPU if this is a timer interrupt.if (which_dev == 2){if (p->alarm_call_exist == 1 && p->allow_trigger_alarm == 1){p->elapsed_time++;if (p->elapsed_time > p->time_interval) // 大于规定间隔时间{p->alarm_saved_trapframe = *p->trapframe; // 保存此时进程状态p->trapframe->epc = p->handler_fn;        // 返回地址设置为handler funtionp->allow_trigger_alarm = 0;               // handler function返回前不再触发p->elapsed_time = 0;                      // 重置计时器}}yield();}
}

注意在代码中alarm调用指的是时钟中断时触发执行handler function的过程,而不是sigalarm系统调用,sigalarm系统调用更像是一个开启alarm触发机制的函数。当然这只是为了便于表述,实际上alarm调用只是时间中断的一个机制而已,并不是系统调用。

在之前我使用struct trapframe指针的形式存储alarm_saved_trapframe成员,并在allocproc函数中申请空间
类似于

if ((p->alarm_saved_trapframe= (struct trapframe *)kalloc()) == 0){freeproc(p);release(&p->lock);return 0;}

但忘了在freeproc中释放空间,导致usertests没有通过,后以结构体存储,通过了全部测试,当然使用指针存储并在freeproc中释放应该也可以,但kalloc申请一页的空间,还是有些浪费的。

测试通过截图

参考博客:MIT 6.S081 Lab4: traps

Copy-on-Write

MIT6.S081 2021 Copy-on-Write Fork for xv6

Multithreading

MIT6.S081 Multithreading

networking

MIT6.S081 2021 networking

locks

MIT6.S081 2021 locks

file system

MIT6.S081 2021 file system

mmap

MIT6.S081 2021 mmap

MIT6.S081 2021相关推荐

  1. MIT6.S081 2021 Copy-on-Write Fork for xv6

    MIT6.S081 2021 Copy-on-Write Fork for xv6 简要介绍 debug 代码参考 简要介绍 There is a saying in computer systems ...

  2. Mit6.S081学习记录

    Mit6.S081学习记录 前言 一.课程简述 二.课程资源 1,课程主页 2,参考书 3,实验环境 三.学习过程 Mit6.S081-实验环境搭建 Mit6.S081-GDB使用 Mit6.S081 ...

  3. 操作系统MIT6.S081:P7->Interrupts

    本系列文章为MIT6.S081的学习笔记,包含了参考手册.课程.实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6. ...

  4. 操作系统MIT6.S081:[xv6参考手册第4章]->Trap与系统调用

    本系列文章为MIT6.S081的学习笔记,包含了参考手册.课程.实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6. ...

  5. 操作系统MIT6.S081:Lab4->Trap

    本系列文章为MIT6.S081的学习笔记,包含了参考手册.课程.实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6. ...

  6. MIT6.S081 Multithreading

    MIT6.S081 Multithreading xv6 book记录 Uthread Using threads Barrier xv6 book记录 阅读xv6 book之前,简要看一下<深 ...

  7. MIT6.S081简单总结

    在复盘MIT6.828的时候,偶然看到了一个将MIT6.S081的视频翻译成文字的gitbook:https://mit-public-courses-cn-translatio.gitbook.io ...

  8. MIT6.S081操作系统实验——操作系统是如何在qemu虚拟机中启动的?

    前言 为了更好的理解基于RISC-V体系的Xv6操作系统是如何在qemu中启动的,我将详细地梳理从执行make qemu命令开始到Xv6的shell启动为止的具体流程. 执行make qemu后发生了 ...

  9. 【MIT6.S081/6.828】手把手教你搭建开发环境

    文章目录 1. 简介 2. 安装ubuntu20.04 3. 更换源 3.1 更换/etc/apt/sources.list文件里的源 3.2 备份源列表 3.3 打开sources.list文件修改 ...

最新文章

  1. 分享:给入职新人的IDEA培训教程。
  2. 当前主要使用的python版本_如何获取当前使用的Python版本信息?(代码示例)
  3. Android Color 判断色值小结
  4. CodeForces - 831D Office Keys
  5. python教程:getattr函数和hasattr函数的用法
  6. Uniapp 导入 Vant 组件库
  7. 怎样使计算机屏幕看着不累,电脑族的显示器如何摆最不累
  8. Rad Controls_Q2_2006 注册机
  9. 串口和TCP测试工具
  10. Java随机产生中文昵称
  11. 怎么进入本地组策略编辑器
  12. 【微信小程序】echarts 正态分布图 区间柱形图
  13. 6v电源控制12v继电器
  14. FPS显示和修改——unity3D
  15. Auto.js Pro安卓免ROOT引流脚本开发系列教程26网易公开课(4)-关注用户
  16. 标准C++为什么没有垃圾回收(Garbage Collection)
  17. linux cp改文件名,更改Linux `cp`命令以复制文件组
  18. Android 相机开发
  19. 【b站雅思笔记】Charlie有好好学习 - 雅思听力
  20. HG30-3交直流校准源

热门文章

  1. NFT周刊|Jay-Z拍卖“Reasonable Doubt”NFT;漫威推出NFT藏品
  2. PM online course from Youtube.com
  3. el-upload编辑时回显图片
  4. 从零开始学python项目_从零开始学Python程序设计
  5. 关于人工智能的思考--以copilot、openAI为例
  6. LabVIEW如何开发大型程序
  7. 稳捷教育行业解决方案
  8. 2008年度中国最佳MBA排行榜
  9. 试题 基础练习 Fibonacci数列
  10. python中小学生编程学习-推荐几个适合小白学习Python的免费网站