OS in Rust: Running our custom kernel on an emulator: Part-5

Reading Time: 4 minutes

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

This article pertains to run our custom kernel on QEMU emulator using customized target that we created previously.

This image has an empty alt attribute; its file name is os.jpeg

If you are new to this series, then it’d be great if you have a look at the previous posts so that you can get an idea from where we started and how much we have covered till now.

To complete the scope of this articles we have to follow few steps:

  • Provide definition to _start()
  • Incorporate Boot-loader
  • Create Bootimage
  • Booting in Emulator

Provide definition to _start()

For now, to verify our kernel whether it runs successfully or not we have to print something to the screen.
To do so, we have to provide a definition to our _start() function because with this we can print text to the screen.
So for this, we’ll use the easiest way to print text to the screen which is using a VGA text buffer.
VGA text buffer is a two-dimensional array with typically 25 rows and 80 columns, which is directly rendered to the screen. We’ll discuss the VGA text buffer in more detail in our coming articles.
Now let’s add few lines of code in the _start() to print text into the screen.

static PRINT_MESSAGE: &[u8] = b"WELCOME TO OUR CUSTOM OS";

#[no_mangle]
pub extern "C" fn _start() -> ! {

    let buffer = 0xb8000 as *mut u8;

    for (num, &byte) in PRINT_MESSAGE.iter().enumerate() {
        unsafe {
            *buffer.offset(num as isize * 2) = byte;
            *buffer.offset(num as isize * 2 + 1) = 0x9;
        }
    }

    loop {}
}

The above lines are used to print text to the screen. Let’s try to understand the code.
Firstly, we’ve cast the integer into a raw pointer(raw pointers can be immutable or mutable).
Secondly, we iterate over the byte string that we defined just above the _start().
Then inside the loop, we use the offset method(Calculates the offset from a pointer) to write the string byte and the color byte, here we’ve used 0x9 for color which means light blue. We’ll understand the functioning of VGA Buffer deeply in our coming articles.
And finally, we wrapped the body of our loop in the unsafe block because we are using raw pointers and the Rust compiler can’t prove that the raw pointers are valid so through unsafe block we are ensuring to the compiler that all the operations are valid.
Now let’s move to the next step where we need to incorporate a bootloader.

Incorporate Boot-loader

To run our kernel we need to compile it into a bootable image by linking it with a bootloader.
As we know, the bootloader is responsible for initializing the CPU and loading the kernel. So instead of writing our bootloader, we’ll use the bootloader crate directly.
To incorporate this we need to add a dependency of the bootloader in our Cargo.toml file like this:

[dependencies]
bootloader = "0.9.8"

Now we have bootloader support to our kernel so, the next step is to compile the bootable image and link it with the provided bootloader, but the problem here is cargo has no support for post-build-scripts, which means cargo can only compiler our kernel and bootloader but unable to link them together after building.
To make a bootable image we have a tool called bootimage, let’s try to make a bootable image using this tool in our next step.

Create Bootimage

In order to use this tool, we’ve to install it using this command:

cargo install bootimage

Before using this tool or we need to have llvm-tools-preview rustup component installed in our machine. We can install this by using this command:

rustup component add llvm-tools-preview

Now we are set to create our bootable image. To create a bootable image we just need to hit one command which is:

cargo bootimage

After hitting this command, the output shows us the location of our bootable image. Like this:

This image has an empty alt attribute; its file name is screenshot-from-2021-01-13-20-20-43.png

So we can find our bootable image in the provided location i.e., ../target/x86_64-os-in-rust/debug/bootimage-os-in-rust.bin.

Booting in Emulator

We’ve created a bootable image of our custom kernel, now we just need to run it using an emulator. So here we are using the QEMU emulator to boot-up the kernel.
To run our bootable image we need to hit a command provided by QEMU which is:

qemu-system-x86_64 -drive format=raw,file=path-to-bootimage

Let’s try to hit this command for our bootable image:

This image has an empty alt attribute; its file name is screenshot-from-2021-01-13-20-38-00.png

Okay, so the text we provided in our implementation is successfully printed to the screen.

That’s all for this article, thanks for reading…

In the next article, we’ll try to understand the role of VGA Buffer.

Stay tuned!!!

References:

Blogs of this series:

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 rust-times.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.