#+title: octox
#+author: Hayato Ohhashi
#+email: o8@vmm.dev
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.
[[https://vhs.charm.sh/vhs-6MQBIyAo3DpBrARBxHxL35.gif]]
- 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.
* Getting Started
** Requirements
- Install the rust toolchain to have cargo installed by following
[[https://www.rust-lang.org/tools/install][this]] guide.
- Install ~qemu-system-riscv~
- (option) Install ~gdb-multiarch~
** Build and Run
- 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, press ~Ctrl+a~ and ~x~.
** Play with the Shell
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: ~>~, ~>>~
* Development
** Userland Application
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
#+begin_src toml
[[bin]]
name = "_rm"
path = "bin/rm.rs"
#+end_src
- 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.
#+begin_src rust
#![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()
}
}
#+end_src
- Then, ~cargo run --target riscv64gc-unknown-none-elf~ in the root of octox.
- To use ~Vec~ and ~String~, etc, do the following:
#+begin_src rust
extern crate alloc;
use alloc::{string::String, vec::Vec};
#+end_src
** Kernel
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 SysCalls~ in src/kernel/syscall.rs.
Here is ~Dup2~ as an example:
#+begin_src rust
pub enum SysCalls {
Fork = 1,
...,
Dup2 = 23,
Invalid = 0,
}
#+end_src
- Define the function signature of the system call in the ~TABLE~ of
~SysCalls~. Use the enum type ~Fn~ to describe the return type(~U~ (Unit),
~I~ (Integer), ~N~ (never)) and use ~&str~ to represent arguments. then,
define kernel-side implementation as a method on ~SysCalls~. ~cfg~ flag is
used to control the compilation target for kernel and the rest. Here is an
example of ~dup2~:
#+begin_src rust
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)
}
}
#+end_src
- With just these steps, the dup2 system call is implemented in both kernel and
userland.
* License
Licensed under either of
- [[http://www.apache.org/licenses/LICENSE-2.0][Apache License, Version 2.0]]
- [[http://opensource.org/licenses/MIT][MIT license]]
at your option.
* Acknowledgments
octox is inspired by [[https://github.com/mit-pdos/xv6-riscv][xv6-riscv]].
I'm also grateful for the bug reports and discussion about the implementation
contributed by Takahiro Itazuri and Kuniyuki Iwashima.
* Contribution
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.
Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.
(top 24 of 53)
94 followers · starred Sep 2026
2,272 followers · starred Jul 2023
884 followers · starred Sep 2026
848 followers · starred May 2024
#+title: octox
#+author: Hayato Ohhashi
#+email: o8@vmm.dev
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.
[[https://vhs.charm.sh/vhs-6MQBIyAo3DpBrARBxHxL35.gif]]
- 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.
* Getting Started
** Requirements
- Install the rust toolchain to have cargo installed by following
[[https://www.rust-lang.org/tools/install][this]] guide.
- Install ~qemu-system-riscv~
- (option) Install ~gdb-multiarch~
** Build and Run
- 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, press ~Ctrl+a~ and ~x~.
** Play with the Shell
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: ~>~, ~>>~
* Development
** Userland Application
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
#+begin_src toml
[[bin]]
name = "_rm"
path = "bin/rm.rs"
#+end_src
- 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.
#+begin_src rust
#![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()
}
}
#+end_src
- Then, ~cargo run --target riscv64gc-unknown-none-elf~ in the root of octox.
- To use ~Vec~ and ~String~, etc, do the following:
#+begin_src rust
extern crate alloc;
use alloc::{string::String, vec::Vec};
#+end_src
** Kernel
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 SysCalls~ in src/kernel/syscall.rs.
Here is ~Dup2~ as an example:
#+begin_src rust
pub enum SysCalls {
Fork = 1,
...,
Dup2 = 23,
Invalid = 0,
}
#+end_src
- Define the function signature of the system call in the ~TABLE~ of
~SysCalls~. Use the enum type ~Fn~ to describe the return type(~U~ (Unit),
~I~ (Integer), ~N~ (never)) and use ~&str~ to represent arguments. then,
define kernel-side implementation as a method on ~SysCalls~. ~cfg~ flag is
used to control the compilation target for kernel and the rest. Here is an
example of ~dup2~:
#+begin_src rust
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)
}
}
#+end_src
- With just these steps, the dup2 system call is implemented in both kernel and
userland.
* License
Licensed under either of
- [[http://www.apache.org/licenses/LICENSE-2.0][Apache License, Version 2.0]]
- [[http://opensource.org/licenses/MIT][MIT license]]
at your option.
* Acknowledgments
octox is inspired by [[https://github.com/mit-pdos/xv6-riscv][xv6-riscv]].
I'm also grateful for the bug reports and discussion about the implementation
contributed by Takahiro Itazuri and Kuniyuki Iwashima.
* Contribution
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.
Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.
(top 24 of 53)
94 followers · starred Sep 2026
2,272 followers · starred Jul 2023
884 followers · starred Sep 2026
848 followers · starred May 2024