Building your own Operating System (Week 03)

Krishan Shamod
4 min readAug 6, 2021

Hello Everyone !!

This is the third article in this series. In this article, I’m going to talk about how to integrate outputs. If you didn’t check my first article about “how to start implementing C language”, I recommend you to check it out first by here. For those coding and implementation staff, I’m following the guide “The little book about OS development” by Erik Helin and Adam Renberg.

First, we need to create our first driver which acts as a layer between the kernel and the hardware, providing a higher abstraction than communicating directly with the hardware.

Interacting with the Hardware

There are two different ways to interact with the hardware, which are memory-mapped I/O and I/O ports. If the hardware uses memory-mapped I/O then you can write to a specific memory address and the hardware will be updated with the new data.

If the hardware uses I/O ports then the assembly code instructions “out” and “in” must be used to communicate with the hardware. The instruction “out” takes two parameters which are the address of the I/O port and the data to send. The instruction “in” takes a single parameter, the address of the I/O port, and returns data from the hardware. The cursor of the framebuffer is one example of hardware controlled via I/O ports.

The Framebuffer

The framebuffer is a hardware device that is capable of displaying a buffer of memory on the screen.

Writing Text

Writing text to the console via the framebuffer is done with memory-mapped I/O. So, you need to create a directory called drivers and add this code to a file called “frame_buffer.h”. It will help to keep everything organized.

Moving the Cursor

Moving the cursor of the framebuffer is done via two different I/O ports. The “out” assembly code instruction can’t be executed directly in C. Therefore it is a good idea to wrap “out” in a function in assembly code that can be accessed from C. So, here is the code for the file called “io.s”.

Then you need to create a header file called “io.h” in the drivers directory using this code.

Moving the cursor can now be wrapped in a C function which is located in “frame_buffer.h” file.

The Driver

The driver should provide an interface that the rest of the code in the OS will use for interacting with the framebuffer. So, you can add this function to “frame_buffer.h” file.

The Serial Ports

The serial port is an interface for communicating between hardware devices. The serial port is easy to use, and, more importantly, it can be used as a logging utility in Bochs. If a computer has support for a serial port, then it usually has support for multiple serial ports, but we will only make use of one of the ports. This is because we will only use the serial ports for logging. Furthermore, we will only use the serial ports for output, not input. The serial ports are completely controlled via I/O ports.

Configuring the Serial Ports

To do that you need to add this code to a file called “serial_port.h” which is in the drivers directory.

Writing Data to the Serial Port

Writing data to the serial port is done via the data I/O port. However, before writing, the transmit FIFO queue has to be empty. For that and writing data to the serial port, you need to add these functions to “serial_port.h” file.

Reading the contents of an I/O port is done via the “in” assembly code instruction. There is no way to use the “in” assembly code instruction from C, therefore it has to be wrapped. So, you can do it by adding this code to “io.s” file.

Then you need to add this code to “io.h” which is in the drivers directory.

To get output to file called “com1.out” from the serial port one you need to add this line to “bochsrc.txt” file.

com1: enabled=1, mode=file, dev=com1.out

And you need to change “loader.s” file like this.

Finally, your “kmain.c” file need to be looks like this to run properly.

You can change the string and string length to get an output whatever you like. Then you can run your OS by the “make run” command and you will get output like this.

Also, we can see that output string in “com1.out”.

I think you all get a good idea about how to integrate outputs. You can check my Github Repo using this link.

Thank you for reading and I will be back next week with part 4 of this article series.

--

--