octox is a Unix-like operating system inspired by xv6-riscv. octox loosely follows the structure and style of xv6, but is implemented in pure Rust.
- Everything from kernel, userland, mkfs, to build system is written in safe Rust as much as possible.
- There are no dependencies on external crates.
- The userland has a library similar to Rust’s std with K&R malloc.
- Multi-core support, buddy allocator as kernel-side memory allocator, file system with logging support, etc.
- Install the rust toolchain to have cargo installed by following this guide.
- Install
qemu-system-riscv - (option) Install
gdb-multiarch
- Clone this project & enter:
git clone ... && cd octox - Build:
cargo build --target riscv64gc-unknown-none-elf. - Run:
cargo run --target riscv64gc-unknown-none-elf, then qemu will boot octox. To exit, pressCtrl+aandx.
A very simple shell is implemented. In addition to executing commands, you can only do the following things.
- Pipe:
cat file | head | grep test - Dump processes:
Ctrl + P - End of line:
Ctrl + D - Redirect output:
>,>>
The userland comes with a user library called ulib (located at src/user/lib)
that is similar to Rust’s std, so you can use it to develop your favorite
commands. If you create a bin crate named _command in src/user/bin, the
build.rs and mkfs.rs will place a file named command in the file system
and make it available for use.
- In src/user/Cargo.toml, define a bin crate with the name of the command you
want to create with a
_prefix[[bin]] name = "_rm" path = "bin/rm.rs"
- userland is also no_std, so don’t forget to add
#[no_std]. Use ulib to develop any command you like. Here is an example of the rm command.#![no_std] use ulib::{env, fs}; fn main() { let mut args = env::args().skip(1).peekable(); if args.peek().is_none() { panic!("Usage: rm files...") } for arg in args { fs::remove_file(arg).unwrap() } }
- Then,
cargo run --target riscv64gc-unknown-none-elfin the root of octox. - To use
VecandString, etc, do the following:extern crate alloc; use alloc::{string::String, vec::Vec};
Developing in src/kernel. Here is an example of adding a system call. If you want to add a new system call, you only need to add a definition to the system call table in libkernel, and the userland library will be automatically generated by build.rs.
- Add a variant and Syscall Number to
enum SysCallsin src/kernel/syscall.rs. Here isDup2as an example:pub enum SysCalls { Fork = 1, ..., Dup2 = 23, Invalid = 0, }
- Define the function signature of the system call in the
TABLEofSysCalls. Use the enum typeFnto describe the return type(U(Unit),I(Integer),N(never)) and use&strto represent arguments. then, define kernel-side implementation as a method onSysCalls.cfgflag is used to control the compilation target for kernel and the rest. Here is an example ofdup2:impl SysCalls { pub const TABLE: [(fn, &'static str); variant_count::<Self>()] = [ (Fn::N(Self::Invalid), ""), (Fn::I(Self::fork), "()"), (Fn::N(Self::exit), "(xstatus: i32)"), ..., (Fn::I(Self::dup2), "(src: usize, dst: usize)"), ]; pub fn dup2() -> Result<usize> { #[cfg(not(all(target_os = "none", feature = "kernel")))] return Ok(0); #[cfg(all(target_os = "none", feature = "kernel"))] { let p = Cpus::myproc().unwrap().data_mut(); let src_fd = argraw(0); let dst_fd = argraw(1); if src_fd != dst_fd { let mut src = p.ofile.get_mut(src_fd).unwrap() .take().unwrap(); src.clear_cloexec(); p.ofile.get_mut(dst_fd) .ok_or(FileDescriptorTooLarge)?.replace(src); } Ok(dst_fd) } }
- With just these steps, the dup2 system call is implemented in both kernel and userland.
Licensed under either of
at your option.
octox is inspired by xv6-riscv.
I’m also grateful for the bug reports and discussion about the implementation contributed by Takahiro Itazuri and Kuniyuki Iwashima.
This is a hobby learning project, but contributions are welcome! However, please note that reviews may take considerable time. Discussions and advice are always appreciated.