MIT 6.S081 OS Lab1 utilities & Lab 2 Syscalls

实验环境

Vmware Station + Ubuntu 20.04

参考链接

Fan Xiao的博客
Tools used in 6.S081
Complie your tool chain

工具准备

参考Tools Used in 6.S081

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/MakefileUPROGS处追加自己编写的用户程序。

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

Xv6系统调用

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中交换寄存器a0sscratch的值,这样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

uservecusertrap,再到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:

  1. Add $U/_trace to UPROGS in Makefile
    在 Makefile 中将 $U/_trace 添加到 UPROGS 中。
  2. 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
  3. 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 中看到它们使用的示例。
  4. Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.
    修改 fork()(参见 kernel/proc.c)以将跟踪掩码从父进程复制到子进程。
  5. 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值