In today's blog post, I'll be discussing how you can build your own operating system from scratch using the C and X64 programming languages. You'll need enough experience in these languages to at least write some essential functions and address hardware components. In order to develop an operating system, your best bet is to use Linux (I'm using Ubuntu) to compile it as it's much easier to install the required software tools. If you want access to the code to build your own non-Linux kernel operating system, my source code is available here: https://github.com/alexanderwalford-official/Renovate-OS
1. Cross-Platform Compiler & Boot Loader
As we'll be using the C language to build our operating system we'll need to use a cross-platform compiler (GCC) to compile it into assembly language. This may sound daunting at first, but it's all quite simple for us as we don't have to mess around with this too much; just to compile our code. You'll also need to use a boot loader such as GRUB so you can actually execute your code; there's a minimal amount of setup required and all the hard work has already been done for you.
2. Build Tools
I personally recommend that you create a "make file" that will allow you to simplify the building process, you can view my "make file" in the repository I have provided the link to. Essentially, this file will follow your instructions to execute commands in the command line and will create the files that you require to make the bootable ISO file, including the ISO file itself on the final command.
3. The Kernel
What is a kernel and what does it do? What sort of code structure should it contain and what are the best practices? The kernel's primary role in an operating system is to execute code in other class files, you could call it the main class file for our operating system; it contains several references to external methods and calls them sequentially. For a general guide on how to write your kernel; ensure that only the code that must be executed immediately is present, do not provide references to methods or class files if you do not need them immediately; instead, do this in another class file.
4. Writing Hardware Drivers
This is perhaps the most important part of our operating system; writing hardware drivers. They are what allow us to have a direct I/O interface with physical hardware components connected to the motherboard. The drivers you write should be as robust as possible, contain only essential code, and have performance as the main focus of development. Writing an initial device driver that works with most modern-day graphics cards or APUs should be the top priority for you. Even if the only function of your driver is to print some text then it's still better than trying to render shapes and images directly without any extensive backend development.
5. Processes And Data Structures
When developing my operating system, this is what really got me interested and motivated. The whole concept of "what is a process" and "how do we define certain structures" really got the cogs in my head turning. I used a series of custom data structures to create an array structure of processes that I then handled with multiple custom methods, including my own custom, single-threaded process stack (which I plan to multithread in the future).
6. The File System
File systems may seem simple to end users, but to completely develop one from absolutely nothing seems like an impossible task for a lone developer. However, I gave it a shot and used a collection of data structures and arrays to create a temporary file system that stays active in memory. To write this to the disk, I have no idea how to do this yet so I'm still learning. I'll update you on my progress later on.
7. Programming Interface
In order to write applications for our operating system, we need some kind of programming interface that can compile our code using a fast, native compiler. This is incredibly difficult as we're essentially writing our own programming language in basic C without the inclusion of external libraries such as STDIO.h which means we'll have to write them ourselves to interact with our operating system. Unfortunately, we can't copy the code from these libraries directly even though they are open source as they will depend on a specific operating system kernel.
8. Almost At Machine Level
As previously mentioned, we can't use references to existing operating system-dependent libraries. Therefore, methods terms such as NULL, REMOVE, REPLACE, etc cannot be used without a custom implementation which is really what I ended up spending much of my time developing. This is, however, a compromise to not using a Linux-based kernel and we must move on and accept this as this is the task we have set for ourselves. At least we don't have to write much assembly language at this point, making our life a whole ton easier.
9. I/O
Input and output (I/O), are the primary interfaces when it comes to computers, for every input, there should be output. But how do we get input from our input devices such as peripherals? The short answer in this situation is to use a Linux kernel. The longer answer that we're looking for because we're challenging ourselves for some reason? Well, it looks like we've got a custom PS2 input driver to write! As of my current progress, I've not been able to successfully get the keypress values with emulation in virtual box. However, I plan on implementing a USB device driver for input which should make things a lot easier.
However, I wanted to talk about how the PS2 drivers work first. PS2 input is a serial method for input and output, it uses pins to send voltages across a wire into a computer's PS2 multi-pin port. The purpose of our driver is to understand what each of the pulses of these signals means and how they translate into ASCII code characters. For example, the PS2 input device such as a keyboard should send an ACK signal and store it in onboard memory at a specific address which is the meaning for "input device initialised". However, to even receive this signal at all, we must first initialise the PS2 device! This is done by setting the memory address 0xf04 to 1. We can then listen to each memory address, for each character to see if the value becomes 1 (true, boolean).
10. Summary
As you can tell, I personally have a large amount of work to do to complete some basic functionality for my operating system. However, you may get the chance to race me to develop your own from-scratch operating system! If that's the case, then I'm praying for you as I'm currently burning to death in Hell (or at least that's how it feels). Feel free to use the provided source code in the repository I have linked at the start of this post and develop your own solution to my terrible code that I plan to eventually improve over time.
Comments
Post a Comment