Lab system calls

6s081 Lab2: system calls

一开始看的时候看见这两个实验都是moderate,还以为挺简单。结果因为对xv6 book不熟悉,没有弄明白整个系统调用的过程,花了很多的时间去理解xv6中系统调用的过程。含泪总结…………一定要认认真真的看xv6 book 不然实验可能真的不会写。

关于实验要求,比较重要的一些点我都加了下划线。(也是我踩过的坑…………)

这个实验看着简单,但是写10行代码差不多要看100行左右的源码。

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.

trace(1 << SYS_fork)表示 trace 命令后面的第一个参数是一个数字,这个数字怎么来的呢。通过将1左移系统调用号的位数。比如说例子中的32是将1左移五位,所以系统调用号就是5,对应的就是read,所以就只追踪read系统调用。而这个mask也是这样用的。第n位为1,就表示追踪第n个系统调用。

In the first example above, trace invokes grep tracing just the read system call. The 32 is 1<<SYS_read. In the second example, trace runs grep while tracing all system calls; the 2147583647 has all 31 low bits set. In the third example, the program isn’t traced, so no trace output is printed. In the fourth example, the fork system calls of all the descendants of the forkforkfork test in usertests are being traced. Your solution is correct if your program behaves as shown above (though the process IDs may be different).

Some hints:

  • Add $U/_trace to UPROGS in Makefile
  • Run make qemu 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 trace 32 grep hello README; it will fail because you haven’t implemented the system call in the kernel yet.
  • 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.
  • Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.
  • 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.

Sysinfo (moderate)

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”.

Some hints:

  • Add $U/_sysinfotest to UPROGS in Makefile

  • Run make qemu; user/sysinfotest.c will fail to compile. Add the system call sysinfo, following the same steps as in the previous assignment. To declare the prototype for sysinfo() in user/user.h you need predeclare the existence of struct sysinfo:

        struct sysinfo;
        int sysinfo(struct sysinfo *);
      
    

    Once you fix the compilation issues, run

    sysinfotest

    ; it will fail because you haven’t implemented the system call in the kernel yet.

  • sysinfo needs to copy a struct sysinfo back to user space; see sys_fstat() (kernel/sysfile.c) and filestat() (kernel/file.c) for examples of how to do that using copyout().

  • To collect the amount of free memory, add a function to kernel/kalloc.c

  • To collect the number of processes, add a function to kernel/proc.c


理论基础

在xv6中的内核代码都放在kernal文件夹下。内核文件中定义的所有函数都应该在 defs.h中进行声明,这个defs.h相当于一个总接口。Xv6对每个进程都有其独立的进程空间。

要完成这个实验要重点理解进程空间中的 trampoline 和 trapframe两项。

每一个进程在运行时有不同的状态。The xv6 kernel maintains many pieces of state for each process, which it gathers into a struct proc
(kernel/proc.h:86).

Xv6中用一个结构体来表示一个进程的运行空间。

Each process has two stacks: a user stack and a kernel stack (p->kstack). When the process is executing user instructions, only its user stack is in use, and its kernel stack is empty. When the process enters the kernel (for a system call or interrupt), the kernel code executes on the process’s kernel stack; while a process is in the kernel, its user stack still contains saved data, but isn’t actively used. A process’s thread alternates between actively using its user stack and its kernel stack. The kernel stack is separate (and protected from user code) so that the kernel can execute even if a
process has wrecked its user stack.

进程通过使用ecall指令来使用系统调用。

在RISC-V开机启动时。依次执行以下过程:(与实验无关)

it initializes itself and runs a boot loader which is stored in read-only memory.

The boot loader loads the xv6 kernel into memory.

Then, in machine mode, the CPU executes xv6 starting at _entry (kernel/entry.S:6). The RISC-V starts with paging hardware disabled: virtual addresses map directly to physical addresses.

The instructions at _entry set up a stack so that xv6 can run C code.

Xv6 declares space for an initial stack, stack0, in the file start.c (kernel/start.c:11).

The code at _entry loads the stack pointer register sp with the address stack0+4096, the top of the stack.

The function start performs some configuration that is only allowed in machine mode, and then switches to supervisor mode.

start isn’t returning from such a call, and instead sets things up as if there had been one: it sets the previous privilege mode to supervisor in the register mstatus, it sets the return address to main by writing main’s address into the register mepc, disables virtual address translation in supervisor mode by writing 0 into the page-table register satp, and delegates all interrupts and exceptions to supervisor mode.

Before jumping into supervisor mode, start performs one more task: it programs the clock chip to generate timer interrupts.

With this housekeeping out of the way, start “returns” to supervisor mode by calling mret. This causes the program counter to change to main (kernel/main.c:11).

start 初始化结束后,开始让main函数依次执行如下过程:

creates the first process by calling userinit (kernel/proc.c:212). The first process executes a small program written in RISC-V assembly, initcode.S (user/initcode.S:1), which reenters the kernel by invoking the exec system call.

用户代码再要进行系统调用时,将exec的参数放在 a0 和 a1 两个寄存器中,将系统调用号放在a7中。系统调用号会匹配对应的条目在系统调用表中。(kernel/syscall.c:133)

syscall (kernel/syscall.c:133)这个函数 retrieves the system call number from the saved a7 in the trapframe and uses it to index into syscalls. For the first system call, a7 contains SYS_exec (ker- nel/syscall.h:8), resulting in a call to the system call implementation function sys_exec.

myproc()函数用来获取当前所执行的进程。

这个trapframe是用来获取寄存器中对应的值。num就是要获取的系统调动编号。

The ecall instruction traps into the kernel and executes uservec, usertrap, and then syscall, as we saw above.前面两个这个实验暂时不用关注,重点明白ecall指令会调用**syscall()**函数.也就是说,每发生一次系统调用,就会调用一次syscall()函数。而syscall()函数会根据ecall传到a7寄存器中的系统调用编号来确定具体使用哪一个系统调用。当系统调用返回后,syscall()函数会将返回值放到a0寄存器中。这个syscall()就相当于是一个系统调用处理例程。

系统调用参数的传递

The functions argint, argaddr, and argfd retrieve the n ’th system call argument from the trap frame as an integer, pointer, or a file descriptor.They all call argraw to retrieve the appropriate saved user register (kernel/syscall.c:35).

这三个函数:分别对应int,指针,和文件描述符。每一个函数都调用了 argraw函数。argraw函数用来从对应的寄存器中取出用户态传过来的参数。

对于传递指针这个比较特殊。因为这里会造成两个问题

These pointers pose two challenges. First, the user program may be buggy or malicious, and may pass the kernel an invalid pointer or a pointer intended to trick the kernel into accessing kernel memory instead of user memory. Second, the xv6 kernel page table mappings are not the same as the user page table mappings, so the kernel cannot use ordinary instructions to load or store from user-supplied addresses.

在这里插入图片描述

而为了解决这两种问题,系统特地的定义了一些特殊的函数。

fetchstr is an example (kernel/syscall.c:25). File system calls such as exec use fetchstr to retrieve string file-name arguments from user space. fetchstr calls copyinstr to do the hard work.
copyinstr (kernel/vm.c:406) copies up to max bytes to dst from virtual address srcva in the user page table pagetable. It uses walkaddr (which calls walk) to walk the page table in software to determine the physical address pa0 for srcva. Since the kernel maps all physical RAM addresses to the same kernel virtual address, copyinstr can directly copy string bytes from pa0 to dst. walkaddr (kernel/vm.c:95) checks that the user-supplied virtual address is part of the process’s user address space, so programs cannot trick the kernel into reading other memory. A similar function, copyout, copies data from the kernel to a user-supplied address.

这里有个copyout函数可以从内核态中传递数据到用户态中的地址。第二个实验会用到。


具体实验实现

先发一张我的学习成果:

在这里插入图片描述

第一个实验实验要求中都写的很明白了。首先是用户态能够调用的系统调用接口 user-space stubs.(这个stub实在想不到应该怎么对应)

user/user.h

kernal/syscall.c

在这里插入图片描述

在这里插入图片描述

这个syscall函数表示每执行一次就打印相应的系统调用信息。而这个syscall_names数组是用来对系统调用号对应的系统调用名进行打印的。

usys.pl

建立用户空间的对应后,接下来对系统调用进行实现。(上面已经贴了syscall()函数中的实现了)。

这个函数就是根据寄存器中传过来的参数设置mask掩码。而mask数组已经写到了proc数据结构中。这样每次调用都会进行追踪。

fork函数也要改一下。因为每次调用子进程都需要将父进程中的掩码复制过去才行。(加最后一行代码)

绝大多数的代码使用都能在就近的代码中找到例子。

第二个实验主要是打印当前系统的信息。

fork函数也要改一下。因为每次调用子进程都需要将父进程中的掩码复制过去才行。(加最后一行代码)

绝大多数的代码使用都能在就近的代码中找到例子。

第二个实验主要是打印当前系统的信息。

和第一个实验思路几乎一致,除了这里需要有一个返回的copyout函数的调用。然后在kernel/kalloc.c和kernel/proc.c中加两个计数的函数。注意这里在加函数的时候一定要记得到defs.h中去声明一下。不然无法调用。具体代码网上到处都是。这里就不贴了。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值