rCore Notes: Suspend and Resume After a Certain Time

Introduction

In the exercise of Chapter 1, a programming exercise is proposed: use system calls to sleep for 5 seconds.

Analysis

Actually, LibOS cannot run and manage multiple processes at the same time, so we can suspend the CPU directly. But in Linux, the sleep function is implemented by the alarm system call, which is a little complex.

In RISC-V SBI, the sbi_hart_suspend function can change the state of the Hardware Thread (hart) to suspend (low power) mode, and the hart will not consume any CPU cycles until it is resumed.

In RISC-V SBI Documentation, sbi_hart_suspend is defined detailedly, here is the simplified explanation:

The signature of sbi_hart_suspend is as follows:

1
2
3
struct sbiret sbi_hart_suspend(uint32_t suspend_type,
                               unsigned long resume_addr,
                               unsigned long opaque)

suspend_type is the type of the suspend operation, two common types are retentive and none-retentive. A retentive suspend state will preserve hart register and CSR values for all privilege modes whereas a non-retentive suspend state will not preserve hart register and CSR values. In this exercise, we use the retentive suspend state, because it’s easier to implement.

resume_addr is the address to resume the hart. When the hart is resumed, it will start executing from this address. However, it unused during retentive suspend.

opaque is an XLEN-bit value which will be set in the a1 register when the hart resumes execution at resume_addr after a non-retentive suspend.

After that, the hart will be suspended, but how to resume it?

In many CPUs, there is a timer interrupt that can be used to wake up the CPU. In RISC-V SBI, the sbi_set_timer function can be used to set the timer interrupt. The signature of the sbi_set_timer function is as follows:

1
struct sbiret sbi_set_timer(uint64_t stime_value)

stime_value is the value to set the timer interrupt. The unit is the number of cycles of the timebase frequency of the CPU. Don’t forget it’s an absolute value, not a relative value. So before we call sbi_hart_suspend, we need to get the current time and add 5 seconds to it, then set the timer interrupt to this value. In addition, the full detail of sbi_set_timer can be found in RISC-V SBI Documentation.

In RISC-V, Control and status register (CSR) is a register that stores various information in CPU. The mtime CSR stores the number of clock cycles since the CPU started, and we can calculate the stime_value by adding the number of cycles of 5 seconds to the value of the mtime CSR.

stime_value = mtime + 5 * timebase_frequency

Solution

Get the Timebase Frequency

At first. we must know the timebase frequency of the CPU. For QEMU, we can get it by decompling the device tree blob (DTB) file.

1
2
3
4
5
$ qemu-system-riscv64 -machine virt,dumpdtb=qemu.dtb
qemu-system-riscv64: info: dtb dumped to qemu.dtb. Exiting.

$ dtc qemu.dtb | grep timebase-frequency
timebase-frequency = <0x989680>;

0x989680 is the timebase frequency of QEMU, in decimal, it is 10,000,000.

Add Dependencies

We need to add the riscv crate as a dependency to read mtime.

1
cargo add [email protected]

Implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// os/src/sbi.rs

const TIMEBASE_FREQ: usize = 0x989680; // 10_000_000 is QEMU's timebase frequency.

pub fn suspend_secs(sec: usize) {
    use riscv::register::time;
    use sbi_rt::set_timer;

    let current_mtime = time::read();
    let wake_up_stime = current_mtime + sec * TIMEBASE_FREQ;

    set_timer(wake_up_stime as u64);

    suspend();
}

pub fn suspend() {
    use sbi_rt::{hart_suspend, Retentive};

    hart_suspend(Retentive, 0, 0);
}

In this implementation, we use the riscv::register::time::read() function to get mtime. Then we calculate wake_up_stime by adding the number of cycles of a certain seconds to the value of the mtime CSR. After that, we set the timer interrupt to this value by calling sbi_set_timer. Finally, we call sbi_hart_suspend to suspend the hart.

Because we use the retentive suspend state, the resume_addr and opaque parameters are not used, we can set them to any value.

Use and Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// os/src/main.rs

#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();

    println!("5s suspend start");
    sbi::suspend_secs(5);
    println!("5s suspend end");

    sbi::shutdown(false);
}

After that, we can try to use the suspend_secs function to suspend the hart for 5 seconds. I tested it in QEMU, and it works well.

Summary

In this exercise, we implement the suspend_secs function to suspend the hart for a certain number of seconds. We use the riscv crate to read the mtime CSR and calculate the value of the timer interrupt. By using the RISC-V SBI functions, we can suspend the hart and resume it after a certain time. This is a basic skill for operating system development.

To be honest, this problem is a little difficult for me, a newbie in operating systems. I solved it by reading many articles and documents. But I still think it’s a good exercise for learning operating systems. I will continue to read the rCore Tutorial Book and write notes about it.

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy