Skip to content

snu-csl/os-pa2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

4190.307 Operating Systems (Spring 2024)

Project #2: System Calls

Due: 11:59 PM, April 7 (Sunday)

Introduction

System calls are the interfaces that allow user applications to request various services from the operating system kernel. This project aims to understand how those system calls are implemented in xv6.

Background

RISC-V Trap Architecture

In RISC-V, a trap is a general term that encompasses both exceptions and interrupts (see Chap. 4 of the xv6 book). Exceptions are typically generated by the CPU itself due to events such as illegal instructions, memory access faults, system call invocations using the ecall instruction, etc. Interrupts, on the other hand, are external signals from devices or timers indicating that they require attention.

RISC-V supports multiple privilege levels (machine, supervisor, and user modes), each with its own set of capabilities and restrictions. Traps can be configured to be handled at different privilege levels depending on their type and the processor's current operating mode. When a trap occurs, the RISC-V hart automatically fills a register (mcause or scause depending on the privilege level) with a value that indicates the reason for the trap, as shown in the following table.

Interrupt Exception code Description
0 0 Instruction address misaligned
0 1 Instruction access fault
0 2 Illegal instruction
0 3 Breakpoint
0 4 Load address misaligned
0 5 Load access fault
0 6 Store/AMO address misaligned
0 7 Store/AMO access fault
0 8 Environment call from U-mode
0 9 Environment call from S-mode
0 10 Reserved
0 11 Environment call from M-mode (mcause only)
0 12 Instruction page fault
0 13 Load page fault
0 14 Reserved
0 15 Store/AMO page fault
0 >= 16 Reserved
1 0 Reserved
1 1 Supervisor software interrupt
1 2 Reserved
1 3 Machine software interrupt (mcause only)
1 4 Reserved
1 5 Supervisor timer interrupt
1 6 Reserved
1 7 Machine timer interrupt (mcause only)
1 8 Reserved
1 9 Supervisor external interrupt
1 10 Reserved
1 11 Machine external interrupt (mcause only)
1 >= 12 Reserved

Besides the mcause (or scause) register, various additional registers are used to handle traps; when a trap is taken into M-mode (or S-mode), mepc (or sepc) register is written with the virtual address of the instruction that was interrupted or that encountered the exception. The mtvec (or stvec) register holds the start address of the trap handler in M-mode (or S-mode). Also, the mstatus (or sstatus) register keeps track of important information such as M-mode or S-mode interrupt-enable bits (MIE or SIE bit), the value of the interrupt-enable bit active prior to the trap (MPIE or SPIE bit), and the previous privilege mode (MPP or SPP bits).

Handling timer interrupts in xv6

RISC-V requires that the timer interrupts be taken in machine mode (M-mode), not supervisor mode. The following shows a step-by-step overview of the actions taken by the RISC-V hart when a timer interrupt is raised. Note that all these steps are performed as a single operation.

  • Store the mstatus.MIE bit to the mstatus.MPIE bit.
  • Disable interrupts by setting the mstatus.MIE bit to 0.
  • Store the current privilege mode (U-mode or S-mode) into mstatus.MPP. (The timer interrupt can occur while the hart is in U-mode or S-mode.)
  • Copy the current pc into mepc.
  • Set the mcause register to reflect the trap's cause. For the timer interrupt, it is set to the value, ((1 << 63) | 7).
  • Raise the current privilege level to M-mode.
  • Copy the mtvec register to pc.

The trap handler for the timer interrupt is set to timervec() in timerinit() @ kernel/start.c. After programming the next timer interrupt event, the trap handler in M-mode triggers a software interrupt directed toward the supervisor mode (S-mode) to inform the xv6 kernel about the occurrence of the timer interrupt event. This software interrupt is another type of trap, and the RISC-V hart jumps into the S-mode trap handler - either uservec() or kernelvec(), depending on whether it was executing in user mode or kernel mode. Note that xv6 delegates the handling of all the other interrupts and exceptions to S-mode except for the timer interrupt (see start() @ kernel/start.c).

Handling system calls in xv6

Each system call in xv6 is assigned a unique number as you can see in kernel/syscall.h. This number is used to identify which system call is being requested by a user program. When a user program makes a system call, it places the system call number in a designated register a7 and the arguments for the system call in other registers from a0 to a6.

The user program then executes a special ecall instruction, which triggers a trap from U-mode to S-mode, transferring the control to the trap handler uservec() @ kernel/trampoline.S. After saving user register contexts and switching into the kernel address space, the RISC-V hart jumps into usertrap() @ kernel/trap.c. Note that both system calls and interrupts originating from U-mode are directed to usertrap(). If the sret (return-from-S-mode) instruction is executed at the end of the system call handler, the control goes back to U-mode. Please note that to return to the instruction following the ecall instruction, the value of sepc should be explicitly increased by 4 before executing the sret instruction.

The same mechanism can be used for the kernel running in S-mode to request services from M-mode. That is, executing the ecall instruction in S-mode by the kernel results in control being transferred to the M-mode trap handler. If the hart executes mret (return-from-M-mode) instruction in M-mode, the control will be returned to the location pointed to by the mepc register.

Problem specification

1. Implement the kbdints() system call (30 points)

First, you need to implement the kbdints() system call. The system call number of kbdints() is already assigned to 22 in the ./kernel/syscall.h file.

SYNOPSYS

    int kbdints();

DESCRIPTION

The kbdints() system call returns the total number of interrupts from the console input device (i.e., keyboard) that have occurred since the system was booted.

RETURN VALUE

  • kbdints() returns the cumulative count of interrupts from the keyboard. The count is initialized to zero upon kernel startup and is expected to increase monotonically afterward.

2. Implement the time() system call (70 points)

The qemu's timer-related hardware is based on the SiFive's CLINT (Core Local Interruptor) specification. It provides two kinds of memory-mapped machine-mode registers named mtime and mtimecmp. The mtime register has a 64-bit precision, and it is incremented at constant frequency. The current qemu-system-riscv64 system emulates an operating frequency of 10MHz, equating 1,000,000 cycles to a duration of 100ms. (cf. timerinit() @ kernel/start.c). In addition, each RISC-V hart has its own 64-bit timer compare register, mtimecmp. A machine timer interrupt becomes pending whenever mtime contains a value greater than or equal to mtimecmp.

The current time of the system can be obtained by reading the mtime register. RISC-V provides a special pseudo-instruction rdtime that can be used to read the mtime register. The following RISC-V assembly code shows how to read and store the current time to the a0 register.

# reading the mtime register
    ...
    rdtime  a0
    ...

Your second task is to implement the time() system call, which returns the value of the mtime register. The system call number of time() is already assigned to 23 in the ./kernel/syscall.h file. Note that the rdtime instruction is only available in M-mode, and you cannot access the value of the mtime register in S-mode where the kernel is running. Instead, you need to make another (nested) system call from S-mode to M-mode to get the value of the mtime register.

SYNOPSYS

    uint64 time();

DESCRIPTION

The time() system call returns the current value of the mtime register. It denotes the total elapsed time since the system was booted.

RETURN VALUE

  • time() returns a monotonically increasing 64-bit integer value that denotes the current time.

Restrictions

  • We found that the rdtime instruction is not supported or does not behave correctly in older versions of qemu. For this project assignment, you should use the qemu version 8.2.0 or higher. To determine the qemu version, use the command: $ qemu-system-riscv64 --version
  • We will run qemu-system-riscv64 with the -icount shift=0 option, which enables aligning the host and virtual clocks. This setting is already included in the Makefile for the pa2 branch.
  • You should not modify the mcounteren register.
  • You can assume a uniprocessor RISC-V system (CPUS = 1) for this project assignment.
  • You only need to change the files in the ./kernel directory. Any other changes will be ignored during grading.

Tips

  • Read Chap. 4.1 of the xv6 book to understand RISC-V's privileged modes (supervisor mode and machine mode) and trap handling mechanism. More detailed information can be found in the RISC-V Privileged Architecture manual.

  • Read Chap. 4.2 ~ 4.5 of the xv6 book to see how traps (system calls and interrupts) are handled in xv6.

  • Read Chap. 5.1 ~ 5.4 of the xv6 book to learn about hardware interrupts.

  • For your reference, the following roughly shows the amount of changes you need to make for this project assignment. Each + symbol indicates 1~5 lines of code that should be added, deleted, or altered.

    kernel/console.c   |  +
    kernel/defs.h      |  +
    kernel/kernelvec.S |  +++
    kernel/riscv.h     |  ++
    kernel/start.c     |  +
    kernel/syscall.c   |  +
    kernel/syscall.h   |  +
    kernel/sysproc.c   |  ++
    kernel/trap.c      |  +
    

Skeleton code

The skeleton code for this project assignment (PA2) is available as a branch named pa2. Therefore, you should work on the pa2 branch as follows:

$ git clone https://github.com/snu-csl/xv6-riscv-snu
$ git checkout pa2

After downloading, you must first set your STUDENTID in the Makefile again.

The pa2 branch has a user-level utility program called kbdints whose source code is available in the user/kbdints.c file. The kbdints program simply calls the kbdints() system call and prints its result. If you successfully implement the kbdints() system call, the output should look like this:

qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 1 -nographic -icount shift=0 -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

init: starting sh
$ kbdints
kbdints: 8
$ hello
exec hello failed
$ kbdints
kbdints: 22
$ QEMU: Terminated

Also, there is a user-level program called time in the user directory. Its source code is available in the user/time.c file. This program records the current time using the time() system call both before (t_start) and after (t_end) executing the sleep(1) system call, subsequently displaying these timestamps along with the elapsed duration (t_end - t_start). The sleep(1) system call pauses for one clock tick. While t_start can be non-deterministic, we can see that t_end is very close to the 100ms (= 1,000,000 cycles) boundary as shown in the following example.

qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 1 -nographic -icount shift=0 -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

init: starting sh
$ time
16387596 17001535 613939
$ time; time; time
36188865 37001600 812735
37029827 38001649 971822
38021471 39001571 980100
$ QEMU: Terminated

Hand in instructions

  • First, make sure you are on the pa2 branch in your xv6-riscv-snu directory. And then perform the make submit command to generate a compressed tar file named xv6-{PANUM}-{STUDENTID}.tar.gz in the ../xv6-riscv-snu directory. Upload this file to the submission server. You don't need to upload any documents for this project assignment.

  • The total number of submissions for this project assignment will be limited to 30. Only the version marked as FINAL will be considered for the project score. Please remember to designate the version you wish to submit using the FINAL button.

  • Note that the submission server is only accessible inside the SNU campus network. If you want off-campus access (from home, cafe, etc.), you can add your IP address by submitting a Google Form whose URL is available in the eTL. Now adding your new IP address is automated by a script that periodically checks the Google Form every 10 minutes.

    • If you cannot reach the server 10 minutes after submitting the Google Form, send the request again, as you might have sent the wrong IP address.
    • If you still cannot access the server after a while, that is likely due to an error in the automated process. The TAs will check if the script is properly running, but that is a manual process, so please do not expect it to be completed immediately.

Logistics

  • You will work on this project alone.
  • Only the upload submitted before the deadline will receive the full credit. 25% of the credit will be deducted for every single day delayed.
  • You can use up to 3 slip days during this semester. If your submission is delayed by one day and you decide to use one slip day, there will be no penalty. In this case, you should explicitly declare the number of slip days you want to use in the QnA board of the submission server right after each submission. Once slip days have been used, they cannot be canceled later, so saving them for later projects is highly recommended!
  • Any attempt to copy others' work will result in a heavy penalty (for both the copier and the originator). Don't take a risk.

Have fun!

Jin-Soo Kim
Systems Software and Architecture Laboratory
Dept. of Computer Science and Engineering
Seoul National University

About

OS Project #2 (Spring 2024)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published