Skip to content

TaijiOS is a hobby operating system written from scratch in Rust for learning systems programming. It's my own OS that attempts to bring back the "P" in PC. Design goals: minimal, modular, reliable, and safe.

License

Notifications You must be signed in to change notification settings

cedrickchee/taijios

Repository files navigation

Image generated by Stable Diffusion v2.0 — stylish computer from 1980s Miami, pink and blue neon color scheme, cyberpunk

TaijiOS

TaijiOS is a hobby operating system written from scratch in Rust for learning systems programming and OSdev.


Table of Contents

About the Project

This project implements a microkernel. It's a minimal 64-bit OS kernel for x86 architecture.

Currently, the kernel boots without crashing and can print something to the screen. Keyboard input is working. (see a demo below)

Demo

taijios-alpha-demo.mp4

Status

This repository contains my Work-In-Progress (WIP) code. Things are still unstable. "Here be dragons".

About Multitasking

Cooperative multitasking is working. I'm implementing preemptive multitasking.

About the Code

The code for each feature lives in a separate git tag. This makes it possible to see the intermediate state after each feature.

The latest code is available in the "main" branch.

You can find the tag for each feature by following the link in the feature list below.

You can check out a tag in a subdirectory using git:

# make sure that the tag exists locally by doing
$ git fetch --tags

# check out the tag by running
$ git checkout tags/09-paging-implementation

Features

A side-effect of this project is that you can follow the tags for each features in the following order to see how the kernel evolved:

  1. A Rust executable that does not link the stdlib
  2. A minimal Rust kernel
  3. Print text to the screen in VGA text buffer
  4. Unit and integration testing
  5. Interrupt: CPU exceptions
  6. Interrupt: Double fault handler
  7. Interrupt: Hardware, timer interrupts, keyboard input
  8. Memory: Concept of paging
  9. Memory: Paging implementation
  10. Memory: Dynamic memory, implement basic support for heap allocations
  11. Memory: Implement heap allocators from scratch
  12. Cooperative multitasking: Task, create a simple executor

Building

Install Rust nightly

This project requires a nightly version of Rust because it uses some unstable features. At least nightly 2020-07-15 is required for building. You might need to run rustup update nightly --force to update to the latest nightly even if some components such as rustfmt are missing it.

The build-std feature of Cargo

Building the kernel for our new target will fail if we don't use the feature. To use the feature, we need to create a Cargo configuration file at .cargo/config.toml with the following content:

...

[unstable]
build-std = ["core", "compiler_builtins"]

Memory-Related Intrinsics

The Rust compiler assumes that a certain set of built-in functions is available for all systems. Most of these functions are provided by the compiler_builtins crate that we just recompiled. However, there are some memory-related functions in that crate that are not enabled by default because they are normally provided by the C library on the system. These functions include memset, memcpy, and memcmp.

Since we can’t link to the C library of the operating system, we need an alternative way to provide these functions to the compiler.

Fortunately, the compiler_builtins crate already contains implementations for all the needed functions, they are just disabled by default to not collide with the implementations from the C library. We can enable them by setting cargo’s [build-std-features] (https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std-features) flag to ["compiler-builtins-mem"]. This can be configured in the unstable table in the .cargo/config.toml file.

...

[unstable]
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "compiler_builtins"]

(Support for the compiler-builtins-mem feature was only [added very recently] (rust-lang/rust#77284), so you need at least Rust nightly 2020-09-30 for it.)

With this change, our kernel has valid implementations for all compiler-required functions, so it will continue to compile even if our code gets more complex.

Set a Default Target

To avoid passing the --target parameter on every invocation of cargo build, we can override the default target. To do this, we add the following to our cargo configuration file at .cargo/config.toml:

...

[build]
target = "x86_64-tiny_os.json"

This tells cargo to use our x86_64-tiny_os.json target when no explicit --target argument is passed. This means that we can now build our kernel with a simple cargo build.

We are now able to build our kernel for a bare metal target!

To build this project, run:

$ cargo build
  Downloaded getopts v0.2.21
  ...
  Downloaded libc v0.2.126
  Downloaded compiler_builtins v0.1.73
  Downloaded cc v1.0.69
  ...
  Downloaded 14 crates (2.1 MB) in 1.36s
   Compiling core v0.0.0 (~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core)
   Compiling compiler_builtins v0.1.73
   Compiling rustc-std-workspace-core v1.99.0 (~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/rustc-std-workspace-core)
   Compiling tiny-os v0.1.0 (~/repo/github/tiny-os)
    Finished dev [unoptimized + debuginfo] target(s) in 11.90s
If you encountered linker errors The linker is a program that combines the generated code into an executable. Since the executable format differs between Linux, Windows, and macOS, each system has its own linker that throws a different error. The fundamental cause of the errors is the same: the default configuration of the linker assumes that our program depends on the C runtime, which it does not.

To solve the errors, we need to tell the linker that it should not include the C runtime. We can do this either by passing a certain set of arguments to the linker or by building for a bare metal target.

Building for a Bare Metal Target

By default Rust tries to build an executable that is able to run in your current system environment. For example, if you’re using Windows on x86_64, Rust tries to build a .exe Windows executable that uses x86_64 instructions. This environment is called your “host” system.

To describe different environments, Rust uses a string called target triple.

By compiling for our host triple, the Rust compiler and the linker assume that there is an underlying operating system such as Linux or Windows that use the C runtime by default, which causes the linker errors. So to avoid the linker errors, we can compile for a different environment with no underlying operating system.

An example for such a bare metal environment is the thumbv7em-none-eabihf target triple, which describes an embedded ARM system. The details are not important, all that matters is that the target triple has no underlying operating system, which is indicated by the none in the target triple. To be able to compile for this target, we need to add it in rustup:

$ rustup target add thumbv7em-none-eabihf
info: downloading component 'rust-std' for 'thumbv7em-none-eabihf'
info: installing component 'rust-std' for 'thumbv7em-none-eabihf'

This downloads a copy of the standard (and core) library for the system. Now we can bu