This repository documents my exploration in operating system development using (primarily) Rust.
- Setup Docker on your system.
- Setup a package manager for your system (this will make it easier to install the required tools)
- Windows: Install Chocolatey.
- MacOS: Install Homebrew.
- Linux: No need to install anything, you can use your distributions package manager.
- Install make for your OS. We'll be using this to automate our build system.
- Windows: From the command line, run
choco install make. - MacOS: From the terminal, run
brew install make. - Linux: Use your distributions package manager. For eg., on Ubuntu run
apt-get install make.
- Windows: From the command line, run
- Install Qemu to test out the operating system.
- Windows: From the command line, run
choco install qemu. - MacOS: From the terminal, run
brew install qemu. - Linux: Use your distributions package manager or follow the install instructions.
- Windows: From the command line, run
- Clone this repository.
cdinto the directory. - Generate the build environment image by running
make env(this might take a while). - This should install all required tools (GCC cross compiler, xorisso, GRUB tools, nasm, the nightly Rust toolchain etc.)
- Firstly, run the build environment docker image:
make docker. - Then, compile the kernel and create the iso using:
make iso. - To test the OS, run
make runon your system shell.
- The project does compiles for a bare metal target, hence it does not use the Rust standard library.
- I couldn't get
cargo testto work with no-std, so I've used a scrappy custom testing framework. - Write your test functions inside the crate::test_cases! macro, to run them as test cases. Example -
crate::test_cases! {
fn box_allocation() {
let heap_value_1 = Box::new(41);
let heap_value_2 = Box::new(13);
assert_eq!(*heap_value_1, 41);
assert_eq!(*heap_value_2, 13);
}
fn vector_allocation() {
let n = 1000;
let mut vec = Vec::new();
for i in 0..n {
vec.push(i);
}
assert_eq!(vec.iter().sum::<u64>(), (n - 1) * n / 2);
}
fn multiple_boxes_causing_reallocation() {
for i in 0..HEAP_SIZE {
let x = Box::new(i);
assert_eq!(*x, i);
}
}
fn reference_counting() {
let rc = Rc::new(42);
assert_eq!(Rc::strong_count(&rc), 1);
let cloned_rc = rc.clone();
assert_eq!(Rc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&cloned_rc), 2);
core::mem::drop(rc);
assert_eq!(Rc::strong_count(&cloned_rc), 1);
}
}Most of the roadmap follows the great blog by Philipp Oppermann. However, I sort of combined the first and second editions of the blog, since I couldn't get some things to work, or just wanted to build it from scratch.
- Basic kernel and booting
- Setup IDT and catch exceptions
- Setup VGA Buffer
- Setup tests (using linkme crate)
- Setup IDT and catch exceptions
- Handle hardware interrupts (using the pic8259 crate)
- Improve testing framework (added the crate::test_cases macro)
- Make a frame allocator to allocate physical frames
- Setup 4-level recursive paging
- Setup a heap allocator
- Create a linked list allocator for heap allocations
- Setup a VESA framebuffer
- Remove the VGA buffer usages for logging
- Setup a better logger (println macro prints to serial if framebuffer is not initialized, else prints to the framebuffer)
- Implement double buffering
- Implement an asynchronous task executor
- Create a process scheduler?
- Implement some filesystem (FAT32 maybe?)