How To Implement Your Own Libc
Our OS doesn’t have libc support, so let’s work on that!
--

This is the second part in a series I’m writing about creating your own operating system. You can follow along through the repository on GitHub (or Sourcehut).
When we left, we had set up a basic shell that would greet us whenever we booted into our system. There wasn’t much else to it beyond the written Assembly code and the bits and pieces of C such as VGA colour codes and low-level printing. In this post, we’ll expand on our shell program and implement some key libc functionality.
First things first, we’ll need to create some files to hold our code. You may call them whatever you like, but I created a basic source tree that looks something like this:
- / (root directory)
- /main — Contains the entry point and our shell program.
- /lib — Contains our “libraries” for use throughout the application.
- /lib/boot — Contains our bootloader.
- /lib/libc — Contains our libc implementation.
- /lib/link — Contains our linker scripts.
NOTE: This layout might not be your favorite, and you’re welcome to set up your source tree as you’d like. This is what helps keep me organized.
You’ll want to create at least two header files to contain the definitions of MemCpy
, MemSet
, MemCmp
, MemMove
, strlen
, abort
, and PutChar
. These are a few basic functions that enable us to expand our kernel further. We will start with the mem
functions:
Now we’ll implement those functions in their own file. Here’s the code:
We now have some basic memory manipulation tools at our disposal, but we need a proper ‘print’ implementation and some of its incarnations. To accomplish this, I want to take a slight detour from the libc implementation and create something like a software UART (universal asynchronous receiver-transmitter).
To start, we’ll create a struct called ‘Uart’ and give it one field: the base memory address. We will add more to this structure later on, but for now, we’ll write it out with a single field and add two functions alongside it. Here’s what that looks like:
Then we implement these functions, as shown below:
Our UartInit
function creates a pointer to an instance of the Uart
structure. This pointer will be passed to the other related functions — Read
and Write
.
In our Read
function, we have two parameters: the Uart
pointer and an integer offset. Shifting the focus to the function body, we can see that we declare a pointer to a volatile, unsigned 8-bit integer and set it to the base address in our struct. We use ‘volatile’ here so that the compiler does not optimize away our code. Finally, we dereference and return the combined value of the volatile pointer and the offset.
To write our value to the UART, we take a pointer to the Uart
and an offset, much like Read
, but we also take an unsigned 8-bit integer value to write. We use ‘volatile’ in the same manner as before, assigning our value to the combined pointer and offset.
Now that we have a very basic memory-mapped IO setup, we can get back to our stdio
headers. We will rewrite our shell functions to use our UART definitions later on. For now, let’s write out our Print
and Printf
functions:
After our include guard, we pull in from a header I haven’t talked about yet called sys/cdefs.h
. This is a file I created to house some of my personal #defines and type aliases and such. It’s not that important as there’s currently only one definition in that file beyond its own include guard, so we will revisit it in a later installment.
Next, we implement our functions with the following code:
There is a lot to unpack here, so we will start with the PutChar
function. This takes an integer to represent a “character code” and then converts that into a char
before feeding it to the UartWrite
function that then performs a volatile write. For now, this function initializes the UART on its own, but we will change that later.
The static Print
function takes a character array and simply loops until it puts each character supplied through the PutChar
function, though Printf
gets necessarily more complex.
Because we’re trying to accomplish some basic formatting, we must get creative: we need to check for some commonly used special formatting characters.
Firstly, we have to make sure we’re not writing nothing, so we check for our EOF
condition, and then we move on to check whether we have encountered a ‘format expression.’ These start with the %
symbol, and the following character denotes the type of formatting we’ll be doing: %s
for string formatting, %c
for character formatting. Another time, we will implement formatting for integers, floats, and other data types.
Now we have one more function to implement: abort
.
This function is called when our system encounters an unrecoverable error. Ideally, we should never reach this point, but we need the ability to perform a fast halt of the system should we get there.
Now that we have some basic libc functionality, we may compile our program as last time, and we can type away! We don’t yet have a “shell” like Bash or ZSH, but we’ll work on implementing something more proper in the next installment, so stay tuned!
Resources
As a disclaimer, some of the code was borrowed or translated from other sources:
My git repositories:
The story so far:
Once again, I very much appreciate you reading my story, and I look forward to the next time we meet! ❤