OS in Rust: Custom target to build kernel for a bare metal: Part-3

Reading Time: 5 minutes

This series pertains to create a basic Operating System using Rust Programming Language. This series aims to learn and understand the basics of Operating System.
Through this series, you will get some ideas about the internal components of Operating System and how they interact with each other.

This article is for building our 64-bit kernel for the x86 architecture on an executable that runs on bare metal, that we created in our previous articles.

This image has an empty alt attribute; its file name is images-1.jpeg

As we are building our kernel for a free-standing library(that runs on bare metal) we need to understand few terminologies before proceeding further.
So, let’s dive in step by step:

  • Booting process
  • BIOS booting
  • Providing custom target Specs

Booting Process

It is the process of starting a computer. After the computer is switched on, the computer’s CPU has no software in its RAM, so some processes must load software into memory before it can be executed.
So initially, it begins executing firmware code that is stored in motherboard ROM. This code performs a power-on self-test, detects available RAM, and pre-initializes the CPU and hardware. Afterward, it looks for a bootable disk and starts booting the operating system kernel.
On x86 architecture, there are two firmware standards: the “Basic Input/Output System” (BIOS) and the “Unified Extensible Firmware Interface” (UEFI). Here in this series will deal with BIOS only. So let’s jump into the BIOS booting.

BIOS Booting

BIOS stands for Basic Input/Output System and also known as the System BIOSROM BIOS or, PC BIOS, it is a firmware used to perform hardware initialization during the booting process and to provide runtime services for operating systems and programs.
At the very first moment, when we turn on a computer, it loads the BIOS from some special flash memory located on the motherboard and then the BIOS runs the self-test and initialization routines of the hardware then it looks for bootable disks.
If it finds the bootable disk then the control is transferred to its bootloader, which is a 512-byte portion of executable code stored at the disk’s beginning.

Bootloader

The main job of bootloader is to determine the location of the kernel image on the disk and load it into the memory.
It also needs to switch the CPU from the 16-bit real mode first to the 32-bit protected mode, and then to the 64-bit long mode.
Its another job is to query certain information from the BIOS and pass it to the OS kernel.
Now let’s quickly understand what is real more, protected mode and long mode.

Real Mode

It is an operating mode of all x86-compatible CPUs. It offers a higher clock speed but limits the processor to only use 16-bit instructions and a minimum of 1 MB of RAM (20-bit).

Protected Mode

It is an operational mode of x86-compatible CPUs. It allows system software to use features such as virtual memory, paging, and safe multi-tasking designed to increase an operating system’s control over application software.

Long Mode

It is the mode where a 64-bit operating system can access 64-bit instructions and registers.

Here in this series, we are not writing our bootloader, we are just using a tool that will automatically prepend bootloader in our kernel.

Providing custom target Specs

Before providing the target specification we need to switch to the Rust Nightly channel as it allows us to opt-in to various experimental features. And here we are building our kernel on an executable that runs on bare metal.
If we don’t switch to nightly channel then the stable channel won’t allow us to build our kernel for our target we will be using.
We can easily switch our channel from stable to nightly using this command:

rustup default nightly

Now let’s continue with the custom target specification process:
In Rust, cargo supports different target systems through --target parameter and the target is described by target triple. We already discussed target triple in our previous articles.

To create a custom kernel, we need a target with no underlying Operating System(OS). And Rust allows us to define our target through a JSON file.

Syntax for defining a custom target

{
    "llvm-target": "target_detail",
    "data-layout": "layout_value",
    "arch": "architecture",
    "target-endian": "value",
    "target-pointer-width": "value",
    "target-c-int-width": "value",
    "os": "os_name",
    "executables": boolean,
    "linker-flavor": "flavour_name",
    "pre-link-args": ["item1","item2"],
    "morestack": boolean
}

This is how we define our custom target in Rust Programming. Now let’s have a look at the JSON file for x86_64-unknown-linux-gnu. As we are creating our custom target, so we’ll use few common configurations from x86_64-unknown-linux-gnu target and will change some of the fields as per our requirement.

Here is the JSON file for x86_64-unknown-linux-gnu target:

{
    "llvm-target": "x86_64-unknown-linux-gnu",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "arch": "x86_64",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "os": "linux",
    "executables": true,
    "linker-flavor": "gcc",
    "pre-link-args": ["-m64"],
    "morestack": false
}

In our custom target, we’ll also use the same architecture (i.e, x86_64) and most of the fields are required by LLVM to generate code for the platform. Some of the fields used by Rust for conditional compilation.

Now, let’s write our custom target by creating a x86_64-os-in-rust.json file, we’ll use some common configuration by changing a few fields:

Custom Specification for our target

{
    "llvm-target": "x86_64-unknown-none",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "arch": "x86_64",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "os": "none",
    "executables": true
}

In the above, configuration we have used llvm-target as x86_64-unknown-none because we don’t need any OS and we’ll run it on bare metal. Same for os field as well.
Now let’s add few build related configurations that we need for our custom target.

"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float",

Let’s understand these fields:

The linker-flavor and linker these fields are used to linking our kernel and here we used cross-platform LLD linked that is shipped with Rust.

panic-strategy specifies that the target doesn’t support stack unwinding on panic, so the program aborts directly.

disable-redzone red zone is a fixed-size area in a function’s stack frame below the stack pointer. To handle interrupts at some point, we have to disable a certain stack pointer optimization called the “red zone” because it would cause stack corruptions.

features this field enables/disables target features.
So here we disable mmx and sse features and enable soft-float. mmx and sse determine support for SIMD.
Since SIMD state is very large and interrupts can occur frequently, so kernel performs the save/restore operations that harm performance. That’s why we disable SIMD for our kernel.
Now there is a problem with disabling SIMD, ie, floating-point operations on x86_64 require SIMD registers by default.

To solve this we enabled soft-float feature, which helps to emulate floating-point operations through software functions.

Okay, so here is the final specification for our custom target:

{
  "llvm-target": "x86_64-unknown-none",
  "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
  "arch": "x86_64",
  "target-endian": "little",
  "target-pointer-width": "64",
  "target-c-int-width": "32",
  "os": "none",
  "executables": true,
  "linker-flavor": "ld.lld",
  "linker": "rust-lld",
  "panic-strategy": "abort",
  "disable-redzone": true,
  "features": "-mmx,-sse,+soft-float"
}

That’s all for this article thank you for reading.
In the next article, we will build our custom kernel by using this custom target specification.

Stay tuned!!!

References:

If you want to read more content like this?  Subscribe Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe Rust Times Newsletter: https://bit.ly/2Vdlld7 .

This image has an empty alt attribute; its file name is screenshot-from-2020-06-08-11-00-35.png

Knoldus-blog-footer-image


Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than a year in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute towards the community which he attained from the community.

Leave a Reply