Reference: https://pdos.csail.mit.edu/6.1810/2022/index.html
6.1810 是MIT的著名的操作系统课程6.828的后续版本,主要区别是6.828基于x86指令集,6.1810基于risc-v指令集。之前做6.828的时候,因为太菜了,实在是做不下去🥺,一度弃坑。趁着毕业之前还有很多时间,再来一遍,争取这次能够做完这套课程。
实验环境设置
我平常的开发设备是MacBook M1,按照Tools中的macOS的工具安装方法,安装了risc-v的cross compile编译工具之后,发现并不能编译成功。多次尝试未果之后,果断放弃,选择拥抱远端环境。这次就没有像上次一样用阿里云的服务器了,这次用的是GitHub提供的Codespaces。这套环境,可以很方便在不同设备上进行使用,可以选择用本地的vscode,也可以选择用浏览器版本的vscode,还可以用ssh连接端口使用vim,超级方便🤣。唯一的不好的地方就是网络差的时候用起来有点卡。
按照Tools中提供的步骤,在Codespaces中安装必要的环境和工具。
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
Lab: Xv6 and Unix utilities
这是第一个Lab,主要是熟悉下环境,然后编写几个命令行小工具。
sleep(easy)
在xv6中实现UNIX当中的sleep函数。
user/sleep.c
的代码实现如下
/*
filename: user/sleep.c
*/
#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char *argv[]){
if(argc != 2){
exit(0);
}
// atoi 是将字符串转换成数字,定义在user/user.h中
int n = atoi(argv[1]);
sleep(n);
exit(0);
}
实现完成之后,需要在Makefile
文件中添加上这个文件生成的执行文件。
UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_sleep\ # 新增此行
然后在命令行中执行./grade-lab-util sleep
来检查是否正确。
参考的commit: https://github.com/dianhsu/xv6-riscv/commit/eee6ab6d5a3c7df8f9f0fff1ac036ae38105ab3a
pingpong(easy)
在xv6中实现UNIX当中的pingpong函数。
#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char* argv[]){
char buf[16];
int p_p2c[2], p_c2p[2];
if(pipe(p_p2c) < 0){
exit(1);
}
if(pipe(p_c2p) < 0){
exit(1);
}
if(fork() > 0){
close(p_p2c[0]);
close(p_c2p[1]);
write(p_p2c[1], "p", 1);
read(p_c2p[0], buf, 1);
fprintf(2, "%d: received pong\n", getpid());
close(p_p2c[1]);
close(p_c2p[0]);
wait(0);
}else{
close(p_p2c[1]);
close(p_c2p[0]);
read(p_p2c[0], buf, 1);
fprintf(2, "%d: received ping\n", getpid());
write(p_c2p[1], "p", 1);
close(p_p2c[0]);
close(p_c2p[1]);
exit(0);
}
exit(0);
}
实现完成之后,需要在Makefile
文件中添加上这个文件生成的执行文件。
UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_sleep\
$U/_pingpong\ # 新增此行
primes(moderate)
#include "kernel/types.h"
#include "user/user.h"
int isPrime(int v){
for(int i = 2; i * i <= v; ++i){
if(v % i == 0){
return 0;
}
}
return 1;
}
int main(){
int p[2];
int c[2];
if(pipe(p) < 0){
exit(1);
}
if(pipe(c) < 0){
exit(1);
}
if(fork() > 0){
close(p[0]);
close(c[1]);
char buf[8];
for(int i = 2; i <= 35; ++i){
buf[0] = i;
write(p[1], buf, 1);
}
close(p[1]);
while(read(c[0], buf, 1) != 0){
fprintf(2, "prime %d\n", (int)buf[0]);
}
close(c[0]);
wait(0);
}else{
close(p[1]);
close(c[0]);
char buf[8];
while(read(p[0], buf, 1) != 0){
int v = buf[0];
if(isPrime(v)){
if(fork() == 0){
write(c[1], buf, 1);
close(c[1]);
exit(0);
}
wait(0);
}
if(v == 35) break;
}
close(p[0]);
close(c[1]);
exit(0);
}
exit(0);
}
find(moderate)
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fs.h"
#include "user/user.h"
void checkAndShow(char* path, char* name){
static char buf[DIRSIZ + 1];
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--);
p++;
// Return blank-padded name.
if(strlen(p) >= DIRSIZ) return;
memmove(buf, p, strlen(p));
memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
for(int i = 0; ; ++i){
if(p[i] == '\0' && name[i] == '\0'){
printf("%s\n", path);
break;
}else if(p[i] == '\0' || name[i] == '\0'){
break;
}else if(p[i] != name[i]){
break;
}
}
}
void find(char* program, char *path, char* name){
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, 0)) < 0){
fprintf(2, "%s: cannot open %s\n", program, path);
return;
}
if(fstat(fd, &st) < 0){
fprintf(2, "%s: cannot stat %s\n", program, path);
close(fd);
return;
}
switch(st.type){
case T_FILE:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("%s: path too long\n", program);
break;
}
checkAndShow(path, name);
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("%s: path too long\n", program);
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("%s: cannot stat %s\n", program, buf);
continue;
}
if(st.type == T_DIR && (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))){
continue;
}else{
find(program, buf, name);
}
}
break;
}
close(fd);
}
int main(int argc, char* argv[]){
if(argc == 2){
find(argv[0], ".", argv[1]);
exit(0);
}
if(argc != 3){
exit(0);
}
char *dir = argv[1];
char *name = argv[2];
find(argv[0], dir, name);
exit(0);
}
xargs(moderate)
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define MAXLEN 1024
char *argvs[MAXARG + 1];
char buf[512];
char argBuf[MAXLEN];
int main(int argc, char* argv[]){
if(argc <= 1){
exit(0);
}
int idx = 0;
argvs[idx] = 0;
while(idx + 1 < argc){
argvs[idx] = argv[idx + 1];
++idx;
argvs[idx] = 0;
}
int cnt = 0;
int pos = 0;
while((cnt = read(0, buf, sizeof(buf))) > 0){
if(pos + cnt >= MAXLEN) exit(0);
memmove(argBuf + pos, buf, cnt);
pos += cnt;
}
for(int i = 0; i < pos; ++i){
if(i == 0 || argBuf[i - 1] == '\0'){
if(idx >= MAXARG) exit(0);
argvs[idx++] = argBuf + i;
}
if(argBuf[i] == '\n') argBuf[i] = '\0';
}
int pid;
if((pid = fork()) > 0){
wait(0);
}else{
exec(argvs[0], argvs);
exit(0);
}
exit(0);
}
Lab: system calls
System call tracing (moderate)
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.
In this task, we are going to design a trace program in order to trace the system calls. The trace program will take one argument, an integer “mask”, whose bits specify which system calls to trace. The other arguments will be the program to be traced and the arguments of traced program. For example, in the first case(trace 32 grep hello README
), the program will trace the read
system call, due to the 1 << SYS_read
in kernel/syscall.h
is 32. The grep
is the running program, hello
and README
will pass to grep
as arguments.
Step1. Add a system call named trace
In user/user.h
, we add a system call named trace
:
// other system calls
int trace(int);
// other system calls
and we modify user/usys.pl
to add the system call:
In kernel/syscall.h
, we define a new system call named trace
:
In kernel/proc.h
, we add a mask flag in struct proc
:
struct proc{
// other fields
int mask;
}
In kernel/sysproc.c
, we add the implementation of trace
, we use argint
to get the mask and set it to the current process:
uint64
sys_trace(void)
{
int mask;
if(argint(0, &mask) < 0){
return -1;
}
myproc()->mask = mask;
return 0;
}
In kernel/proc.c
, we modify the fork
function, we copy the mask from the parent process to the child process:
int
fork(void){
// other code
np->sz = p->sz;
np->mask = p->mask;
// other code
}
Finally, we print system calls usage in kernel/syscall.c
, and we should create a system call name array in kernel/syscall.c
:
static uint64 (*syscalls[])(void) = {
// other system calls
[SYS_trace] sys_trace,
// other system calls
};
extern int sys_trace(void);
static char* syscall_name[] = {
"",
"fork",
"exit",
"wait",
"pipe",
"read",
"kill",
"exec",
"fstat",
"chdir",
"dup",
"getpid",
"sbrk",
"sleep",
"uptime",
"open",
"write",
"mknod",
"unlink",
"link",
"mkdir",
"close",
"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]();
if((p->mask >> num) & 1){
// if the system call is traced, print the system call name and return value.
printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
Commit: https://github.com/dianhsu/xv6-riscv/commit/25048b45071e4331a7374c75b9ef076877c56d91
FAQs
In Linux environment, make report error: -Winfinite-recursion
?
solve: add -Wno-infinite-recursion
to Makefile
.
CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -Wno-infinite-recursion
References