Open-source project
rcore-os/rCore-Tutorial-v3 avatar
rcore-os/rCore-Tutorial-v3

rCore-Tutorial-v3: a 4K-line RISC-V kernel that only exists one chapter branch at a time

Let's write an OS which can run on RISC-V in Rust from scratch!

2,123 stars559 forksRustGPL-3.0

At a glance

What is it?
A teaching kernel rather than a product: about 4K lines of Rust that build a Unix-like RISC-V system, but whose real code lives on chapter branches instead of one linear history.
Who is it for?
rCore-Tutorial-v3 earns a place on the desk of someone learning operating systems from source, and none on a product roadmap. Its 4K lines say nothing about hardening, multi-hart behaviour or long-term support, and its narrative exists only in Chinese.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 112 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A 4K-line kernel aimed at readers with no architecture background

This project answers one narrow question: can somebody who has never studied computer architecture, assembly language or operating systems write a Unix-like system that boots on RISC-V? The answer it ships is roughly 4K lines of Rust, arranged so that each subsystem arrives only after the previous chapter has explained it.

What those lines implement is a fixed list. Processes run concurrently and each one carries several native threads. Scheduling is preemptive with a Round-Robin algorithm. Memory is handled dynamically inside the kernel, on top of virtual memory. Storage is a simple filesystem with a block cache. Interaction happens through an interactive shell that runs in user space rather than in the kernel. Each item is there because a chapter needed it, which makes the list read as a syllabus instead of a product feature sheet.

The top-level layout follows that logic. A `bootloader/` directory sits beside the kernel sources in the os directory, the user programs live in `user/`, the filesystem crate is `easy-fs/`, and `easy-fs-fuse/` covers the host side of that same filesystem. Supporting entries include a `ping.py` helper, `setenv.sh`, a `rust-toolchain.toml` that pins the compiler, a `Dockerfile`, `dev-env-info.md`, and editor and CI directories. Roughly 4K lines is small enough to read end to end, and that is the point of the whole exercise.

Chapter code lives on chX branches while the header claims version 3.6

The most confusing thing about this repository is where the code actually lives. The header line calls the project rCore-Tutorial version 3.6, yet the news entry dated 23/06/2022 says version 3.6.0 is on the way and that updates now land directly on chX branches. The only tagged release is v3.5.0 from 2021-10-17, and the last push arrived on 2026-07-03 under GPL-3.0, so branches keep receiving work while the version label in the header does not move with them.

Practically, a reader who clones the default branch and expects to find the last chapter may not find it. The project documents exactly one checkout, and it is not the mainline build but the logging branch used to trace kernel internals:

sh
$ cd rCore-Tutorial-v3/os
$ git checkout ch9-log
$ make run

That branch name is also the only hint about how chapters are organised, since the project ships no per-chapter tags and no branch list beyond the chX convention stated in the news entry. If you are following the book, matching your checkout to the chapter you are reading is the first thing to get right, and the repository gives you no automated way to check that you did.

riscv64gc-unknown-none-elf and a cargo-binutils flag with a space in it

Before any kernel code compiles, the host needs a bare-metal RISC-V target, a binutils shim for the linker tools, and two rustup components. The project gives the exact four commands, and the middle one carries an unusual detail worth copying carefully rather than correcting:

sh
$ rustup target add riscv64gc-unknown-none-elf
$ cargo install cargo-binutils --vers =0.3.3
$ rustup component add llvm-tools-preview
$ rustup component add rust-src

The target is a bare one, with no operating system underneath it, which is consistent with a kernel that must own the machine. The cargo-binutils line pins an exact version, 0.3.3, rather than taking whatever the registry serves, and its `--vers =0.3.3` spelling with a space before the equals sign is what the project prints. The two component installs pull LLVM tools and the Rust standard library sources; the project does not state in that block what each one is needed for, so a reader assembling this from memory will be guessing.

Rust itself is installed through the official rustup route. That part is not automated inside the project, so anyone automating a classroom or CI image will be reproducing these commands by hand.

QEMU 7.0.0 built from source, and the same build inside Docker

The simulator is not taken from a package manager. QEMU 7.0.0 is compiled from a downloaded tarball with both RISC-V targets enabled, system emulation for full machines and the user-mode target for individual programs:

sh
$ wget https://download.qemu.org/qemu-7.0.0.tar.xz
$ tar xvJf qemu-7.0.0.tar.xz
$ cd qemu-7.0.0
$ ./configure --target-list=riscv64-softmmu,riscv64-linux-user
$ make -j$(nproc)

The build lands outside the system prefix, so the README has you append the output directory to your shell path and reload the shell before checking anything:

sh
$ qemu-system-riscv64 --version
QEMU emulator version 7.0.0

Debugging adds a separate dependency that no package manager provides: the RISC-V GNU Embedded toolchain including GDB is downloaded from the vendor site by hand, and its bundled GDB reports itself as SiFive GDB-Metal 10.1.0-2020.12.7. Running on real Kendryte K210 boards also needs `pyserial` for the serial console, which the project marks as optional.

The Dockerfile in the repository performs the same QEMU build in a first stage from `ubuntu:20.04`, parameterised by an argument defaulting to QEMU_VERSION=7.0.0, then copies the resulting binaries into a second lab stage that also installs a multi-architecture GDB and a minimal Rust profile. Package mirrors are swapped to Chinese mirrors at build time, so an offline or regional build depends on those hosts.

make run prints the app table and refuses two entries

A first run clones the repository, enters the kernel directory and hands everything to make:

sh
$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git
$ cd rCore-Tutorial-v3/os
$ make run

After debug output, the kernel prints the applications it found on disk and drops you at a prompt called the Rust user shell. The table is long and specific, mixing console experiments with concurrency and filesystem tests:

code
/**** APPS ****
mpsc_sem
usertests
pipetest
forktest2
cat
initproc
race_adder_loop
threads_arg
forktree
user_shell
huge_write
stack_overflow
cmdline_args
matrix
phil_din_mutex
**************/
Rust user shell
>> 

Two entries cannot be launched by name: `initproc` and `user_shell` itself. Everything else is started by typing its filename and pressing enter, and one app is pointed at explicitly as the recommended first run because it executes a batch of the others, which is `usertests`. The names also expose what the kernel is meant to survive: pipe tests, a large pipe, fork trees, thread argument passing, mutex spin versus blocking races, stack overflow and a huge write.

One gap is worth naming. The table includes an application called cmdline_args, yet the only documented interaction is a filename with no documented way to pass an argument to it. Quitting the simulator is a key sequence, `Ctrl+a` followed by x, which differs from the `Ctrl+]` used for the board.

The sdcard target writes over /dev/sdb unless you redirect it

Board work takes a different path from the simulator. Before chapter 6 no SD card is needed at all; from chapter 6 onward the filesystem image has to exist on a real card before the kernel runs:

sh
$ cd rCore-Tutorial-v3/os
$ make sdcard

That target writes directly to a block device, and by default it assumes the SD card is `/dev/sdb`. On a machine with two disks, or on one where the enumeration order differs after a reboot, that assumption is a data-loss hazard rather than a formality, because nothing in the command asks which device it is. The project does document the escape hatch, an override that names a different device:

`make sdcard SDCARD=/dev/sdc`

After the image is written, the card is moved out of the PC and into the K210 slot, the board is reconnected, and the kernel is launched through the same makefile with a board argument rather than through the simulator path:

sh
$ make run BOARD=k210

The trade-off for that extra hardware step is a real target: the same kernel image that runs under QEMU runs on Kendryte K210 boards such as the Maix Dock, with the serial console handled by `pyserial`. Leaving the board session is `Ctrl+]`.

ch9-log exposes the boot chain, and the prose explaining it is Chinese

The most instructive run is not the main branch but the logging one, because it prints what the firmware layer and the kernel do before the shell appears. On the ch9-log branch the output starts in the supervisor interface:

code
[rustsbi] RustSBI version 0.2.0-alpha.10, adapting to RISC-V SBI v0.3
[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] enter supervisor 0x80200000
[KERN] rust_main() begin
[KERN] clear_bss() begin
[KERN] clear_bss() end
[KERN] mm::init() begin
[KERN] mm::init_heap() begin
[KERN] mm::init_heap() end

Those lines place the whole boot path in one screen: a pre-MSI standard interface at an alpha firmware version, an ISA string naming the RISC-V extensions the machine exposes, a single hart, and a supervisor entry address. The kernel side then clears its BSS, sets up the heap and moves on to the frame allocator. A physical memory protection window printed on the same run covers 0x80000000 through 0x8fffffff with read, write and execute rights, which tells a reader that kernel text is mapped as executable and writable in this configuration.

The catch is that the reasoning behind those lines lives in the Chinese book, and an English version is not available at present, with the code itself carrying few comments. Reading this output well requires the prose, so the branch is close to unusable alone for an English-only reader.

What 4K lines cannot cover, and the two lighter paths around it

Two limits are structural rather than fixable by reading harder. First, the debug boot log shows a machine with one hart, so nothing in this repository demonstrates scheduling or memory behaviour across cores. Second, the storage story is a small filesystem with a block cache, written either to a raw SD card or through a host-side mount: the tree carries `easy-fs-fuse/` next to `easy-fs/`, and the top-level format target walks both, which confirms they are separate crates, but the README does not document a command line for the FUSE one. That is the real alternative to the destructive `make sdcard` path, and the project leaves its invocation to you.

The second alternative is QEMU's own user-mode target. The configure line builds `riscv64-linux-user` alongside the full system emulator, which opens a completely different workflow: user programs can be run under `riscv64-linux-user` against a hosted toolchain with no kernel, no firmware, no memory management and no bootloader at all. You give up every layer the chapters exist to teach, and in exchange a user program starts in the time it takes to compile it, which is exactly the tradeoff a reader debugging one process in `user/` sometimes wants.

The same reasoning applies to the environment. The Makefile exposes a container route with a tag defaulting through `DOCKER_TAG`, which skips the host QEMU build entirely and hands you a shell inside the prepared lab image. Either way, the project assumes a Linux host with a compiler toolchain and does not document a rollback path for a half-finished chapter branch.

Editorial conclusion

rCore-Tutorial-v3 earns a place on the desk of someone learning operating systems from source, and none on a product roadmap. Its 4K lines say nothing about hardening, multi-hart behaviour or long-term support, and its narrative exists only in Chinese. Before starting, confirm that the branch you checked out matches the chapter you intend to read, that the machine running make sdcard has no real disk behind /dev/sdb, and that your host can build QEMU 7.0.0 or reach the Docker image built from the same Dockerfile.

Frequently asked questions

Can I build rCore-Tutorial-v3 without compiling QEMU on my own machine?

Yes, through the Dockerfile in the repository, which builds QEMU 7.0.0 in one stage and copies the binaries into a prepared lab stage, and through the top-level Makefile targets that build and enter that image using DOCKER_TAG. You still supply the Rust and GDB pieces inside the image rather than on the host.

How do I run rCore-Tutorial-v3 on real hardware instead of the simulator?

Use the board argument with make run BOARD=k210, which targets Kendryte K210 boards such as the Maix Dock. Before chapter 6 no SD card is required; from chapter 6 you must first write the filesystem image with make sdcard and move the card into the board.

Which application should I run first to check that my rCore-Tutorial-v3 build works?

The kernel lists every application it found and enters the Rust user shell, where you start one by typing its filename. The project points at usertests as the recommended first run because it executes a batch of the others, and notes that initproc and user_shell cannot be launched that way.

Where can I read the rCore-Tutorial-v3 manual in English?

You cannot at present. The documentation link in the README points at the Chinese book, and an English version is stated to be unavailable, with the code carrying few comments, so the log branch is hard to interpret without the Chinese prose.

How do I see what the kernel is doing during boot?

Check out the ch9-log branch and run make run. That build prints the firmware layer, including RustSBI version 0.2.0-alpha.10 adapting to RISC-V SBI v0.3 and the hart count, followed by kernel trace lines such as rust_main(), clear_bss() and mm::init().

Official sources

  1. License: GPL-3.0
  2. Project website
  3. rcore-os/rCore-Tutorial-v3 on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rcore-os-rcore-tutorial-v3.svg)](https://hysenlabs.com/projects/rcore-os-rcore-tutorial-v3)

Was this useful?