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.

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:

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:

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:
- An executable that runs on bare metal: Part-1
- An executable that runs on bare metal: Part-2
- Custom target to build kernel for a bare metal: Part-3
- Building kernel for custom target: Part-4
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 .

