Sitelet https://github.com/e7ite/EliteOS
Skip to content

Latest commit

 

History

37 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

EliteOS

This is an operating system project for the x86-64 architecture built from the ground up for learning purposes.

The project is still in its early stages. Right now, it consists of a legacy BIOS bootloader that starts in 16-bit real mode, enters 32-bit protected mode, and verifies that the processor supports x86-64 long mode.

The long-term goal is to build a stable operating system that runs on real x86-64 hardware, continuously improve its performance, and learn how each major part of an operating system works along the way.

The name is just for fun. I like the word Elite.

I am learning OS development as I build this, so expect mistakes, experiments, refactoring, and plenty of changes along the way.

How to Use

1. Install the required dependencies

If the required tools are already installed, skip this step.

On Debian, Ubuntu, or WSL, run:

sudo apt update
sudo apt install nasm qemu-system-x86 git fdisk

2. Clone the repository

If the repository is already cloned, skip this step.

Clone the project and move into the new directory:

git clone https://github.com/e7ite/EliteOS.git
cd EliteOS

3. Pick where to run EliteOS

               Where do you want to run EliteOS?
                               |
              +---------------------------------+
              |                                 |
            QEMU                        Physical hardware
   a quick test on your PC             boot a real PC from
           in a VM                         a USB drive

Jump to QEMU or Physical hardware.

QEMU

Run this command:

./scripts/run-qemu.sh

That is the whole development loop. The script assembles the bootloader, rebuilds and verifies the disk image, and boots it, so every run starts from freshly built code and the same disk layout used on physical hardware.

If you get an error like this:

bash: ./scripts/run-qemu.sh: Permission denied

the scripts have lost their executable permission. This usually happens when the project is downloaded as a ZIP archive rather than cloned with git. Fix it with:

chmod +x scripts/*.sh

To boot an image that has already been built, without rebuilding it first, run QEMU directly:

qemu-system-x86_64 -drive format=raw,file=build/eliteos.img

Physical hardware

Run both of these commands. The first assembles the bootloader, and the second packages it into the bootable disk image:

./scripts/build-bootloader.sh
./scripts/build-disk-image.sh

These are the same two commands ./scripts/run-qemu.sh runs before it starts a virtual machine. They are run directly here because this path ends with a file to write to a USB drive rather than a running QEMU window.

Write the resulting build/eliteos.img to a USB drive using a raw-image writing tool such as Rufus.

The current legacy BIOS boot path requires firmware that supports Legacy BIOS or Compatibility Support Module (CSM) booting.

Enable Legacy BIOS/CSM support if necessary, then use the system's one-time boot menu or firmware boot order to boot from the USB drive.

The legacy BIOS path has been successfully tested using CSM booting on a PC with the following parts:

  • ASRock X470 Master SLI/ac motherboard
  • AMD Ryzen 7 2700X
  • NVIDIA GeForce GTX 1080 Ti

This does not imply driver support for every device. I plan to expand real-hardware testing to additional systems and hardware configurations as the project grows.

The need for this particular MBR layout was observed on the tested physical machine. Other firmware implementations may accept different disk layouts.

Current Status

  • Boot from legacy BIOS
  • Execute in 16-bit real mode
  • Set up a Global Descriptor Table (GDT)
  • Enter 32-bit protected mode
  • Detect x86-64 long mode support
  • Enter 64-bit long mode
  • Load a separate x86-64 kernel
  • Add a UEFI boot path
  • Boot the same kernel through both BIOS and UEFI
  • Add GRUB/Multiboot2 compatibility

The immediate goal is to enter 64-bit long mode. After that, the next major milestone is loading a separate x86-64 kernel and establishing a clean boundary between the bootloader and kernel.

Build Artifacts and Disk Layout

The build produces two files, and the distinction between them matters:

build/boot.bin      assembled BIOS boot sector, an internal build artifact
build/eliteos.img   the canonical bootable disk image

build/eliteos.img is what gets booted, by QEMU and by physical hardware alike. build/boot.bin is never booted directly. Using one image for both keeps the disk layout tested under emulation identical to the one written to real hardware.

scripts/build-bootloader.sh assembles src/boot.asm into build/boot.bin with a single nasm call. The -f bin output format emits a flat binary with no object headers, because the processor executes the boot sector directly and there is no loader to interpret a file format.

scripts/build-disk-image.sh turns that boot sector into the disk image. It removes any previous image, creates a fresh 32 MiB image, writes a conventional MBR partition table containing one bootable partition of type 0x0c starting at sector 2048, installs the boot code, and then verifies the result. Recreating the image instead of overwriting one in place prevents bytes written by an earlier build from surviving somewhere the current build does not touch.

A traditional 512-byte MBR is laid out as:

Bytes 0-445     Bootloader area
Bytes 446-509   Partition table
Bytes 510-511   0x55AA boot signature

Only the first 446 bytes of build/boot.bin are installed into the image. The partition table begins at byte 446, so copying the whole sector would destroy it. src/boot.asm fails to assemble if the MBR-resident bootloader grows past that 446-byte area.

scripts/build-disk-image.sh expects build/boot.bin to already exist and will not build it. Keeping compilation separate means building the bootloader does not require disk-image tooling such as sfdisk, and lets the image be built without launching QEMU. scripts/run-qemu.sh composes both steps for the normal development loop.

Dependencies

  • NASM — x86 assembler
  • sfdisk — creates the MBR partition table used by the bootable disk image (provided by the fdisk package on Debian/Ubuntu)
  • QEMU — virtual x86-64 machine used for development and automated testing
  • Git — source control

Goals

  • Build a stable operating system that runs on real x86-64 hardware.
  • Continuously improve the performance and efficiency of the system as it develops.
  • Implement components myself when doing so is important to understanding how operating systems and the underlying hardware work.
  • Build each subsystem deeply enough to understand its important concepts, tradeoffs, and failure modes.
  • Build implementations that are stable and testable enough to be used as real parts of the operating system.
  • Understand the full path from firmware execution through the kernel and eventually into userspace.
  • Gain hands-on experience with memory management, interrupts, scheduling, drivers, storage, networking, graphics, and other major OS subsystems.
  • Expand hardware support and system capabilities over time as the project grows.
  • Maintain clean subsystem boundaries so implementations can evolve without unnecessarily coupling the entire system together.
  • Use QEMU for fast iteration, debugging, and automated testing while treating physical hardware as the primary target.
  • Introduce unit, integration, and end-to-end testing alongside the systems they validate.
  • Document important architectural decisions when they become relevant to active development.

Non-Goals

  • Competing with production operating systems such as Linux, Windows, or BSD.
  • Requiring every subsystem to reach production-level sophistication before moving on to other parts of the operating system, that can be revisited later.
  • Making broad hardware support or feature completeness a prerequisite for progress elsewhere.
  • Reimplementing a component when an existing implementation can be reused or ported without meaningfully reducing what I would learn about how the operating system works.
  • Designing the entire operating system before implementation begins.
  • Preserving early design decisions when implementation shows that they were wrong.

The goal is not to make every subsystem as sophisticated as its equivalent in a mature operating system before moving forward. A scheduler, virtual memory manager, filesystem, network stack, GPU driver, or any other subsystem can be stable and useful without being finished forever.

These systems can continue to be expanded, optimized, and redesigned as the project grows.

Development Approach

  • Implementation-driven: Get hands-on with a subsystem early instead of spending weeks designing code that does not exist yet.
  • Design important boundaries early: Decisions that could create major coupling or be especially expensive to reverse should still be considered before implementation.
  • Design details when they become relevant: More detailed design documents can be written alongside the subsystem being implemented.
  • Keep documentation grounded in reality: Document what has actually been implemented, learned, and decided.
  • Accept refactoring: Some early assumptions will be wrong. Finding those mistakes and understanding how to correct them is part of the project.
  • Keep improving: Moving on from a subsystem does not mean it is finished forever. It can be revisited as new requirements, performance problems, hardware, or better designs become relevant.
  • Choose implementation depth based on what I want to learn: If building something myself is important to understanding how the OS works, I will build it. If reusing or porting an existing implementation preserves that understanding, I can spend that time learning another part of the system instead.

Tradeoffs

Advantages

  • More time is spent implementing and debugging real systems.
  • Design decisions can be informed by actual implementation experience.
  • Documentation stays connected to code that exists.
  • Mistakes provide experience diagnosing and correcting bad assumptions.
  • The project can make progress across many areas without requiring any one subsystem to be perfected first.
  • Subsystems can evolve as the requirements of the operating system become clearer.

Disadvantages

  • Some early decisions may require significant refactoring.
  • Problems may appear later that better upfront planning could have prevented.
  • Interfaces may evolve as later subsystems expose requirements that were not obvious earlier.
  • Implementing important components myself will often take longer than relying entirely on mature existing implementations.

These tradeoffs are intentional. EliteOS is primarily a learning project, and learning how to recognize, understand, and correct bad assumptions is part of the experience.

Testing

Physical x86-64 hardware is the primary testing target for EliteOS. QEMU is used alongside it for faster iteration, debugging, and automated testing.

QEMU and physical hardware use the same build/eliteos.img disk image so both environments exercise the same bootloader and disk layout.

The goal is for implemented subsystems and features to work correctly on real hardware, not only under emulation.

As the project grows, testing will include:

  • Unit tests for individual components and algorithms
  • Integration tests for interactions between subsystems and drivers
  • End-to-end tests that boot complete EliteOS images in QEMU
  • Real-hardware validation of implemented subsystems and features

Automated tests will be added to CI alongside the features they validate rather than being deferred until later in development.

QEMU is a development and testing tool, not the final target. EliteOS is intended to run on actual x86-64 hardware.

Project Direction

EliteOS will support multiple ways of reaching the same x86-64 kernel rather than tying the kernel to a specific bootloader.

Legacy BIOS ---> EliteOS BIOS bootloader ---┐
                                             ├---> EliteOS x86-64 kernel
UEFI ----------> EliteOS UEFI bootloader ---┤
                                             │
GRUB / Multiboot2 ---------------------------┘

The BIOS path provides experience with the traditional x86 boot process, including real mode, protected mode, paging, and the transition into 64-bit long mode.

The UEFI path will provide experience with the firmware environment used by modern x86-64 systems.

GRUB/Multiboot2 compatibility will provide a standardized alternate boot path and help verify that the kernel remains independent from the custom bootloaders.

Longer-term areas I want to explore include:

  • Memory management
  • Interrupts
  • Multitasking and scheduling
  • Userspace
  • System calls
  • Filesystems
  • Hardware drivers
  • Networking
  • Graphics
  • GPU drivers
  • A graphical user interface

These areas are directions rather than a fixed design. Individual goals can be expanded as I reach them and understand their requirements better.

FAQ

Why not use Limine or another existing bootloader?

Using something like Limine would be a practical way to reach kernel development much faster. If the main objective were simply getting an operating system running as quickly as possible, that would often be the better engineering choice.

For this project, understanding how execution gets from firmware into the kernel is itself an important part of what I want to learn.

A production-quality bootloader can be a substantial project on its own. I want to implement enough of the boot process to understand it deeply and provide this project with reliable boot paths, without turning the bootloader into the entire project.

Where does the bootloader stop?

The exact implementation will evolve, but the responsibility of the bootloader is roughly:

Firmware
   ↓
EliteOS bootloader
   ↓
collect required firmware and hardware information
   ↓
load the kernel
   ↓
establish the required CPU state and memory mappings
   ↓
construct common boot information
   ↓
enter the x86-64 kernel

For the legacy BIOS path, reaching that point requires learning and implementing the transitions through real mode, protected mode, paging, and 64-bit long mode.

The UEFI loader will reach the same kernel through a different firmware environment. It will eventually be responsible for tasks such as:

  • Loading the same kernel
  • Obtaining the system memory map
  • Obtaining graphics information when available
  • Exiting UEFI boot services
  • Constructing the common boot information
  • Transferring control to the kernel

Once a boot path can reliably perform those responsibilities, adding unrelated bootloader features is not automatically a priority.

Why support both legacy BIOS and UEFI?

They expose different parts of x86 system development.

BIOS provides experience with:

  • 16-bit real mode
  • Legacy PC firmware interfaces
  • Protected mode
  • Paging setup
  • The transition into 64-bit long mode

UEFI provides experience with:

  • Modern x86-64 firmware
  • EFI applications and protocols
  • Firmware memory maps
  • Graphics Output Protocol
  • Boot services
  • ExitBootServices()
  • Booting on modern physical hardware

Supporting both also creates a useful architectural constraint: both paths should eventually reach the same kernel through a common boot interface.

Why support GRUB if EliteOS has its own bootloaders?

The two serve different purposes.

Custom bootloaders:

  • Exist primarily for learning.
  • Give me direct experience with firmware, processor state, and the bootloader-to-kernel transition.

GRUB/Multiboot2:

  • Provides a standardized alternate boot path.
  • Helps verify that the kernel does not depend on assumptions made by my custom loaders.
  • Provides another way to exercise the bootloader/kernel boundary.

The same x86-64 kernel should eventually work regardless of which supported boot path reaches it.

Aren't you going to make mistakes by developing this way?

Yes. That is expected.

The goal is not to avoid thinking ahead. The goal is to avoid becoming stuck trying to predict every future problem before writing code.

If an early decision turns out to be wrong, I want to understand:

  • Why it failed
  • Which assumption was incorrect
  • What symptoms the design caused
  • How the design can be improved
  • How I could recognize the same problem earlier in the future

Learning how to identify and correct those decisions is part of the project.

If this is "from the ground up," why not write everything yourself?

"From the ground up" does not mean that every existing component has to be reinvented.

The boundary is whether implementing something myself is important to understanding how the operating system works.

If implementing a component exposes concepts or behavior that I specifically want to understand, I want to build it myself. That is why areas such as booting, memory management, scheduling, interrupts, and other fundamental OS mechanisms are important parts of this project.

If an existing component can be reused or ported without taking away much of that understanding, I am comfortable using it and spending that time exploring another part of the operating system instead.

The goal is to maximize what I learn about operating systems, not maximize the amount of code I personally rewrite.

References and Credits

Resource Reason
Daniel McCarthy Course on developing a multithreaded kernel from scratch
Intel Software Developer Manuals Intel 64 and IA-32 architecture documentation
AMD Developer Documentation AMD64 architecture and system programming documentation
OSDev Wiki Reference for operating-system development
AI agents (Claude Code, Codex) Explaining operating-system and hardware concepts, reviewing changes, and working through problems while building this

About

An OS for the x86-64 architecture from the ground up, strictly made for learning purposes

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages